Skip to content

fix(StatelessHTTPServerTransport): reject colliding in-flight JSON-RPC ids (#254, #265) - #267

Open
jpurnell wants to merge 1 commit into
modelcontextprotocol:mainfrom
jpurnell:fix/stateless-transport-id-correlation
Open

jpurnell wants to merge 1 commit into
modelcontextprotocol:mainfrom
jpurnell:fix/stateless-transport-id-correlation

Conversation

@jpurnell

Copy link
Copy Markdown

Summary

StatelessHTTPServerTransport keys per-request state — the response waiter and the
originating HTTP context — on the client-chosen JSON-RPC id. JSON-RPC ids are
client-scoped, and this transport is explicitly multi-client, so concurrent POSTs with
colliding ids are legal and common (most clients start their id sequence at 1). Two
concurrent requests sharing an id currently:

Fix

Reject a request whose id is already in flight with HTTP 409 / JSON-RPC -32600
instead of silently displacing the first request. The in-flight context map is populated
synchronously before any suspension, so it is a reliable "id in flight" marker on the
transport actor. This isolates every in-flight exchange and closes both the hang/leak and
the cross-client context disclosure.

This is the minimal, non-behavioral-change-to-ctx.id fix. Transparent support for
concurrent colliding ids (via internal id remapping) is a larger change tied to the
stateless-core semantics in SEP-2575 (#245) and is intentionally out of scope here.

Tests

Adds testCollidingInFlightRequestIDsRejected:

  • the duplicate returns promptly with 409 (no hang),
  • the first request's httpRequestContext is never overwritten (auth stays isolated),
  • the first request still completes normally with its own response,
  • only the first request is ever dispatched to the server.

All existing HTTP-server transport and handler-context tests pass unchanged; full MCP
suite green (552 tests).

Fixes #254
Fixes #265

…C ids

Per-request state (response waiter + HTTP context) was keyed by the client-chosen
JSON-RPC id. Those ids are client-scoped, and this transport is multi-client, so
concurrent POSTs with colliding ids (most clients start at 1) would:

- overwrite the first request's response waiter — the first POST hangs forever and its
  continuation leaks (modelcontextprotocol#254); and
- overwrite the first request's HTTP context — a handler processing one client's request
  could read another client's Authorization header (modelcontextprotocol#265).

Reject a request whose id is already in flight with HTTP 409 / JSON-RPC -32600 instead of
silently displacing the first. This isolates every in-flight exchange and closes both the
hang/leak and the cross-client context disclosure. Transparent support for concurrent
colliding ids is deferred to the stateless core (SEP-2575).

Adds a regression test asserting the duplicate is rejected promptly (no hang), the first
request's context is never overwritten, and only the first request reaches the server.

Fixes modelcontextprotocol#254
Fixes modelcontextprotocol#265
@varun06

varun06 commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Hi @jpurnell, thank you for this contribution, and for #268 as well.

We don't think rejecting duplicate in-flight ids with 409 is the right approach. JSON-RPC ids only need to be unique per client, so two different clients legitimately using id: 1 at the same time would be rejected. #298 instead gives each HTTP exchange a private internal id, which fixes #254 and #265 without rejecting valid requests.

We plan to close this PR once #298 is merged. If you think a case here isn't covered by #298, please comment and we'll make sure it's handled.

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

Labels

None yet

Projects

None yet

2 participants