From eba05f43331c1cecc783f657c6a1be4cec966aed Mon Sep 17 00:00:00 2001 From: yucheng Date: Sat, 12 Sep 2026 19:09:17 +0000 Subject: [PATCH] fix(mcp): record OpenAPI tool definitions for pre-call guardrail metadata Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../mcp_server/mcp_server_manager.py | 8 +++-- .../mcp_server/test_mcp_server_manager.py | 34 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index f49fa718139..27265b6585e 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -4442,11 +4442,9 @@ class MCPServerManager: # applied (e.g. "test_petstore-getinventory"). Do NOT pass them # through _create_prefixed_tools — that would add the prefix a second # time producing "test_petstore-test_petstore-getinventory". - if add_prefix: - return tools prefix: Final = get_server_prefix(server) sep: Final = MCP_TOOL_PREFIX_SEPARATOR - return [ # mutable-ok: returned through the list[MCPTool] listing contract + unprefixed_tools: Final = [ # mutable-ok: returned through the list[MCPTool] listing contract ( t.model_copy(update={"name": t.name[len(prefix) + len(sep) :]}) if t.name.startswith(f"{prefix}{sep}") @@ -4454,6 +4452,10 @@ class MCPServerManager: ) for t in tools ] + self._listed_tools_by_server_id[server.server_id] = MappingProxyType( + {t.name: t for t in unprefixed_tools} + ) + return tools if add_prefix else unprefixed_tools else: tools = await self._fetch_tools_with_timeout(client, server.name) self._remember_upstream_initialize_instructions(server, client) diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index d8511617699..cddc2feb6c1 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -6632,6 +6632,40 @@ class TestMCPServerManager: listed = manager.get_listed_tool(server, "echo") assert listed is not None and listed.description == "shared" + @pytest.mark.asyncio + @pytest.mark.parametrize("add_prefix", [True, False]) + async def test_openapi_listing_records_listed_tools(self, add_prefix): + from litellm.proxy._experimental.mcp_server.tool_registry import global_mcp_tool_registry + + server = MCPServer( + server_id="petstore-id", + name="petstore", + alias="petstore", + transport=MCPTransport.http, + url=None, + spec_path="/spec.yaml", + ) + manager = MCPServerManager() + manager._create_mcp_client = AsyncMock(return_value=AsyncMock()) + + async def _handler(**kwargs): + return None + + with patch.dict(global_mcp_tool_registry.tools, {}, clear=True): + global_mcp_tool_registry.register_tool( + name="petstore-list_pets", + description="List pets", + input_schema={"type": "object", "properties": {"limit": {"type": "integer"}}}, + handler=_handler, + ) + listed = await manager._get_tools_from_server(server=server, add_prefix=add_prefix) + + assert [t.name for t in listed] == ["petstore-list_pets" if add_prefix else "list_pets"] + for name in ("list_pets", "petstore-list_pets"): + tool = manager.get_listed_tool(server, name) + assert tool is not None and tool.description == "List pets" + assert tool.inputSchema["properties"] == {"limit": {"type": "integer"}} + @pytest.mark.asyncio async def test_get_allowed_mcp_servers_with_user_api_key_auth(self): """