Skip to content

Production MCP mode-aware tools are not exposed or callable via JSON-RPC (/mcp and WebSocket) #1212

Description

@mugenkyou

Summary

Tools registered with @self.tool(mode="production") on an MCPEnvironment are not reachable through the production MCP transport served by HTTPEnvServer. With register_routes(app, mode="production"):

  • tools/list omits production-mode tools
  • tools/call fails with Unknown tool
  • MCPToolClient cannot list or call them

This affects both the HTTP /mcp JSON-RPC path and the WebSocket MCP path.

Reproduction

class DualModeEnv(MCPEnvironment):
    SUPPORTS_CONCURRENT_SESSIONS = True

    def __init__(self):
        mcp = FastMCP("demo")
        super().__init__(mcp)

        @mcp.tool
        def shared_tool(x: int) -> int:
            return x * 10

        @self.tool(mode="production")
        def production_tool(msg: str) -> str:
            return f"PROD_RESULT: {msg}"

        @self.tool(mode="simulation")
        def simulation_tool(msg: str) -> str:
            return f"SIM_RESULT: {msg}"

server = HTTPEnvServer(
    env=DualModeEnv,
    action_cls=CallToolAction,
    observation_cls=CallToolObservation,
)
server.register_routes(app, mode="production")

Using FastAPI TestClient, create a session via POST /mcp, then:

tools/list returns only shared_tool; production_tool is missing.

tools/call:

{"jsonrpc": "2.0", "method": "tools/call", "id": 103,
 "params": {"name": "production_tool", "arguments": {"msg": "hello-prod"}}}

returns:

{"jsonrpc": "2.0", "id": 103,
 "error": {"code": -32603, "message": "Unknown tool: 'production_tool'", "data": null}}

MCPToolClient:

list_tools() -> ['shared_tool']
call_tool('production_tool', ...) -> RuntimeError: Tool 'production_tool' failed: Unknown tool

The WebSocket MCP path behaves the same. The failure is deterministic and reproduces on main at 7e591317fa85dab4991b6869ac33c0d303b645df.

Root cause

There are two related problems.

1. mcp_handler bypasses the mode-aware registry.
MCPEnvironment.tool(mode=...) stores mode-specific tools in _mode_tools / _mode_tool_schemas, not on the underlying FastMCP server. HTTPEnvServer.mcp_handler handles tools/list and tools/call by delegating to mcp_client.list_tools() and mcp_client.call_tool(...), which only see the FastMCP registry. Mode-aware tools are never consulted.

2. The server mode is not propagated to environment instances.
register_routes(app, mode="production") receives the mode, but _create_session() does not apply it to new environments, so getattr(env, "_mode", None) is None instead of "production". The existing mode-aware resolution therefore cannot pick the right implementation.

Relevant code:

  • src/openenv/core/env_server/mcp_environment.py (mode-aware registration, _mode_tools, _mode_tool_schemas, tool resolution)
  • src/openenv/core/env_server/http_server.py (HTTPEnvServer.mcp_handler, register_routes, _create_session)

Expected behavior

With register_routes(app, mode="production"):

  • tools/list returns mode-agnostic tools plus production-mode tools, without duplicates
  • tools/call dispatches production-mode tools to their production implementation
  • simulation-only tools do not appear in the production tool list
  • the same holds over HTTP /mcp, WebSocket MCP, and MCPToolClient

Suggested fix direction

  • Pass the server mode into environments created by _create_session() so env._mode is set correctly.
  • In mcp_handler, build tools/list from the FastMCP tools plus the mode-appropriate entries of _mode_tool_schemas, deduplicated by name.
  • In mcp_handler, resolve tools/call through the environment's existing mode-aware dispatch when the tool is in the mode registry.
  • Reuse the existing MCPEnvironment mode-aware logic instead of adding a second registration mechanism.

No change to the public @self.tool(mode=...) API is needed.

Test coverage gap

Current mode-aware tests call step() directly after manually setting the environment mode. Current production MCP tests mostly cover mode-agnostic FastMCP tools. Nothing exercises the full path:

HTTP /mcp -> JSON-RPC -> HTTPEnvServer.mcp_handler -> MCPEnvironment -> mode-aware registry

Proposed regression tests

Using a real TestClient against /mcp with mode="production":

  1. Create an MCP session.
  2. Call tools/list and assert that both the shared tool and the production tool are present, and the simulation tool is absent.
  3. Call tools/call on the production tool and assert it returns the production result.

Add the same scenario through MCPToolClient (and ideally WebSocket MCP) to cover the client-to-server path.

Scope

Limited to the interaction between mode-aware MCP tools, HTTPEnvServer, and the production MCP transport.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions