Skip to content

Wire request-declared HTTP MCP tool discovery into ToolRegistry #134

Description

@maralbahari

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:

invalid MCP tool config

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:

  1. Build an MCP client for each request-declared server using McpClientPool::from_params and McpClient::connect.
  2. Call McpClient::list_tools after the MCP initialization handshake.
  3. 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.
  4. When the model calls the exposed name, route dispatch to execute_tool_call, which invokes McpClient::call_tool using the original MCP tool name.
  5. Move discovery out of the ReadResource handler variant into a transport-neutral function owned by McpHandlerFactory, McpClientPool, or the MCP registry builder.
  6. Remove or replace register_declared_tool_call; an undiscovered McpToolParam is not sufficient configuration for a callable MCP server tool.
  7. 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:

  1. McpClient::connect completes the MCP handshake.
  2. tools/list discovers the fixture tool.
  3. ToolRegistry contains {server_label}__{tool_name}.
  4. The normalized tool spec contains the discovered schema.
  5. Dispatch invokes tools/call with the original MCP tool name and arguments.
  6. The result is represented as a completed mcp_tool_call.
  7. 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.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions