fix(client): re-send Last-Event-ID when a resumed SSE stream disconnects before any id-bearing event - #2809
Draft
nikolas-sapa wants to merge 1 commit into
Draft
Conversation
…cts before any id-bearing event _handleSseStream() initialized its local lastEventId tracker to undefined instead of seeding it from the resumptionToken the stream was opened with. When such a stream disconnected before any event with an id arrived (LB idle timeout, server restart), the reconnect GET went out without a Last-Event-ID header, so the server treated it as a brand-new stream instead of a resumption: missed events were never replayed and long-running requests hung until timeout. Seed the tracker from options.resumptionToken; replay is idempotent, so re-sending the same token when no new events arrived is safe. Fixes modelcontextprotocol#2499
🦋 Changeset detectedLatest commit: f06b671 The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
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.
Summary
Fixes #2499 (
ready for work, P2).In
StreamableHTTPClientTransport._handleSseStream(), the locallastEventIdtracker starts asundefinedand is only set when an event with an id arrives. But the stream itself may have been opened with aresumptionToken(_startOrAuthSsesends it asLast-Event-ID). If that stream disconnects before any id-bearing event arrives — LB idle timeout, server restart — the scheduled reconnect passesresumptionToken: lastEventId=undefined, so the reconnect GET carries noLast-Event-IDheader. The server treats it as a brand-new standalone stream instead of a resumption: missed events are never replayed and a long-running request hangs until timeout.Fix: seed the tracker from the options the stream was opened with:
Replay is idempotent, so re-sending the same token when no new events arrived is safe.
Verification
New regression test (mirrors the existing
should reconnect on graceful stream closesibling): a stream opened withresumptionToken: 'evt-1'that closes before any id-bearing event.last-event-id: null—expected null to be 'evt-1'last-event-id: 'evt-1'— test passesFull
test/client/streamableHttp.test.tssuite: 76/76 passing.Type of change
Checklist
@modelcontextprotocol/client: patch)