Bug
In StreamableHTTPClientTransport, _handleSseStream() tracks the latest event id in a local lastEventId initialized to undefined. it is never seeded from the resumptionToken the stream was opened with. Both reconnect paths pass resumptionToken: lastEventId when scheduling the next attempt.
So if a stream resumed with Last-Event-ID: e1 disconnects again before any id-bearing event arrives (LB idle timeout, server restart), the reconnect GET is sent with no Last-Event-ID header. 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.
Repro
- Open a stream with
resumptionToken: 'event-1' (GET carries Last-Event-ID: event-1 )
- Server accepts (200,
text/event-stream) but the stream closes before any event with an id
- The scheduled reconnect GET carries no
Last-Event-ID header.
Verified with a failing unit test: mock the resumed GET with a body that closes immediately,
then assert the second fetch call's headers and last-event-id is null.
Suggested fix
Seed the tracker from the options the stream was opened with:
let lastEventId: string | undefined = options.resumptionToken;
so a reconnect that saw no new events re-sends the same token (replay is idempotent)
instead of silently dropping it.
Happy to submit a PR with the fix + regression test.
Bug
In
StreamableHTTPClientTransport,_handleSseStream()tracks the latest event id in a locallastEventIdinitialized toundefined. it is never seeded from theresumptionTokenthe stream was opened with. Both reconnect paths passresumptionToken: lastEventIdwhen scheduling the next attempt.So if a stream resumed with
Last-Event-ID: e1disconnects again before any id-bearing event arrives (LB idle timeout, server restart), the reconnect GET is sent with noLast-Event-IDheader. The server treats it as a brand-new standalone stream instead ofa resumption, missed events are never replayed and a long-running request hangs until timeout.
Repro
resumptionToken: 'event-1'(GET carriesLast-Event-ID: event-1)text/event-stream) but the stream closes before any event with an idLast-Event-IDheader.Verified with a failing unit test: mock the resumed GET with a body that closes immediately,
then assert the second fetch call's headers and
last-event-idisnull.Suggested fix
Seed the tracker from the options the stream was opened with:
so a reconnect that saw no new events re-sends the same token (replay is idempotent)
instead of silently dropping it.
Happy to submit a PR with the fix + regression test.