Skip to content

Refuse a request whose id is null and do not run a request method without an id - #612

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:reject_requests_with_a_null_id
Open

koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:reject_requests_with_a_null_id

Conversation

@koic

@koic koic commented Oct 15, 2026

Copy link
Copy Markdown
Member

Motivation and Context

A message carrying "id": null is treated as a notification: JsonRpcHandler accepts a null id, the method runs, and no response is produced, so over Streamable HTTP the client gets 202 Accepted for a tools/call whose write it never hears about (issue #601). MCP narrows JSON-RPC here: a request MUST carry a string or integer id and the id MUST NOT be null. The TypeScript server refuses such a message at schema validation with 400 and the Python server parses it as a notification it has no handler for and drops it; neither runs it. A request method sent with no id member at all, a notification by JSON-RPC rules, was run the same way.

A null id now fails the message with -32600 Invalid Request, carrying a null id as the only id there is, and the method does not run; the Streamable HTTP transport treats such a message as a request, so the error reaches the client with 200 on the legacy lifecycle and 400 on the modern one, instead of a 202. A protocol request method sent without an id is not run either, and gets no response, as a notification; a custom method keeps running without an id, since define_custom_method serves notifications too. The invalid-id message no longer names null as accepted.

Fixes #601.

How Has This Been Tested?

test/json_rpc_handler_test.rb (null id refused without calling the method, the message without a pattern), test/mcp/server_test.rb (null id and id-less tools/call do not run the tool, a custom method still runs without an id) and test/mcp/server/transports/streamable_http_transport_test.rb (null id answered with the error on both lifecycles, an id-less tools/call acknowledged without running, an id-less ping acknowledged with no response).
Against the previous library the null-id and id-less tests run the tool.

Breaking Changes

A client sending "id": null no longer has the method executed and gets -32600, as the TypeScript server already refused it; a request method sent without an id, framed as a notification, is no longer executed either, and gets no response, as the TypeScript and Python servers already dropped it.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

…hout an id

## Motivation and Context

A message carrying `"id": null` is treated as a notification: `JsonRpcHandler` accepts a null id, the method
runs, and no response is produced, so over Streamable HTTP the client gets `202 Accepted` for a `tools/call`
whose write it never hears about (issue modelcontextprotocol#601). MCP narrows JSON-RPC here: a request MUST carry a string or
integer id and the id MUST NOT be null. The TypeScript server refuses such a message at schema validation
with 400 and the Python server parses it as a notification it has no handler for and drops it; neither runs it.
A request method sent with no id member at all, a notification by JSON-RPC rules, was run the same way.

A null id now fails the message with `-32600` Invalid Request, carrying a null id as the only id there is,
and the method does not run; the Streamable HTTP transport treats such a message as a request, so the error
reaches the client with 200 on the legacy lifecycle and 400 on the modern one, instead of a 202. A protocol
request method sent without an id is not run either, and gets no response, as a notification; a custom
method keeps running without an id, since `define_custom_method` serves notifications too. The invalid-id
message no longer names null as accepted.

Fixes modelcontextprotocol#601.

## How Has This Been Tested?

`test/json_rpc_handler_test.rb` (null id refused without calling the method, the message without a pattern),
`test/mcp/server_test.rb` (null id and id-less `tools/call` do not run the tool, a custom method still runs
without an id) and `test/mcp/server/transports/streamable_http_transport_test.rb` (null id answered with
the error on both lifecycles, an id-less `tools/call` acknowledged without running, an id-less `ping`
acknowledged with no response).
Against the previous library the null-id and id-less tests run the tool.

## Breaking Changes

A client sending `"id": null` no longer has the method executed and gets `-32600`, as the TypeScript server
already refused it; a request method sent without an id, framed as a notification, is no longer executed
either, and gets no response, as the TypeScript and Python servers already dropped it.
@koic
koic force-pushed the reject_requests_with_a_null_id branch from 9e5f6cd to e20b7e4 Compare October 15, 2026 08:24

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Requests with "id": null are run like notifications

2 participants