mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(mcp): list OpenAPI tools by exact prefix boundary so overlapping server prefixes stay separate
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
eba05f4333
commit
4296a5d5d7
2 changed files with 41 additions and 9 deletions
|
|
@ -4436,21 +4436,15 @@ class MCPServerManager:
|
|||
if server.spec_path:
|
||||
# OpenAPI tools were stored in the registry under the prefix
|
||||
# active at registration time — fetch by that same prefix.
|
||||
_tools: Final = global_mcp_tool_registry.list_tools(tool_prefix=get_server_prefix(server))
|
||||
registry_prefix: Final = normalize_server_name(get_server_prefix(server)) + MCP_TOOL_PREFIX_SEPARATOR
|
||||
_tools: Final = global_mcp_tool_registry.list_tools(tool_prefix=registry_prefix)
|
||||
tools = global_mcp_tool_registry.convert_tools_to_mcp_sdk_tool_type(_tools)
|
||||
# OpenAPI tools are stored in the registry with their prefix already
|
||||
# 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".
|
||||
prefix: Final = get_server_prefix(server)
|
||||
sep: Final = MCP_TOOL_PREFIX_SEPARATOR
|
||||
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}")
|
||||
else t
|
||||
)
|
||||
for t in tools
|
||||
t.model_copy(update={"name": t.name[len(registry_prefix) :]}) for t in tools
|
||||
]
|
||||
self._listed_tools_by_server_id[server.server_id] = MappingProxyType(
|
||||
{t.name: t for t in unprefixed_tools}
|
||||
|
|
|
|||
|
|
@ -6666,6 +6666,44 @@ class TestMCPServerManager:
|
|||
assert tool is not None and tool.description == "List pets"
|
||||
assert tool.inputSchema["properties"] == {"limit": {"type": "integer"}}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_openapi_listing_ignores_overlapping_server_prefix(self):
|
||||
from litellm.proxy._experimental.mcp_server.tool_registry import global_mcp_tool_registry
|
||||
|
||||
server = MCPServer(
|
||||
server_id="pet-id",
|
||||
name="pet",
|
||||
alias="pet",
|
||||
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="pet-petstore-list",
|
||||
description="Local pet tool",
|
||||
input_schema={"type": "object", "properties": {"limit": {"type": "integer"}}},
|
||||
handler=_handler,
|
||||
)
|
||||
global_mcp_tool_registry.register_tool(
|
||||
name="petstore-list",
|
||||
description="Foreign petstore tool",
|
||||
input_schema={"type": "object", "properties": {"status": {"type": "string"}}},
|
||||
handler=_handler,
|
||||
)
|
||||
listed = await manager._get_tools_from_server(server=server, add_prefix=True)
|
||||
|
||||
assert [t.name for t in listed] == ["pet-petstore-list"]
|
||||
tool = manager.get_listed_tool(server, "petstore-list")
|
||||
assert tool is not None and tool.description == "Local pet tool"
|
||||
assert tool.inputSchema["properties"] == {"limit": {"type": "integer"}}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_allowed_mcp_servers_with_user_api_key_auth(self):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue