Skip to content

fix(mcp): return JSON-RPC -32601 for unknown methods - #16

Open
jonathanhhb wants to merge 1 commit into
mainfrom
fix/jsonrpc-method-not-found
Open

jonathanhhb wants to merge 1 commit into
mainfrom
fix/jsonrpc-method-not-found

Conversation

@jonathanhhb

@jonathanhhb jonathanhhb commented May 12, 2026 •

Copy link
Copy Markdown
Collaborator

This change is motivated entirely by a quest to get JENNER-Measles-MCP to connect happily from the Claude Managed Agent. We have no idea if this is the issue. Seems like a bit of a stretch to me since it's patching fastmcp bugs and I would have thought fastmcp was compliant enough for most folks.

Not in any rush to merge this but here just in case.

JSON-RPC 2.0 §5.1 requires error code -32601 (Method Not Found) for requests whose method name is not recognized. The mcp library validates incoming messages with Pydantic models that use Literal[...] for the method field, so unknown methods fail validation and the streamable_http transport maps them to -32602 (Invalid Params) instead.

Add a thin ASGI middleware (MethodNotFoundMiddleware) on both core and measles servers that buffers the request body, peeks at the method field, and returns a proper -32601 response if the method isn't in the MCP spec. Known methods fall through to the inner app via a replayed receive() so behavior is otherwise unchanged.

JSON-RPC 2.0 §5.1 requires error code -32601 (Method Not Found) for requests
whose method name is not recognized. The mcp library validates incoming
messages with Pydantic models that use Literal[...] for the method field, so
unknown methods fail validation and the streamable_http transport maps them
to -32602 (Invalid Params) instead.

Add a thin ASGI middleware (MethodNotFoundMiddleware) on both core and measles
servers that buffers the request body, peeks at the method field, and returns
a proper -32601 response if the method isn't in the MCP spec. Known methods
fall through to the inner app via a replayed receive() so behavior is
otherwise unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an ASGI middleware layer to the two FastMCP server entrypoints to correctly return JSON-RPC error code -32601 (Method Not Found) when a request’s method is unknown, avoiding the current behavior where unknown methods fail Pydantic Literal[...] validation and get mapped to -32602 (Invalid Params).

Changes:

  • Add MethodNotFoundMiddleware that buffers the request body, parses JSON, and short-circuits unknown methods to a JSON-RPC -32601 error response.
  • Register the middleware via mcp.run(..., middleware=[Middleware(MethodNotFoundMiddleware)]) in both servers.
  • Introduce a hard-coded _KNOWN_MCP_METHODS set to decide whether a method is recognized.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
main.py Adds MethodNotFoundMiddleware + known-method allowlist and wires it into mcp.run() for the core server.
main_measles.py Adds the same middleware (with fuller docs) and wires it into mcp.run() for the measles server.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread main.py
method = data.get("method") if isinstance(data, dict) else None
has_id = isinstance(data, dict) and "id" in data

if method and method not in _KNOWN_MCP_METHODS and has_id:
Comment thread main.py
Comment on lines +122 to +128
chunks = []
more = True
while more:
msg = await receive()
chunks.append(msg.get("body", b""))
more = msg.get("more_body", False)
body = b"".join(chunks)
Comment thread main.py
Comment on lines +91 to +110
# MethodNotFoundMiddleware: shared with main_measles.py — see that file for docs.
_KNOWN_MCP_METHODS = frozenset({
"initialize", "ping",
"resources/list", "resources/templates/list", "resources/read",
"resources/subscribe", "resources/unsubscribe",
"prompts/list", "prompts/get",
"tools/list", "tools/call",
"logging/setLevel",
"sampling/createMessage",
"completion/complete",
"roots/list",
"tasks/get", "tasks/result", "tasks/cancel", "tasks/list",
"elicitation/create",
"notifications/initialized", "notifications/progress",
"notifications/message",
"notifications/resources/list_changed", "notifications/resources/updated",
"notifications/prompts/list_changed", "notifications/tools/list_changed",
"notifications/roots/list_changed", "notifications/cancelled",
"notifications/elicitation/complete", "notifications/tasks/status",
})
Comment thread main_measles.py
method = data.get("method") if isinstance(data, dict) else None
has_id = isinstance(data, dict) and "id" in data

if method and method not in _KNOWN_MCP_METHODS and has_id:
Comment thread main_measles.py
Comment on lines +265 to +272
# Buffer the full body — ASGI receive() is single-use.
chunks = []
more = True
while more:
msg = await receive()
chunks.append(msg.get("body", b""))
more = msg.get("more_body", False)
body = b"".join(chunks)
Comment thread main_measles.py
Comment on lines +222 to +243
# Every method name the MCP spec defines (derived from mcp.types Literal fields).
# Unknown methods must return -32601 (Method Not Found) per JSON-RPC 2.0.
# The mcp library's Pydantic validation incorrectly maps them to -32602 instead.
_KNOWN_MCP_METHODS = frozenset({
"initialize", "ping",
"resources/list", "resources/templates/list", "resources/read",
"resources/subscribe", "resources/unsubscribe",
"prompts/list", "prompts/get",
"tools/list", "tools/call",
"logging/setLevel",
"sampling/createMessage",
"completion/complete",
"roots/list",
"tasks/get", "tasks/result", "tasks/cancel", "tasks/list",
"elicitation/create",
"notifications/initialized", "notifications/progress",
"notifications/message",
"notifications/resources/list_changed", "notifications/resources/updated",
"notifications/prompts/list_changed", "notifications/tools/list_changed",
"notifications/roots/list_changed", "notifications/cancelled",
"notifications/elicitation/complete", "notifications/tasks/status",
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants