diff --git a/tests/test_toolsets.py b/tests/test_toolsets.py index 944d96b742..daf06b469b 100644 --- a/tests/test_toolsets.py +++ b/tests/test_toolsets.py @@ -56,6 +56,24 @@ class TestGetToolset: assert set(ts["tools"]) == {"web_search", "web_extract", "web_search_plus"} + def test_static_and_mcp_alias_with_same_name_are_merged(self, monkeypatch): + # An MCP server named like a built-in toolset registers a bare alias to its + # `mcp-` toolset; the static entry must union those tools in (and keep + # its own includes) instead of shadowing the server. + TOOLSETS["_mergetest"] = {"description": "static", "tools": ["builtin_tool_a"], "includes": ["web"]} + try: + reg = ToolRegistry() + reg.register(name="mcp__mergetest_call", toolset="mcp-_mergetest", + schema=_make_schema("mcp__mergetest_call", "Call"), handler=_dummy_handler) + reg.register_toolset_alias("_mergetest", "mcp-_mergetest") + monkeypatch.setattr("tools.registry.registry", reg) + + ts = get_toolset("_mergetest") + assert {"builtin_tool_a", "mcp__mergetest_call"} <= set(ts["tools"]) + assert ts["includes"] == ["web"] + finally: + del TOOLSETS["_mergetest"] + class TestResolveToolset: def test_leaf_toolset(self): diff --git a/toolsets.py b/toolsets.py index cf7aef5c5a..ffade803fa 100644 --- a/toolsets.py +++ b/toolsets.py @@ -281,8 +281,14 @@ def get_toolset(name: str, *, include_registry: bool = True) -> Optional[Dict[st return toolset if toolset else None if toolset: - merged_tools = sorted(set(toolset.get("tools", [])) | set(registry.get_tool_names_for_toolset(name))) - return {**toolset, "tools": merged_tools} + merged_tools = set(toolset.get("tools", [])) | set(registry.get_tool_names_for_toolset(name)) + # An MCP server named like a built-in toolset ("homeassistant", "browser") registers a bare + # alias to its `mcp-` toolset; without this union the static entry shadows it and the + # server's tools never reach the model even though discovery registered them. + alias_target = registry.get_toolset_alias_target(name) + if alias_target and alias_target != name: + merged_tools |= set(registry.get_tool_names_for_toolset(alias_target)) + return {**toolset, "tools": sorted(merged_tools)} if name in _get_plugin_toolset_names(): # Plugin toolset; shown as its MCP server alias when one exists.