MCP

Status: Draft; implementation checked against the development source on 2026-09-08.

What it does

Exposes your @route methods as tools an AI agent can call over the Model Context Protocol (MCP), served as JSON-RPC over Streamable HTTP. Because genro-routes is protocol-neutral, the same route tree can answer REST and MCP at once.

When to use it

When you want an AI agent to invoke your application’s operations as tools — either as a pure MCP endpoint or side-by-side with a REST/OpenAPI face.

Setup

Two base classes cover the two shapes:

  • McpApplication — the whole application is a single JSON-RPC MCP endpoint.

    from genro_asgi import McpApplication
    app = McpApplication(routing_class=..., code="mcp")
    
  • McpOpenApiApplication — a dual-faced application: a REST/OpenAPI face and an MCP face served on /mcp. The MCP segment is configurable via mcp_name_segment="mcp".

    from genro_asgi import McpOpenApiApplication
    # subclass of OpenApiApplication: REST + OpenAPI + MCP on /mcp
    

Both are importable from genro_asgi.

Marking a route as a tool

Expose a method as an MCP tool with channel_channels:

from genro_asgi import AsgiServer, McpOpenApiApplication
from genro_routes import route


class Calc(McpOpenApiApplication):
    mount = ""
    openapi_info = {"title": "Calc", "version": "1.0.0"}

    @route(channel_channels="mcp")
    def add(self, a: int = 0, b: int = 0) -> dict:
        return {"result": a + b}

    @route(channel_channels="mcp,rest")
    def mul(self, a: int = 0, b: int = 0) -> dict:
        return {"result": a * b}


if __name__ == "__main__":
    AsgiServer(applications=[Calc()]).serve(host="127.0.0.1", port=8000)

Save this as calc.py, run python calc.py, and stop it with Ctrl-C after the checks below.

  • channel_channels="mcp" includes the method in the MCP tool surface.

  • channel_channels="mcp,rest" declares both channel names for consumers that filter by channel.

HTTP dispatch does not apply this channel filter. Either declaration leaves the route callable directly over HTTP; channel metadata is not an HTTP access restriction. Use authorization rules to restrict callers.

AsgiServer automatically arms the pydantic and openapi plugins used by this example. Explicit entries are needed only to configure their options.

The /mcp endpoint

The MCP face speaks JSON-RPC on /mcp, protocol version 2025-11-25:

  • initialize

  • tools/list

  • tools/call

  • ping

Parameter handling for tools/call is the same as for REST: the arguments bind to the method signature, typed and with defaults.

A GET on /mcp opens an SSE push stream (see streaming). It returns 405 when tasks are explicitly disabled (tasks=False) or the server has no task capability. Tasks are enabled by default on AsgiServer.

How to verify it

List the tools with a JSON-RPC call:

$ curl -X POST http://127.0.0.1:8000/mcp \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Call one:

curl -X POST http://127.0.0.1:8000/mcp \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
         "params":{"name":"add","arguments":{"a":2,"b":3}}}'

Gotchas

  • Only routes carrying channel_channels that includes "mcp" appear as tools. A plain @route() is not offered as an MCP tool. Channel metadata does not prevent HTTP access; authorization rules govern access restrictions.

  • GET /mcp (the SSE push stream) is 405 unless the server has tasks armed — see the tasks guide.

  • The MCP JSON-RPC lives on /mcp (segment configurable via mcp_name_segment), separate from the OpenAPI _meta prefix.

  • McpApplication, McpOpenApiApplication, McpEngine and McpError are importable from genro_asgi.