From 6a7178af91c6d8f2114e359dcd4e368fb803b568 Mon Sep 17 00:00:00 2001 From: Alessandro <155005371+3clyp50@users.noreply.github.com> Date: Thu, 16 Jul 2026 02:29:39 +0200 Subject: [PATCH] Fix MCP lookup for Responses tool aliases Resolve provider-safe Responses tool names back to their canonical MCP names inside the MCP handler before dispatch. Preserve canonical lookups and reject stale or unrelated aliases so local tool resolution can continue. Add regression coverage for Google Workspace-style names and document the lookup contract. --- helpers/mcp_handler.py | 12 +++++++++- helpers/mcp_handler.py.dox.md | 1 + tests/test_mcp_handler_multimodal.py | 35 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/helpers/mcp_handler.py b/helpers/mcp_handler.py index e30c3916c..1a0f733f2 100644 --- a/helpers/mcp_handler.py +++ b/helpers/mcp_handler.py @@ -45,6 +45,7 @@ from helpers import dirty_json, media_artifacts from helpers.print_style import PrintStyle from helpers.tool import Tool, Response from helpers.defer import DeferredTask +from helpers.responses_tools import original_tool_name MCP_MEDIA_TOKENS_ESTIMATE = 1500 @@ -1211,7 +1212,16 @@ class MCPConfig(BaseModel): if effective_config is not self: return effective_config.get_tool(agent, tool_name) if not self.has_tool(tool_name): - return None + get_data = getattr(agent, "get_data", None) + name_map_key = getattr(agent, "DATA_NAME_RESPONSES_TOOL_NAME_MAP", "") + tool_name = original_tool_name( + tool_name, + get_data(name_map_key) + if name_map_key and callable(get_data) + else None, + ) + if not self.has_tool(tool_name): + return None return MCPTool(agent=agent, name=tool_name, method=None, args={}, message="", loop_data=None) async def call_tool( diff --git a/helpers/mcp_handler.py.dox.md b/helpers/mcp_handler.py.dox.md index 1cab10612..a07ddd776 100644 --- a/helpers/mcp_handler.py.dox.md +++ b/helpers/mcp_handler.py.dox.md @@ -82,6 +82,7 @@ - Project-scoped MCP servers overlay global servers by normalized name. The resulting `MCPConfig` cache key is derived from both config strings so project instances refresh when either scope changes. - Server status and detail responses include `scope`, and MCP tools resolve through `MCPConfig.get_for_agent(agent)` before execution. - MCP tool names are qualified as `server_name.tool_name`; server names are normalized without dots, and the tool portion may contain dots. +- `MCPConfig.get_tool()` tries the supplied qualified name first, then restores an advertised Responses alias from the calling agent's name map; names that still do not identify an MCP tool return `None` unchanged for downstream local-tool resolution. - Servers may define `disabled_tools` as a list of MCP tool names. Disabled tools are omitted from agent-facing prompts, status counts, `has_tool`, and calls, while detail views can still retrieve them through `get_all_tools()` with a `disabled` flag so users can re-enable them. - Server-specific `init_timeout` and `tool_timeout` override global MCP client timeout settings for list-tools and call-tool operations. - Local stdio server configs accept either strict MCP JSON (`command: "uvx", args: [...]`) or manager-style command lines (`command: "uvx package"`) and normalize them before spawning the process. diff --git a/tests/test_mcp_handler_multimodal.py b/tests/test_mcp_handler_multimodal.py index fd402dcc7..189ec4130 100644 --- a/tests/test_mcp_handler_multimodal.py +++ b/tests/test_mcp_handler_multimodal.py @@ -212,6 +212,41 @@ def test_mcp_config_preserves_dotted_tool_names(mcp_handler_module): assert called == [("alpha.beta", {"value": 7})] +def test_mcp_config_resolves_advertised_responses_alias( + mcp_handler_module, monkeypatch +): + module, _tmp_path = mcp_handler_module + canonical_name = "google_workspace.search_gmail_messages" + native_name = "google_workspace_search_gmail_messages_ecb900b9" + + class _FakeServer: + name = "google_workspace" + + def has_tool(self, tool_name): + return tool_name == "search_gmail_messages" + + config = module.MCPConfig(servers_list=[]) + config.servers = [_FakeServer()] + monkeypatch.setattr( + module.MCPConfig, + "get_for_agent", + classmethod(lambda cls, _agent: config), + ) + + agent = SimpleNamespace( + DATA_NAME_RESPONSES_TOOL_NAME_MAP="responses_tool_name_map", + get_data=lambda key: ( + {native_name: canonical_name} + if key == "responses_tool_name_map" + else None + ), + ) + + assert config.get_tool(agent, canonical_name).name == canonical_name + assert config.get_tool(agent, native_name).name == canonical_name + assert config.get_tool(agent, "local_tool") is None + + def test_mcp_config_call_tool_releases_config_lock_before_await( mcp_handler_module, monkeypatch ):