Repository navigation
Malformed requests get -32602 where JSON-RPC 2.0 requires -32600 (non-Request body) and -32601 (unknown method) #3557
Description
Activity
- addedv2Affects the v2 line (2.x on main)Affects the v2 line (2.x on main)v1Affects the v1.x maintenance lineAffects the v1.x maintenance line
on Sep 21, 2026 qinpei-dev commented
on Sep 22, 2026 More actionsHi, I'd like to work on this.
I reproduced both cases on current main:
- valid JSON that is not a JSON-RPC request returns
-32602instead of-32600 - an unknown method returns
-32602instead of-32601
I plan to keep the change focused, preserve
-32602for known methods with invalid params, and add regression tests for both error-code paths.Could you assign this issue to me?
- valid JSON that is not a JSON-RPC request returns
Real-world impact data point for case 2, from a 2026-07-28-era client talking to a
server built on this SDK. I searched first: #1561 (closed 2026-06-11), #3193 (closed
2026-07-29) and PR #3210 (closed 2026-08-17 under the repo'smissing-issue-linkrule —
the linked issue has to be assigned to the PR author) all cover this same code path, and it
is still present in mcp 1.29.0.Environment
- Server: OpenViking 0.4.15, Streamable HTTP
POST /mcp,serverInfo.version = "1.29.0". - Client:
@modelcontextprotocol/client2.0.0, constructed exactly like
new Client({name, version}, {capabilities: {}, versionNegotiation: {mode: "auto"}}). - The client reaches the server over stdio through a small stdio→HTTP bridge (the
topology our harness uses).
The error code
POST /mcp {"jsonrpc":"2.0","id":1,"method":"server/discover","params":{}} → HTTP 200, text/event-stream → {"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Invalid request parameters","data":""}} POST /mcp {"jsonrpc":"2.0","id":2,"method":"totally/unknown","params":{}} → the same -32602Same code for both, so this is the generic unknown-method path, not a
server/discover
special case.error.datais an empty string, so neither the client nor the operator gets
any diagnostic — #3193 raised that as well. The server log line is the same one #3193
quoted (logging.warning), once per attempt:WARNING Failed to validate request PingRequest.method Input should be 'ping' input_value='server/discover' session.py:383 # 1.29.0; the issue text cites 1.28.1:390Measured client consequence
With the connection above, the client does not take the legacy path on the first
response; counting theserver/discoverlines the server logs around a single connect:- current server (
-32602): 17 probes sent for one connect, then it falls back - same client, same options, but the probe answered locally with
-32601 Method not found:
0 probes sent
Both variants end in a successful connection — era negotiated
legacyandtools/list
returns 15 tools — so this is a compatibility/noise defect rather than a hard failure:
17 extra round trips and 17 misleadingWARNINGlines per connect. That is probably why it
went unreported for so long.Scope note (measured, so nobody wastes time)
The probe storm is transport-dependent: over
StreamableHTTPClientTransportpointed
directly athttp://127.0.0.1:1933/mcp, with the same client and the same
versionNegotiation: {mode: "auto"}, we measured 0 probes and a successful connect.
The stdio path is the affected one in our setup. (For an HTTP-side data point,
volcengine/OpenViking#4830 shows a ChatGPT Secure MCP Tunnel client sending
server/discovertoo.) Once an era is negotiated,discover()is refused locally by the
client (METHOD_NOT_SUPPORTED_BY_PROTOCOL_VERSION), so this is negotiation-phase only.Why this error code matters more than it looks
On the same server,
initializewithprotocolVersion: "2026-07-28"down-negotiates
correctly to2025-11-25— so the only thing between a modern client and a clean one-shot
handshake is this code. A client on the 2026-07-28 era reads-32602as "you called it
wrong" and, since a probe has no parameters to fix, keeps re-probing instead of switching
to the legacy handshake. Clients that pin the era
(versionNegotiationaccepts'legacy' | 'auto' | {pin: string}in the TypeScript SDK),
use tighter timeouts, or surface the last negotiation error will fail outright rather than
retry.State of the fix
@qinpei-dev offered to work on this on 2026-09-22 and asked to be assigned; the issue still
has no assignee and no maintainer reply. PR #3210 implemented exactly this (unknown method →
-32601,-32602preserved for known methods with bad params) and was auto-closed for the
missing issue assignment, so the fix never landed.Happy to share the exact client script, the counting command and the local
-32601shim if
that helps move it forward.- Server: OpenViking 0.4.15, Streamable HTTP
Two malformed-request shapes are answered with
-32602 INVALID_PARAMSwhere JSON-RPC 2.0 requires a different code. Both are still present onmain(checked against the 2.2.0 wheel).1. A body that is valid JSON but not a JSON-RPC message →
-32600, not-32602mcp/server/streamable_http.py(1.28.1: lines 501-505; 2.2.0: 591-593) catches theJSONRPCMessage.model_validateValidationErrorand passesINVALID_PARAMSto_create_error_response:Request:
POST /mcpwith{"hello": "world"}→{"code": -32602, "message": "Validation error: 11 validation errors for JSONRPCMessage…"}. There are no params to be invalid, so-32600 INVALID_REQUESTis the correct code (-32700would suit a body that does not parse as JSON at all, which this path already handles separately).2. An unknown method →
-32601, not-32602mcp/shared/session.py(1.28.1: line 390) catches any exception fromself._receive_request_type.model_validate(...)in_receive_loopand answers:An unknown
methodfails theClientRequestunion exactly like a bad-params request does, so it is reported as invalid params and never reachesServer._handle_request, where theelsebranch already returnsMETHOD_NOT_FOUND(server/lowlevel/server.py, ~line 801). Request:{"jsonrpc":"2.0","id":7,"method":"no/such/method","params":{}}→-32602; JSON-RPC 2.0 requires-32601(and the existing-32602for a known method with bad params must be preserved).Why it matters
A client that mis-types a method is told its params are wrong, which is not the failure it has. Discovery makes it worse:
/.well-known/oauth-authorization-serveradvertisesauthorization_endpoint, and spec-conformance suites that pin the JSON-RPC codes go red against a server that is otherwise healthy.What we did locally
INVALID_REQUESTfor case 1 (matching the SDK's own"Validation error:"prefix) and a read-stream filter for case 2 that answers-32601for a method not intypes.ClientRequestType, deriving the known set from the union. Both are monkeypatches because we did not want to fork; both would be unnecessary if the two call sites used the codes above.Server._handle_request'sMETHOD_NOT_FOUNDbranch suggests case 2 is unintentional.