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
Requests with "id": null silently misclassified as notifications #2057
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':
JSONRPCRequest validation rejects id: null, working as intended.
Pydantic falls through to JSONRPCNotification, which absorbs "id": None as an extra field via extra='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.
┌────────────────────┤ 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 null ID and returned a 202 status 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:
"The id member is used to correlate the context between the two objects. This can be a string, number, or null. If included, it MUST be the same in the Response as in the Request. A request without an id is a notification and does not require a response."
However, MCP 2025-11-25 overrides this aspect by explicitly restricting request IDs to strings or numbers only and mandates rejecting requests with null IDs:
"Request identifiers (id) SHALL be either strings or numbers. Requests containing id: null MUST be rejected with an error response indicating invalid request format." (Section 4.2.1 MCP 2025-11-25)
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 id must never be null.
Accepting a null ID 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 as 400 Bad Request or a JSON-RPC Error response with code -32600 "Invalid Request"), the server fails to perform necessary input validation.
Relevant Spec Sections and Correct Server Behavior
Source
Requirement
MCP 2025-11-25 §4.2.1
Request id must be string or number; null IDs must be rejected explicitly with error.
RFC 9728 §3.6
JSON-RPC parameters and structure in MCP must be strictly validated to avoid malformed requests.
JSON-RPC 2.0
Requests must have valid id; MCP limits acceptable id types further to exclude null.
Expected Correct Server Behavior
Input Validation: On receiving a JSON-RPC request, the MCP server should check that the id field is either
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':
JSONRPCRequest validation rejects id: null, working as intended.
Pydantic falls through to JSONRPCNotification, which absorbs "id": None as an extra field via extra='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
The fix adds a model_validator(mode="before") on JSONRPCNotification that rejects any input containing an "id" field. Per JSON-RPC 2.0, a notification is a request without an id member — if "id" is present (regardless of value), it's a request, not a notification. This prevents Pydantic's union resolution from silently absorbing "id": null as an extra field on the Notification type.
Includes 12 test cases covering null/int/str id rejection, both validate_python and validate_json paths, and verification that valid requests and notifications still parse correctly.
The silent 202 with no response is particularly nasty from a client perspective -- callers get no indication their request was absorbed. The Pydantic union fallthrough via extra='allow' is exactly the kind of subtle validation gap that shows up in security scans like authprobe.
Good catch, and glad to see PR #2075 already in flight. Explicit rejection of any message containing an 'id' field in the notification validator is the right fix -- null is not a valid JSON-RPC request ID per the spec and should never silently degrade.
@hshahrokni2 if you found authprobe helpful, please star / share , much appreciated!
@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 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.
@maxisbey real non-AI non-Bot person here :-) if you found authprobe helpful, please star / share , much appreciated!
I'll take this. The root cause is in Pydantic's union fallthrough — when JSONRPCRequest rejects an invalid id (e.g. null), the message falls through to JSONRPCNotification which silently absorbs the extra id field. The fix is to add a model_validator on JSONRPCNotification that rejects any input containing an id field, since notifications must not have one per JSON-RPC 2.0. Will have a PR up shortly.
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 id field). Would love to avoid duplicating effort! 😊
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.
This still reproduces on mcp==2.3.0 (and 1.30.0), and it is wider than null. Decoding {"jsonrpc":"2.0","id":<id>,"method":"tools/list"} through JSONRPCMessage:
id
Parsed as
42, "abc"
request
null
notification
1.0, 1e2
notification
1.5
notification
true, [1]
notification
So any id that is not an int or a string falls through to JSONRPCNotification, not just null. I checked message decoding only, not a live server.
For comparison, on the same inputs the TypeScript SDK reads 1.0 and 1e2 as requests with ids 1 and 100 and 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.
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.