Skip to content

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
modelcontextprotocol:mainfrom
nikolas-sapa:fix/sse-resume-token-seed
Draft

fix(client): re-send Last-Event-ID when a resumed SSE stream disconnects before any id-bearing event#2809
nikolas-sapa wants to merge 1 commit into
modelcontextprotocol:mainfrom
nikolas-sapa:fix/sse-resume-token-seed

Conversation

@nikolas-sapa

Copy link
Copy Markdown

Summary

Fixes #2499 (ready for work, P2).

In StreamableHTTPClientTransport._handleSseStream(), the local lastEventId tracker starts as undefined and is only set when an event with an id arrives. But the stream itself may have been opened with a resumptionToken (_startOrAuthSse sends it as Last-Event-ID). If that stream disconnects before any id-bearing event arrives — LB idle timeout, server restart — the scheduled reconnect passes resumptionToken: lastEventId = undefined, so the reconnect GET carries 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.

Fix: seed the tracker from the options the stream was opened with:

let lastEventId: string | undefined = options.resumptionToken;

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 close sibling): a stream opened with resumptionToken: 'evt-1' that closes before any id-bearing event.

  • without the fix: reconnect headers carry last-event-id: nullexpected null to be 'evt-1'
  • with the fix: last-event-id: 'evt-1' — test passes

Full test/client/streamableHttp.test.ts suite: 76/76 passing.

Type of change

  • Bug fix (non-breaking change that fixes an issue)

Checklist

  • Straightforward bug fix (one line + regression test), per CONTRIBUTING's issue-optional path
  • Changeset included (@modelcontextprotocol/client: patch)

…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-bot

changeset-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f06b671

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@modelcontextprotocol/client Patch
@modelcontextprotocol/codemod Patch
@modelcontextprotocol/core Patch
@modelcontextprotocol/server-legacy Patch
@modelcontextprotocol/server Patch
@modelcontextprotocol/core-internal Patch

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

@pkg-pr-new

pkg-pr-new Bot commented Sep 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2809

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/@modelcontextprotocol/codemod@2809

@modelcontextprotocol/core

npm i https://pkg.pr.new/@modelcontextprotocol/core@2809

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2809

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/@modelcontextprotocol/server-legacy@2809

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2809

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2809

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2809

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2809

commit: f06b671

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.

Bug: Resumption token dropped when a resumed SSE stream disconnects before any id-bearing event

1 participant