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.
Alessandro committed
Jul 16, 2026 at 02:29 UTC
6a7178af91c6d8f2114e359dcd4e368fb803b568
3 files changed
+47
-1
helpers/mcp_handler.py
+11
-1
@@ -45,6 +45,7 @@ from helpers import dirty_json, media_artifacts
45
from helpers.print_style import PrintStyle
46
from helpers.tool import Tool, Response
47
from helpers.defer import DeferredTask
48
+from helpers.responses_tools import original_tool_name
49
50
51
MCP_MEDIA_TOKENS_ESTIMATE = 1500
@@ -1211,7 +1212,16 @@ class MCPConfig(BaseModel):
1212
if effective_config is not self:
1213
return effective_config.get_tool(agent, tool_name)
1214
if not self.has_tool(tool_name):
1214
- return None
1215
+ get_data = getattr(agent, "get_data", None)
1216
+ name_map_key = getattr(agent, "DATA_NAME_RESPONSES_TOOL_NAME_MAP", "")
1217
+ tool_name = original_tool_name(
1218
+ tool_name,
1219
+ get_data(name_map_key)
1220
+ if name_map_key and callable(get_data)
1221
+ else None,
1222
+ )
1223
+ if not self.has_tool(tool_name):
1224
+ return None
1225
return MCPTool(agent=agent, name=tool_name, method=None, args={}, message="", loop_data=None)
1226
1227
async def call_tool(
helpers/mcp_handler.py.dox.md
+1
@@ -82,6 +82,7 @@
82
- 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.
83
- Server status and detail responses include `scope`, and MCP tools resolve through `MCPConfig.get_for_agent(agent)` before execution.
84
- MCP tool names are qualified as `server_name.tool_name`; server names are normalized without dots, and the tool portion may contain dots.
85
+- `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.
86
- 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.
87
- Server-specific `init_timeout` and `tool_timeout` override global MCP client timeout settings for list-tools and call-tool operations.
88
- 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.
tests/test_mcp_handler_multimodal.py
+35
@@ -212,6 +212,41 @@ def test_mcp_config_preserves_dotted_tool_names(mcp_handler_module):
212
assert called == [("alpha.beta", {"value": 7})]
213
214
215
+def test_mcp_config_resolves_advertised_responses_alias(
216
+ mcp_handler_module, monkeypatch
217
+):
218
+ module, _tmp_path = mcp_handler_module
219
+ canonical_name = "google_workspace.search_gmail_messages"
220
+ native_name = "google_workspace_search_gmail_messages_ecb900b9"
221
+
222
+ class _FakeServer:
223
+ name = "google_workspace"
224
+
225
+ def has_tool(self, tool_name):
226
+ return tool_name == "search_gmail_messages"
227
+
228
+ config = module.MCPConfig(servers_list=[])
229
+ config.servers = [_FakeServer()]
230
+ monkeypatch.setattr(
231
+ module.MCPConfig,
232
+ "get_for_agent",
233
+ classmethod(lambda cls, _agent: config),
234
+ )
235
+
236
+ agent = SimpleNamespace(
237
+ DATA_NAME_RESPONSES_TOOL_NAME_MAP="responses_tool_name_map",
238
+ get_data=lambda key: (
239
+ {native_name: canonical_name}
240
+ if key == "responses_tool_name_map"
241
+ else None
242
+ ),
243
+ )
244
+
245
+ assert config.get_tool(agent, canonical_name).name == canonical_name
246
+ assert config.get_tool(agent, native_name).name == canonical_name
247
+ assert config.get_tool(agent, "local_tool") is None
248
+
249
+
250
def test_mcp_config_call_tool_releases_config_lock_before_await(
251
mcp_handler_module, monkeypatch
252
):