Problem statement / motivation
The gateway currently supports read_mcp_resource, which bridges the MCP resources/read operation. Although McpHandlerKind::ToolCall and McpClient::call_tool exist, tools exposed by an MCP server cannot currently complete the tools/list -> registry -> tools/call flow.
This issue covers request-declared HTTP MCP servers connected through McpClient::connect. Gateway-managed stdio server startup is tracked separately.
Current behavior
register_declared_tool_call in tool/mcp/registry.rs stores a serialized McpToolParam as the ToolEntry configuration.
Both normalization and execution of McpHandlerKind::ToolCall, however, deserialize that configuration as McpDiscoveredToolParam, which contains:
server_label
tool_name
exposed_name
- the discovered
rmcp::model::Tool
The two configuration shapes are incompatible. A declared MCP tool can therefore reach dispatch but fails with an error similar to:
McpHandler::discovered_tool_handlers already performs the missing tools/list operation and produces a McpDiscoveredToolParam plus a ToolCall handler for every discovered server tool. That method is currently not wired into registry construction and is unnecessarily coupled to the ReadResource handler variant.
Proposed solution
Wire MCP server-tool discovery into ToolRegistry::build_with_handlers:
- Build an MCP client for each request-declared server using
McpClientPool::from_params and McpClient::connect.
- Call
McpClient::list_tools after the MCP initialization handshake.
- For each discovered tool:
- expose it to the model as
{server_label}__{tool_name};
- create an
McpDiscoveredToolParam containing the original server and tool metadata;
- create an
McpHandlerKind::ToolCall bound to that server's McpClient;
- register a
ToolEntry keyed by exposed_name;
- store the serialized
McpDiscoveredToolParam in ToolEntry.config.
- When the model calls the exposed name, route dispatch to
execute_tool_call, which invokes McpClient::call_tool using the original MCP tool name.
- Move discovery out of the
ReadResource handler variant into a transport-neutral function owned by McpHandlerFactory, McpClientPool, or the MCP registry builder.
- Remove or replace
register_declared_tool_call; an undiscovered McpToolParam is not sufficient configuration for a callable MCP server tool.
- Preserve the existing
read_mcp_resource registration and resources/read behavior.
The ToolCall executor must remain transport-independent. Connection and discovery happen while building the pool and registry; execution only performs call_tool.
Error and collision behavior
Define explicit behavior for:
- a
tools/list failure;
- a connected server returning no tools;
- two discovered tools producing the same exposed name;
- an exposed MCP name colliding with another declared function tool;
- a tool disappearing between discovery and
tools/call.
Name collisions should produce a configuration error rather than silently replacing an existing registry entry.
Alternatives considered
No response
Additional context
Test plan
Automated HTTP round trip
Use a hermetic loopback Streamable HTTP MCP fixture that exposes at least one non-trivial tool, such as search_repo or echo with optional arguments.
Note: the core crate currently enables only rmcp's client-side features. The fixture requires either adding rmcp server features (server, transport-streamable-http-server) as a dev-dependency or hand-rolling a minimal JSON-response HTTP fixture.
Verify:
McpClient::connect completes the MCP handshake.
tools/list discovers the fixture tool.
ToolRegistry contains {server_label}__{tool_name}.
- The normalized tool spec contains the discovered schema.
- Dispatch invokes
tools/call with the original MCP tool name and arguments.
- The result is represented as a completed
mcp_tool_call.
- Invalid arguments or a server-side tool error produce a failed
mcp_tool_call without failing the response.
Regression coverage
- Cover the current
McpToolParam versus McpDiscoveredToolParam mismatch.
- Verify that duplicate exposed names are rejected.
- Run the existing
read_mcp_resource tests unchanged.
Manual verification
Optionally run a standard MCP fixture server in Streamable HTTP mode on localhost to exercise the loopback allowlist and the same discovery/dispatch path.
Out of scope
- Accepting stdio
command, args, env, or cwd from a Responses API request.
- Gateway startup configuration for managed stdio MCP servers.
- MCP approval modes beyond the behavior already supported by the gateway.
allowed_tools filtering on the mcp tool declaration; every discovered tool is registered.
- Emitting the
mcp_list_tools output item after discovery (OpenAI Responses parity); follow-up work.
Problem statement / motivation
The gateway currently supports
read_mcp_resource, which bridges the MCPresources/readoperation. AlthoughMcpHandlerKind::ToolCallandMcpClient::call_toolexist, tools exposed by an MCP server cannot currently complete thetools/list-> registry ->tools/callflow.This issue covers request-declared HTTP MCP servers connected through
McpClient::connect. Gateway-managed stdio server startup is tracked separately.Current behavior
register_declared_tool_callintool/mcp/registry.rsstores a serializedMcpToolParamas theToolEntryconfiguration.Both normalization and execution of
McpHandlerKind::ToolCall, however, deserialize that configuration asMcpDiscoveredToolParam, which contains:server_labeltool_nameexposed_namermcp::model::ToolThe two configuration shapes are incompatible. A declared MCP tool can therefore reach dispatch but fails with an error similar to:
McpHandler::discovered_tool_handlersalready performs the missingtools/listoperation and produces aMcpDiscoveredToolParamplus aToolCallhandler for every discovered server tool. That method is currently not wired into registry construction and is unnecessarily coupled to theReadResourcehandler variant.Proposed solution
Wire MCP server-tool discovery into
ToolRegistry::build_with_handlers:McpClientPool::from_paramsandMcpClient::connect.McpClient::list_toolsafter the MCP initialization handshake.{server_label}__{tool_name};McpDiscoveredToolParamcontaining the original server and tool metadata;McpHandlerKind::ToolCallbound to that server'sMcpClient;ToolEntrykeyed byexposed_name;McpDiscoveredToolParaminToolEntry.config.execute_tool_call, which invokesMcpClient::call_toolusing the original MCP tool name.ReadResourcehandler variant into a transport-neutral function owned byMcpHandlerFactory,McpClientPool, or the MCP registry builder.register_declared_tool_call; an undiscoveredMcpToolParamis not sufficient configuration for a callable MCP server tool.read_mcp_resourceregistration andresources/readbehavior.The
ToolCallexecutor must remain transport-independent. Connection and discovery happen while building the pool and registry; execution only performscall_tool.Error and collision behavior
Define explicit behavior for:
tools/listfailure;tools/call.Name collisions should produce a configuration error rather than silently replacing an existing registry entry.
Alternatives considered
No response
Additional context
Test plan
Automated HTTP round trip
Use a hermetic loopback Streamable HTTP MCP fixture that exposes at least one non-trivial tool, such as
search_repoorechowith optional arguments.Note: the core crate currently enables only rmcp's client-side features. The fixture requires either adding rmcp server features (
server,transport-streamable-http-server) as a dev-dependency or hand-rolling a minimal JSON-response HTTP fixture.Verify:
McpClient::connectcompletes the MCP handshake.tools/listdiscovers the fixture tool.ToolRegistrycontains{server_label}__{tool_name}.tools/callwith the original MCP tool name and arguments.mcp_tool_call.mcp_tool_callwithout failing the response.Regression coverage
McpToolParamversusMcpDiscoveredToolParammismatch.read_mcp_resourcetests unchanged.Manual verification
Optionally run a standard MCP fixture server in Streamable HTTP mode on localhost to exercise the loopback allowlist and the same discovery/dispatch path.
Out of scope
command,args,env, orcwdfrom a Responses API request.allowed_toolsfiltering on themcptool declaration; every discovered tool is registered.mcp_list_toolsoutput item after discovery (OpenAI Responses parity); follow-up work.