Skip to content

fix: reject duplicate JSON-RPC request IDs with 409 Conflict (fixes #2655)#2944

Closed
kaXianc2-gom wants to merge 1 commit into
modelcontextprotocol:mainfrom
kaXianc2-gom:fix/duplicate-request-id-conflict
Closed

fix: reject duplicate JSON-RPC request IDs with 409 Conflict (fixes #2655)#2944
kaXianc2-gom wants to merge 1 commit into
modelcontextprotocol:mainfrom
kaXianc2-gom:fix/duplicate-request-id-conflict

Conversation

@kaXianc2-gom

Copy link
Copy Markdown

Summary

Fixes #2655: StreamableHTTPServerTransport now detects and rejects duplicate JSON-RPC request IDs before overwriting the in-flight stream slot.

Before: A client sending two requests with the same id on the same session would silently overwrite the first request's (send, receive) stream pair in _request_streams. The original caller hangs until timeout; the response may route to the wrong caller.

After: The server returns HTTP 409 Conflict with a JSON-RPC -32600 Invalid Request error, preserving the original in-flight stream.

Changes

  1. _handle_post_request — added collision check before stream registration, mirroring the existing GET_STREAM_KEY guard at line 695
  2. _create_error_response — added optional keyword-only request_id parameter so the JSON-RPC error envelope carries the offending id instead of null

AI Assistance Disclosure

This contribution was developed with AI assistance (Claude / Anthropic Claude Code).

Verification Process

  1. Synced sandbox to upstream main (2397319)
  2. uv sync --no-group docs installed all dependencies
  3. Baseline test suite: 2269 passed, 12 skipped, 1 xfailed (×3, no flakes)
  4. Applied fix (18 lines in streamable_http.py, +1 noqa: C901)
  5. Added targeted test test_duplicate_request_id_rejected_with_409:
    • Seeds _request_streams with an in-flight request
    • POSTs a duplicate request with the same id
    • Asserts 409 status, correct JSON-RPC error envelope with the id, and original stream preserved
  6. Full test suite after upstream merge: 2571 passed, 12 skipped, 9 xfailed
  7. ruff format + ruff check: all clean

Cross-Validation

Scenario Before After
Normal request (unique id)
Duplicate id while original in-flight ❌ silent overwrite ✅ 409 + preserved stream
Duplicate id after original completed ✅ normal (stream cleaned) ✅ same
Error response id field ❌ always null ✅ carries the request id
GET_STREAM_KEY collision (existing) ✅ (unchanged)

🤖 Generated with Claude Code

When a client reuses a JSON-RPC request id while the original request
is still in flight, StreamableHTTPServerTransport now checks for a
collision in _request_streams before registering a new stream slot.

Previously the assignment was unconditional, which silently overwrote
the prior (send, receive) pair — the original request's caller would
hang until timeout and the response could be routed to the wrong
caller.

The fix mirrors the existing GET_STREAM_KEY collision guard at line 695.

Also adds an optional request_id parameter to _create_error_response
so the JSON-RPC error envelope carries the offending id instead of
always using null.

Fixes modelcontextprotocol#2655

Co-Authored-By: Claude <noreply@anthropic.com>
@Kludex

Kludex commented Jun 25, 2026

Copy link
Copy Markdown
Member

You've opened a duplicated pull request, please search opened PRs before creating new ones. Duplicated from #2657.

@Kludex Kludex closed this Jun 25, 2026
@modelcontextprotocol modelcontextprotocol locked as resolved and limited conversation to collaborators Jun 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Streamable HTTP server silently drops in-flight request when client reuses a JSON-RPC id

2 participants