Fix MCP lookup for Responses tool aliases
Some checks are pending
Build And Publish Docker Images / plan (push) Waiting to run
Build And Publish Docker Images / build (push) Blocked by required conditions

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.
This commit is contained in:
Alessandro 2026-07-16 02:29:39 +02:00
parent e2fd320ac6
commit 6a7178af91
3 changed files with 47 additions and 1 deletions

View file

@ -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(

View file

@ -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.

View file

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