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 ):