Serve subscriptions/listen streams that carry the same request id - #567
Merged
koic merged 1 commit intoSep 22, 2026
Conversation
## Motivation and Context The Streamable HTTP transport kept its open `subscriptions/listen` streams in one registry keyed by the request's JSON-RPC id, and closed a new stream before its acknowledgement whenever another stream already carried that id. The specification scopes request id uniqueness to a single sender's in-flight requests, and stamps the id into `_meta` as `subscriptionId` so that a client can tell its own streams apart; nothing makes it unique across clients. The TypeScript and Python clients number their requests with a counter, so two of them whose listen request carried the same small integer collided, and the second saw its stream end with neither an acknowledgement nor an error. On the modern lifecycle every client reaches that registry without a session, so one client could also keep another from opening a stream under an id it held. The reference SDKs keep each listen stream scoped to its own connection and hold no id-keyed registry across clients. The registry is now keyed by an identifier the transport mints for each stream, and the request id is kept on the entry for the `subscriptionId` in acknowledgements, notifications, and the closing result. Streams that carry the same request id are served side by side, each stamped with the id it sent, and a write failure or the keepalive of one of them frees only that stream. The concurrent stream cap is unchanged. ## How Has This Been Tested? New tests in `test/mcp/server/transports/streamable_http_transport_test.rb` open two streams under the same request id and check that both are acknowledged, both receive a notification stamped with that id, each receives its own result on transport close, and a failed write on one frees only that one. Against the previous library the second stream is closed before its acknowledgement. ## Breaking Changes None. A `subscriptions/listen` stream whose request id another open stream already carries is served instead of being closed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation and Context
The Streamable HTTP transport kept its open
subscriptions/listenstreams in one registry keyed by the request's JSON-RPC id, and closed a new stream before its acknowledgement whenever another stream already carried that id. The specification scopes request id uniqueness to a single sender's in-flight requests, and stamps the id into_metaassubscriptionIdso that a client can tell its own streams apart; nothing makes it unique across clients. The TypeScript and Python clients number their requests with a counter, so two of them whose listen request carried the same small integer collided, and the second saw its stream end with neither an acknowledgement nor an error. On the modern lifecycle every client reaches that registry without a session, so one client could also keep another from opening a stream under an id it held. The reference SDKs keep each listen stream scoped to its own connection and hold no id-keyed registry across clients.The registry is now keyed by an identifier the transport mints for each stream, and the request id is kept on the entry for the
subscriptionIdin acknowledgements, notifications, and the closing result. Streams that carry the same request id are served side by side, each stamped with the id it sent, and a write failure or the keepalive of one of them frees only that stream. The concurrent stream cap is unchanged.How Has This Been Tested?
New tests in
test/mcp/server/transports/streamable_http_transport_test.rbopen two streams under the same request id and check that both are acknowledged, both receive a notification stamped with that id, each receives its own result on transport close, and a failed write on one frees only that one. Against the previous library the second stream is closed before its acknowledgement.Breaking Changes
None. A
subscriptions/listenstream whose request id another open stream already carries is served instead of being closed.Types of changes
Checklist