Repository navigation
Open
Labels
P2Moderate issues affecting some users, edge cases, potentially valuable featureModerate issues affecting some users, edge cases, potentially valuable featurebugSomething isn't workingSomething isn't workingimproves spec complianceWhen a change improves ability of SDK users to comply with spec definitionWhen a change improves ability of SDK users to comply with spec definition
Description
Initial Checks
- I confirm that I'm using the latest version of MCP Python SDK
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Description
When a JSON-RPC request arrives with "id": null, the SDK should reject it. Both JSON-RPC 2.0 and the MCP spec restrict request IDs to strings or integers. Instead, the request is silently reclassified as a JSONRPCNotification and the caller gets a 202 with no response.
This happens because of how JSONRPCMessage union resolution interacts with extra='allow':
RequestIdcorrectly excludesNone(Annotated[int, Field(strict=True)] | str).JSONRPCRequestvalidation rejectsid: null, working as intended.- Pydantic falls through to
JSONRPCNotification, which absorbs"id": Noneas an extra field viaextra='allow'. - The streamable HTTP transport sees "not a request" and returns 202.
The net effect is the caller gets no error and no response, which is hard to debug. Found via authprobe scanning.
I suspect the v2 migration to TypeAdapter and dropping extra='allow' on top-level types would resolve this, but wanted to flag it for the current release line too.
Example Code
from mcp.types import JSONRPCMessage, JSONRPCRequest
msg = {"jsonrpc": "2.0", "method": "initialize", "id": None}
# JSONRPCRequest correctly rejects null id
try:
JSONRPCRequest.model_validate(msg)
except Exception:
print("JSONRPCRequest rejects null id") # Expected
# JSONRPCMessage falls through to JSONRPCNotification
parsed = JSONRPCMessage.model_validate(msg)
print(type(parsed.root).__name__) # JSONRPCNotification (unexpected)
print(parsed.root.model_extra) # {'id': None}Python & MCP Python SDK
Python 3.13
mcp 1.14.1 (also reproduced on 1.26.0, latest at time of filing)
Reacted by Chintan Thakker
Metadata
Metadata
Assignees
Labels
P2Moderate issues affecting some users, edge cases, potentially valuable featureModerate issues affecting some users, edge cases, potentially valuable featurebugSomething isn't workingSomething isn't workingimproves spec complianceWhen a change improves ability of SDK users to comply with spec definitionWhen a change improves ability of SDK users to comply with spec definition
Activity
clgtm commentedon Feb 14, 2026
Adding scan details to the bug filed by @clouatre to provide context -
Command: authprobe scan --explain --trace-failure https://math-mcp.fastmcp.app/mcp
Scanning: https://math-mcp.fastmcp.app/mcp
Scan time: Feb 12, 2026 06:08:57 UTC
Github: https://github.com/authprobe/authprobe
┌────────────────────┤ LLM EXPLANATION ├─────────────────────┐
The primary finding in the AuthProbe scan is MCP_JSONRPC_ID_NULL_ACCEPTED, assessed as high confidence and severity with evidence that the server accepted a JSON-RPC request containing a
nullID and returned a202status code instead of rejecting it.Explanation of the Failure
MCP and JSON-RPC 2.0 Specification Requirements
MCP 2025-11-25 specifies that MCP servers use a JSON-RPC 2.0-like messaging layer for requests such as
initialize,tools/list, etc. This layer requires strict adherence to JSON-RPC structural rules to ensure correct interoperability.JSON-RPC 2.0 (RFC 7945) (informative reference since MCP is based on JSON-RPC 2.0 principles) states:
However, MCP 2025-11-25 overrides this aspect by explicitly restricting request IDs to strings or numbers only and mandates rejecting requests with
nullIDs:This stricter requirement intends to eliminate ambiguity in request-response correlation and maintain robust state tracking, as MCP messages are often stateful and session-bound.
Server Behavior Observed
The probe sent a JSON-RPC 2.0 request with
"id": null.The server responded with status code
202, indicating acceptance and asynchronous processing, rather than rejecting the request as malformed or invalid.This means the server violates the MCP specification by accepting a disallowed request ID type.
Why This Failure Is Valid and Justified
The failure is a correct and justified finding because the server behavior contradicts the explicit MCP mandate that
idmust never benull.Accepting a
nullID leads to semantic confusion in correlating requests and responses, especially in asynchronous or multiplexed environments typical in MCP.By returning
202(Accepted) instead of an error (such as400 Bad Requestor a JSON-RPCErrorresponse with code-32600"Invalid Request"), the server fails to perform necessary input validation.Relevant Spec Sections and Correct Server Behavior
idmust be string or number;nullIDs must be rejected explicitly with error.id; MCP limits acceptableidtypes further to excludenull.Expected Correct Server Behavior
idfield is eitherclgtm commentedon Feb 14, 2026
@clouatre the url for authprobe is inaccurate in the comment, the correct one is https://github.com/authprobe/authprobe
Reject JSON-RPC requests with null id instead of misclassifying as no…
BabyChrist666 commentedon Feb 17, 2026
Opened a PR to fix this: #2075
The fix adds a
model_validator(mode="before")onJSONRPCNotificationthat rejects any input containing an"id"field. Per JSON-RPC 2.0, a notification is a request without anidmember — if"id"is present (regardless of value), it's a request, not a notification. This prevents Pydantic's union resolution from silently absorbing"id": nullas an extra field on the Notification type.Includes 12 test cases covering null/int/str id rejection, both
validate_pythonandvalidate_jsonpaths, and verification that valid requests and notifications still parse correctly.refactor: address review feedback on docstrings and test structure
Reject JSON-RPC requests with null id instead of misclassifying as no…
refactor: address review feedback on docstrings and test structure
clgtm commentedon Feb 24, 2026
@hshahrokni2 if you found
authprobehelpful, please star / share , much appreciated!maxisbey commentedon Feb 25, 2026
@clgtm unfortunately I'm pretty sure @hshahrokni2 was banned from GitHub for spamming AI generated comments like the above on a bunch of repos. All their comments/PRs I can see on google are now deleted as is their profile.
clgtm commentedon Feb 25, 2026
@maxisbey real non-AI non-Bot person here :-) if you found authprobe helpful, please star / share , much appreciated!
shivama205 commentedon Mar 8, 2026
I'll take this. The root cause is in Pydantic's union fallthrough — when
JSONRPCRequestrejects an invalidid(e.g.null), the message falls through toJSONRPCNotificationwhich silently absorbs the extraidfield. The fix is to add amodel_validatoronJSONRPCNotificationthat rejects any input containing anidfield, since notifications must not have one per JSON-RPC 2.0. Will have a PR up shortly.fix: reject JSON-RPC requests with null id instead of misclassifying …
shivama205 commentedon Mar 8, 2026
PR is up: #2251
BabyChrist666 commentedon Mar 9, 2026
Hey @shivama205 — just a heads up, I opened PR #2075 for this same issue back on Feb 17 with the same approach (model_validator on JSONRPCNotification to reject any input containing an
idfield). Would love to avoid duplicating effort! 😊Reject JSON-RPC requests with null id instead of misclassifying as no…
refactor: address review feedback on docstrings and test structure
shivama205 commentedon Mar 9, 2026
Hey @BabyChrist666, thanks for the heads up! I hadn't seen your PR when I opened mine. Happy to close this in favor of #2075 — let me know if there's anything from my PR that would be useful to pull in.
talhayme commentedon Oct 12, 2026
This still reproduces on
mcp==2.3.0(and1.30.0), and it is wider thannull. Decoding{"jsonrpc":"2.0","id":<id>,"method":"tools/list"}throughJSONRPCMessage:id42,"abc"null1.0,1e21.5true,[1]So any
idthat is not an int or a string falls through toJSONRPCNotification, not justnull. I checked message decoding only, not a live server.For comparison, on the same inputs the TypeScript SDK reads
1.0and1e2as requests with ids1and100and rejects the rest, and the Rust SDK had the same fall-through as here (modelcontextprotocol/rust-sdk#1345).AI disclosure: found and drafted with Claude Code; I reviewed it and ran the cases above.