mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(mcp): name the access group that resolved when an agent vetoes a mixed scope
This commit is contained in:
parent
681eb0eced
commit
222cb293ca
2 changed files with 36 additions and 7 deletions
|
|
@ -1489,10 +1489,14 @@ if MCP_AVAILABLE:
|
|||
)
|
||||
}
|
||||
raise HTTPException(status_code=403, detail=agent_denial)
|
||||
vetoed_group: Final = (
|
||||
next((name for name in requested_names if _registered_server_id(name) is None), None)
|
||||
if resolved_ids
|
||||
else None
|
||||
vetoed_group: Final = next(
|
||||
(
|
||||
name
|
||||
for name in requested_names
|
||||
if _registered_server_id(name) is None
|
||||
and any(name in (server.access_groups or ()) for server in resolved_without_agent)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if vetoed_group is not None:
|
||||
group_denial: Final[_McpDeniedDetail] = {
|
||||
|
|
|
|||
|
|
@ -1336,11 +1336,13 @@ def _denied_scope_manager(known_server_names_to_ids: dict[str, str]) -> MagicMoc
|
|||
return manager
|
||||
|
||||
|
||||
def _scope_resolver(resolved_without_agent: list[str]) -> AsyncMock:
|
||||
def _scope_resolver(resolved_without_agent: list[str], access_groups: tuple[str, ...] = ()) -> AsyncMock:
|
||||
async def resolve(user_api_key_auth, mcp_servers, client_ip=None):
|
||||
if user_api_key_auth is not None and user_api_key_auth.agent_id:
|
||||
return []
|
||||
return [MagicMock(server_id=server_id) for server_id in resolved_without_agent]
|
||||
return [
|
||||
MagicMock(server_id=server_id, access_groups=list(access_groups)) for server_id in resolved_without_agent
|
||||
]
|
||||
|
||||
return AsyncMock(side_effect=resolve)
|
||||
|
||||
|
|
@ -1450,7 +1452,7 @@ async def test_scoped_list_access_group_vetoed_by_agent_names_agent_and_group():
|
|||
user_api_key_auth,
|
||||
["prod-group"],
|
||||
_denied_scope_manager({}),
|
||||
_scope_resolver(resolved_without_agent=["srv-github"]),
|
||||
_scope_resolver(resolved_without_agent=["srv-github"], access_groups=("prod-group",)),
|
||||
)
|
||||
|
||||
assert denial.status_code == 403
|
||||
|
|
@ -1460,6 +1462,29 @@ async def test_scoped_list_access_group_vetoed_by_agent_names_agent_and_group():
|
|||
assert "mcp_access_groups" in message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_scoped_list_mixed_unknown_and_vetoed_group_names_the_group_that_resolved():
|
||||
"""With an unknown name ahead of the agent-vetoed group in the scope, the 403 must name the group
|
||||
whose servers the key can reach, never the unknown name, or the admin is told to grant a group
|
||||
that does not exist."""
|
||||
pytest.importorskip("litellm.proxy._experimental.mcp_server.server")
|
||||
|
||||
user_api_key_auth = UserAPIKeyAuth(api_key="test_key", user_id="test_user", agent_id="agent-123")
|
||||
|
||||
denial = await _denied_scoped_list(
|
||||
user_api_key_auth,
|
||||
["no-such-group", "prod-group"],
|
||||
_denied_scope_manager({}),
|
||||
_scope_resolver(resolved_without_agent=["srv-github"], access_groups=("prod-group",)),
|
||||
)
|
||||
|
||||
assert denial.status_code == 403
|
||||
message = denial.detail["error"]
|
||||
assert "access group 'prod-group'" in message
|
||||
assert "no-such-group" not in message
|
||||
assert "agent 'agent-123'" in message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_scoped_list_agent_key_denied_by_key_grants_raises_generic_403():
|
||||
"""When the agent-stripped rerun still resolves nothing, the 403 stays generic instead of
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue