mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
fix(mcp): scope the Agent 365 connect challenge to single-server routes and drop stale listed tools on invalidation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
b1c3573390
commit
485f4522e4
4 changed files with 26 additions and 1 deletions
|
|
@ -4501,6 +4501,7 @@ class MCPServerManager:
|
|||
self._prompt_discovery_cache.invalidate(server_id)
|
||||
self._resource_discovery_cache.invalidate(server_id)
|
||||
self._template_discovery_cache.invalidate(server_id)
|
||||
self._listed_tools_by_server_id.pop(server_id, None)
|
||||
|
||||
def _discovery_key(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -4144,10 +4144,13 @@ if MCP_AVAILABLE:
|
|||
# header lost, so the discovery flow needs this pre-emptive challenge. Servers gated by an
|
||||
# Agent 365 guardrail (OBO to the evaluate API) get the same challenge, also when the only
|
||||
# bearer is the LiteLLM key itself, which admits the caller but is not an exchangeable subject.
|
||||
# Only on the server's own route: the per-server metadata ``resource`` must equal the URL the
|
||||
# client connected to (RFC 9728 3.3), which aggregate ``/mcp`` and multi-server connects never do.
|
||||
if server and (
|
||||
(server.auth_type == MCPAuth.oauth2_token_exchange and not oauth2_headers)
|
||||
or (
|
||||
not agent_365_subject_token_present(oauth2_headers)
|
||||
_get_mcp_servers_in_path(get_route_relative_request_path(scope)) == [server_name]
|
||||
and not agent_365_subject_token_present(oauth2_headers)
|
||||
and agent_365_authorization_servers(server, user_api_key_auth)
|
||||
)
|
||||
):
|
||||
|
|
|
|||
|
|
@ -9020,6 +9020,14 @@ class TestAgent365ChallengeAtConnect:
|
|||
in www_authenticate
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("path", ["/mcp", "/mcp/tools,other"])
|
||||
async def test_aggregate_route_is_not_challenged_at_connect(self, agent_365_guardrail, path):
|
||||
"""The per-server metadata's ``resource`` can never equal the aggregate ``/mcp`` URL the client
|
||||
connected to (RFC 9728 3.3), and one guarded server must not 401 a multi-server connect, so the
|
||||
Agent 365 challenge is left to tools/call there."""
|
||||
assert await self._connect(self._server([self.GATEWAY_SCOPE]), None, path=path) is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_entra_assertion_present_connects(self, agent_365_guardrail):
|
||||
bearer = {"Authorization": "Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1LTEifQ.c2ln"}
|
||||
|
|
|
|||
|
|
@ -6606,6 +6606,19 @@ class TestMCPServerManager:
|
|||
assert by_prefixed_name is not None and by_prefixed_name.description == "v2"
|
||||
assert manager.get_listed_tool(server, "missing") is None
|
||||
|
||||
def test_invalidate_discovery_lists_drops_listed_tools(self):
|
||||
manager = MCPServerManager()
|
||||
server = MCPServer(server_id="srv", name="srv", transport=MCPTransport.http, url="http://srv")
|
||||
other = MCPServer(server_id="other", name="other", transport=MCPTransport.http, url="http://other")
|
||||
manager._create_prefixed_tools([MCPTool(name="echo", description="old", inputSchema={})], server)
|
||||
manager._create_prefixed_tools([MCPTool(name="ping", description="kept", inputSchema={})], other)
|
||||
|
||||
manager._invalidate_discovery_lists(server.server_id)
|
||||
|
||||
assert manager.get_listed_tool(server, "echo") is None
|
||||
kept = manager.get_listed_tool(other, "ping")
|
||||
assert kept is not None and kept.description == "kept"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_allowed_mcp_servers_with_user_api_key_auth(self):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue