From 0463a7490566f958226bd260e45d3b3f1ecbe88a Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 22 Jun 2026 18:36:54 -0700 Subject: [PATCH] fix: route hyphenated mcp tools --- .../mcp_server/mcp_server_manager.py | 4 ++- .../proxy/_experimental/mcp_server/server.py | 11 ++++++-- .../proxy/_experimental/mcp_server/utils.py | 20 +++++++++++--- .../mcp_server/test_is_tool_name_prefixed.py | 27 ++++++++++++++++--- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index afec884cd96..3eaac0fafe4 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -3852,7 +3852,9 @@ class MCPServerManager: ( original_tool_name, server_name_from_prefix, - ) = split_server_prefix_from_name(tool_name) + ) = split_server_prefix_from_name( + tool_name, known_server_prefixes=set(prefix_to_server.keys()) + ) normalised_prefix = normalize_server_name(server_name_from_prefix) matched_server = prefix_to_server.get(normalised_prefix) if matched_server is not None and ( diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 5af1aac1463..f263c851723 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -2688,8 +2688,15 @@ if MCP_AVAILABLE: # translate it back to the original prefixed name before any routing. name = _resolve_display_name_to_original(name, allowed_mcp_servers) - # Remove prefix from tool name for logging and processing - original_tool_name, server_name = split_server_prefix_from_name(name) + allowed_server_prefixes = { + normalize_server_name(prefix) + for server in allowed_mcp_servers + for prefix in iter_known_server_prefixes(server) + if prefix + } + original_tool_name, server_name = split_server_prefix_from_name( + name, known_server_prefixes=allowed_server_prefixes + ) requested_server: Optional[MCPServer] = None if requested_server_id: diff --git a/litellm/proxy/_experimental/mcp_server/utils.py b/litellm/proxy/_experimental/mcp_server/utils.py index b0141d3207c..f98b804ddef 100644 --- a/litellm/proxy/_experimental/mcp_server/utils.py +++ b/litellm/proxy/_experimental/mcp_server/utils.py @@ -323,8 +323,18 @@ def iter_known_server_prefixes(server: Any) -> Iterator[str]: yield from _emit(server_id) -def split_server_prefix_from_name(prefixed_name: str) -> Tuple[str, str]: +def split_server_prefix_from_name( + prefixed_name: str, + known_server_prefixes: Optional[Set[str]] = None, +) -> Tuple[str, str]: """Return the unprefixed name plus the server name used as prefix.""" + if known_server_prefixes: + for prefix in sorted(known_server_prefixes, key=len, reverse=True): + normalized_prefix = normalize_server_name(prefix) + separator = f"{normalized_prefix}{MCP_TOOL_PREFIX_SEPARATOR}" + if prefixed_name.startswith(separator): + return prefixed_name[len(separator) :], normalized_prefix + if MCP_TOOL_PREFIX_SEPARATOR in prefixed_name: parts = prefixed_name.split(MCP_TOOL_PREFIX_SEPARATOR, 1) if len(parts) == 2: @@ -360,8 +370,12 @@ def is_tool_name_prefixed( return False if known_server_prefixes is not None: - candidate_prefix = tool_name.split(MCP_TOOL_PREFIX_SEPARATOR, 1)[0] - return normalize_server_name(candidate_prefix) in known_server_prefixes + return any( + tool_name.startswith( + f"{normalize_server_name(prefix)}{MCP_TOOL_PREFIX_SEPARATOR}" + ) + for prefix in known_server_prefixes + ) # Legacy fallback – separator present somewhere in the name. return True diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_is_tool_name_prefixed.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_is_tool_name_prefixed.py index 8f09e2410c4..351048b8a84 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_is_tool_name_prefixed.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_is_tool_name_prefixed.py @@ -4,9 +4,10 @@ Tests for is_tool_name_prefixed with known_server_prefixes parameter. Verifies fix for https://github.com/BerriAI/litellm/issues/25081 """ -import pytest - -from litellm.proxy._experimental.mcp_server.utils import is_tool_name_prefixed +from litellm.proxy._experimental.mcp_server.utils import ( + is_tool_name_prefixed, + split_server_prefix_from_name, +) # --------------------------------------------------------------------------- @@ -36,7 +37,7 @@ class TestLegacyBehaviour: class TestWithKnownPrefixes: """When known_server_prefixes is supplied, only real prefixes match.""" - PREFIXES = {"myserver", "weather_api", "code_tools"} + PREFIXES = {"myserver", "weather_api", "code_tools", "deepwiki-mcp"} def test_known_prefix_returns_true(self): assert ( @@ -82,3 +83,21 @@ class TestWithKnownPrefixes: ) is True ) + + def test_hyphenated_server_prefix_returns_true(self): + assert ( + is_tool_name_prefixed( + "deepwiki-mcp-read_wiki_structure", + known_server_prefixes=self.PREFIXES, + ) + is True + ) + + def test_hyphenated_server_prefix_split_uses_longest_known_prefix(self): + tool_name, server_name = split_server_prefix_from_name( + "deepwiki-mcp-read_wiki_structure", + known_server_prefixes=self.PREFIXES, + ) + + assert tool_name == "read_wiki_structure" + assert server_name == "deepwiki-mcp"