Skip to content

fix(server): reject duplicate in-flight request ids in streamable HTTP#2434

Open
Sammy-Dabbas wants to merge 1 commit into
modelcontextprotocol:mainfrom
Sammy-Dabbas:issue-2433-duplicate-request-ids
Open

fix(server): reject duplicate in-flight request ids in streamable HTTP#2434
Sammy-Dabbas wants to merge 1 commit into
modelcontextprotocol:mainfrom
Sammy-Dabbas:issue-2433-duplicate-request-ids

Conversation

@Sammy-Dabbas

Copy link
Copy Markdown

Fixes #2433.

The transport registers every request via _requestToStreamMapping.set(message.id, streamId) with no duplicate check. A concurrent POST reusing an in-flight id overwrites the entry, send() routes the first request's response onto the second POST's stream, and the completion cleanup retires the id so the first POST hangs forever. Reproduced in SSE mode, JSON response mode, and within a single batch before the fix.

This rejects a POST containing a request whose id is already in flight, or duplicated within the same batch, with HTTP 400 and JSON-RPC -32600, matching the transport's existing Invalid Request rejection pattern. Sequential reuse after completion stays allowed, since deployed clients send a constant id for every request. One necessary companion change: a cancelled request never produces a response (the protocol layer suppresses responses for aborted handlers), so notifications/cancelled now retires the transport bookkeeping for its target id. Without that, a cancelled id would stay in flight forever and lock out cancel-then-reuse clients. That required adding the missing isCancelledNotification guard to core-internal, following the existing isInitializedNotification pattern.

Two judgment calls worth flagging: the guard enforces uniqueness among in-flight ids rather than the spec's literal never-reused-in-a-session, since strict enforcement needs unbounded memory and would break clients that reuse a constant id. And rejection uses 400 with -32600 rather than a different status, to match how the transport already rejects invalid requests.

Tests: five new tests, of which three reproduce the bug pre-fix (SSE, JSON mode, within-batch) and two pin preserved behavior (sequential reuse, cancel-then-reuse). Transport file 52/52, server package 407/407, core-internal and node middleware suites pass, typecheck and lint clean.

This is the TypeScript side of the same defect just fixed in the python SDK (modelcontextprotocol/python-sdk#3063); the two guards use the same semantics for cross-SDK consistency.

The transport registered every request via
_requestToStreamMapping.set(message.id, streamId) with no duplicate
check, so a concurrent POST reusing an in-flight id overwrote the
entry, cross-wired the first request's response onto the second POST's
stream, and left the first POST hanging.

Reject a POST containing a request whose id is already in flight, or
duplicated within the same batch, with HTTP 400 and JSON-RPC -32600.
Sequential reuse after completion stays allowed since deployed clients
send a constant id for every request. Cancelled requests never produce
a response, so notifications/cancelled now retires the transport
bookkeeping for its target id; without that, a cancelled id would stay
in flight forever and lock out cancel-then-reuse clients. Adds the
missing isCancelledNotification guard to core-internal alongside the
existing notification guards.

Fixes modelcontextprotocol#2433.
@Sammy-Dabbas Sammy-Dabbas requested a review from a team as a code owner July 4, 2026 05:28
@changeset-bot

changeset-bot Bot commented Jul 4, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 992dced

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2434

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/@modelcontextprotocol/codemod@2434

@modelcontextprotocol/core

npm i https://pkg.pr.new/@modelcontextprotocol/core@2434

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2434

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/@modelcontextprotocol/server-legacy@2434

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2434

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2434

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2434

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2434

commit: 992dced

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.

streamable HTTP server: no duplicate-in-flight request-id guard — concurrent POSTs with the same JSON-RPC id cross-wire responses

1 participant