Skip to content

Streamable HTTP server misroutes responses when concurrent requests on one session reuse a JSON-RPC request ID #1004

@SagenKoder

Description

@SagenKoder

Describe the bug

If two POSTs are concurrently in flight on the same Mcp-Session-Id with the same JSON-RPC request ID, the server delivers the first request's response to the second POST, while the first hangs until client timeout — its tool having executed. The second request is never executed and gets no error; the misrouted response carries the ID it was waiting on, so it is accepted silently.

Reusing an in-flight ID is a client-side spec violation, but the server reacts by corrupting the compliant request rather than rejecting the offending one. Observed in practice with multiple client processes sharing one session (which the SDK permits): 6–25% of parallel tools/call failed silently, 0% sequentially.

To Reproduce

  1. POST a slow tools/call with request ID 1.
  2. While it is in flight, POST another tools/call on the same session, also with ID 1.
  3. The slow tool's response is written to the second POST; the first POST hangs in hangResponse.

Expected behavior

The second POST is rejected (e.g. HTTP 400 / JSON-RPC -32600) and the first request completes unaffected — matching the stdio transport, which already rejects in-flight duplicates (ioConn.addBatch).

Mechanism (at main, 88c58c3)

servePOST registers c.requestStreams[reqID] = stream.id with no duplicate check (mcp/streamable.go:1586), so the second POST overwrites the first request's response routing, and the response later routes by bare ID to the last-registered stream (:1697-1707). jsonrpc2 does detect the duplicate but zeroes req.ID, so it is handled as a notification and no error response is written (internal/jsonrpc2/conn.go:556). Sequential ID reuse is unaffected — entries are deleted when responses are delivered — so this requires two simultaneously in-flight requests.

Versions

Reproduced on v1.6.0 (f5f2015) and main (88c58c3); go version go1.25.0 linux/arm64.

I have a minimal fix (atomic check-and-register in servePOST, rejecting duplicates before any response data is written) with deterministic regression tests, and can open a PR if rejecting server-side is the agreed direction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions