You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Malformed requests get -32602 where JSON-RPC 2.0 requires -32600 (non-Request body) and -32601 (unknown method) #3557
Two malformed-request shapes are answered with -32602 INVALID_PARAMS where JSON-RPC 2.0 requires a different code. Both are still present on main (checked against the 2.2.0 wheel).
1. A body that is valid JSON but not a JSON-RPC message → -32600, not -32602
mcp/server/streamable_http.py (1.28.1: lines 501-505; 2.2.0: 591-593) catches the JSONRPCMessage.model_validateValidationError and passes INVALID_PARAMS to _create_error_response:
exceptValidationErrorase: # pragma: no coverresponse=self._create_error_response(
f"Validation error: {str(e)}",
HTTPStatus.BAD_REQUEST,
INVALID_PARAMS, # <- a non-Request is INVALID_REQUEST, not invalid params
)
Request: POST /mcp with {"hello": "world"} → {"code": -32602, "message": "Validation error: 11 validation errors for JSONRPCMessage…"}. There are no params to be invalid, so -32600 INVALID_REQUEST is the correct code (-32700 would suit a body that does not parse as JSON at all, which this path already handles separately).
2. An unknown method → -32601, not -32602
mcp/shared/session.py (1.28.1: line 390) catches any exception from self._receive_request_type.model_validate(...) in _receive_loop and answers:
An unknown method fails the ClientRequest union exactly like a bad-params request does, so it is reported as invalid params and never reaches Server._handle_request, where the else branch already returns METHOD_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 -32602 for 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-server advertises authorization_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_REQUEST for case 1 (matching the SDK's own "Validation error:" prefix) and a read-stream filter for case 2 that answers -32601 for a method not in types.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's METHOD_NOT_FOUND branch suggests case 2 is unintentional.
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's missing-issue-link rule —
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/client 2.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 -32602
Same code for both, so this is the generic unknown-method path, not a server/discover
special case. error.data is 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:390
Measured client consequence
With the connection above, the client does not take the legacy path on the first
response; counting the server/discover lines 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 legacy and tools/list
returns 15 tools — so this is a compatibility/noise defect rather than a hard failure:
17 extra round trips and 17 misleading WARNING lines 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 StreamableHTTPClientTransport pointed
directly at http://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/discover too.) 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, initialize with protocolVersion: "2026-07-28" down-negotiates
correctly to 2025-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 -32602 as "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
(versionNegotiation accepts '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, -32602 preserved 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 -32601 shim if
that helps move it forward.
Activity
qinpei-dev commentedon Sep 22, 2026
Hi, I'd like to work on this.
I reproduced both cases on current main:
-32602instead of-32600-32602instead of-32601I 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?
XHR666 commentedon Oct 9, 2026
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's
missing-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
POST /mcp,serverInfo.version = "1.29.0".@modelcontextprotocol/client2.0.0, constructed exactly likenew Client({name, version}, {capabilities: {}, versionNegotiation: {mode: "auto"}}).topology our harness uses).
The error code
Same code for both, so this is the generic unknown-method path, not a
server/discoverspecial case.
error.datais an empty string, so neither the client nor the operator getsany diagnostic — #3193 raised that as well. The server log line is the same one #3193
quoted (
logging.warning), once per attempt:Measured client consequence
With the connection above, the client does not take the legacy path on the first
response; counting the
server/discoverlines the server logs around a single connect:-32602): 17 probes sent for one connect, then it falls back-32601 Method not found:0 probes sent
Both variants end in a successful connection — era negotiated
legacyandtools/listreturns 15 tools — so this is a compatibility/noise defect rather than a hard failure:
17 extra round trips and 17 misleading
WARNINGlines per connect. That is probably why itwent unreported for so long.
Scope note (measured, so nobody wastes time)
The probe storm is transport-dependent: over
StreamableHTTPClientTransportpointeddirectly at
http://127.0.0.1:1933/mcp, with the same client and the sameversionNegotiation: {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 theclient (
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-negotiatescorrectly to
2025-11-25— so the only thing between a modern client and a clean one-shothandshake is this code. A client on the 2026-07-28 era reads
-32602as "you called itwrong" 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 themissing issue assignment, so the fix never landed.
Happy to share the exact client script, the counting command and the local
-32601shim ifthat helps move it forward.