Repository navigation
Description
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_validate ValidationError and passes INVALID_PARAMS to _create_error_response:
except ValidationError as e: # pragma: no cover
response = 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:
except Exception as e:
error_response = JSONRPCError(
jsonrpc="2.0",
id=message.message.root.id,
error=ErrorData(code=INVALID_PARAMS, message="Invalid request parameters", data=""),
)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.
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.