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.
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:
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.