From fa79e52bec8459b2b86073ffafcd190ece7bb02f Mon Sep 17 00:00:00 2001 From: fangliquanflq Date: Fri, 14 Aug 2026 18:48:45 +0800 Subject: [PATCH] fix(mcp): resolve aliases that collide with built-in toolsets --- tests/test_toolsets.py | 16 ++++++++++++++ tests/tools/test_tool_search.py | 37 +++++++++++++++++++++++++++++++++ toolsets.py | 12 +++++++++-- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/tests/test_toolsets.py b/tests/test_toolsets.py index e59ce350f2625..7976426294092 100644 --- a/tests/test_toolsets.py +++ b/tests/test_toolsets.py @@ -55,6 +55,22 @@ class TestGetToolset: assert ts is not None assert set(ts["tools"]) == {"web_search", "web_extract", "web_search_plus"} + def test_merges_colliding_mcp_alias_into_builtin_toolset(self, monkeypatch): + reg = ToolRegistry() + reg.register( + name="mcp__memory__read_graph", + toolset="mcp-memory", + schema=_make_schema("mcp__memory__read_graph", "Read graph"), + handler=_dummy_handler, + ) + reg.register_toolset_alias("memory", "mcp-memory") + + monkeypatch.setattr("tools.registry.registry", reg) + + tools = resolve_toolset("memory") + assert "memory" in tools + assert "mcp__memory__read_graph" in tools + class TestResolveToolset: diff --git a/tests/tools/test_tool_search.py b/tests/tools/test_tool_search.py index d4d66f283c13a..dc344994b8af7 100644 --- a/tests/tools/test_tool_search.py +++ b/tests/tools/test_tool_search.py @@ -460,6 +460,43 @@ class TestRegression_ToolsetScoping: # core tools are never deferrable assert "terminal" not in names + def test_builtin_name_collision_keeps_mcp_alias_searchable(self): + from tools.registry import registry + import model_tools + + self._register("mcp__memory__read_graph", "mcp-memory") + registry.register_toolset_alias("memory", "mcp-memory") + + source_result = model_tools.handle_function_call( + function_name="tool_search", + function_args={"query": "unrelated capability", "limit": 5}, + enabled_toolsets=["memory"], + ) + + source_payload = json.loads(source_result) + assert {source["name"] for source in source_payload["available_sources"]} == { + "memory" + } + + match_result = model_tools.handle_function_call( + function_name="tool_search", + function_args={"query": "read graph", "limit": 5}, + enabled_toolsets=["memory"], + ) + assert "mcp__memory__read_graph" in { + match["name"] for match in json.loads(match_result)["matches"] + } + + call_result = model_tools.handle_function_call( + function_name="tool_call", + function_args={"name": "mcp__memory__read_graph", "arguments": {}}, + enabled_toolsets=["memory"], + ) + assert json.loads(call_result) == { + "ok": True, + "tool": "mcp__memory__read_graph", + } + # --------------------------------------------------------------------------- # Catalog listing (skills-style progressive disclosure) diff --git a/toolsets.py b/toolsets.py index 26d635043f295..ccda20cc59862 100644 --- a/toolsets.py +++ b/toolsets.py @@ -693,9 +693,17 @@ def get_toolset(name: str, *, include_registry: bool = True) -> Optional[Dict[st return toolset if toolset else None if toolset: + registry_toolsets = {name} + alias_target = registry.get_toolset_alias_target(name) + if alias_target: + registry_toolsets.add(alias_target) merged_tools = sorted( - set(toolset.get("tools", [])) - | set(registry.get_tool_names_for_toolset(name)) + set(toolset.get("tools", [])).union( + *( + registry.get_tool_names_for_toolset(toolset_name) + for toolset_name in registry_toolsets + ) + ) ) return {**toolset, "tools": merged_tools}