Skip to content

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

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

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>
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 silently drops in-flight request when client reuses a JSON-RPC id

1 participant