fix(toolsets): merge MCP tools when alias collides with static toolset
If an MCP server registers itself under the same name as a built-in toolset (e.g. an MCP "homeassistant" running alongside the built-in `homeassistant` toolset), `get_toolset()` returned the static definition early and the MCP tools were silently shadowed — they got registered into `mcp-homeassistant` but never surfaced when the agent looked up `homeassistant`. Detect the collision via the registry alias (`alias_target` starting with `mcp-`) and merge the two tool lists. The description is annotated so the source is visible in `/toolsets info`.
This commit is contained in:
@@ -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-<name>` 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):
|
||||
|
||||
10
toolsets.py
10
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-<name>` 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.
|
||||
|
||||
Reference in New Issue
Block a user