Repository navigation
fix: resolve stream ID from event store metadata in InMemoryEventStore - #3149
Open
zhouyunding wants to merge 1 commit into
Open
zhouyunding wants to merge 1 commit into
zhouyunding wants to merge 1 commit into
Conversation
`getStreamIdFromEventId()` recovered the stream ID with
`eventId.split('_')[0]`, which returns an empty string for stream IDs
that start with an underscore (the standalone GET stream `_GET_stream`)
and truncates IDs containing an underscore (`stream_1` becomes `stream`).
As a result `replayEventsAfter()` returned an empty stream ID and replayed
events were silently dropped after a resume.
Read the stream ID from the event's stored metadata instead of parsing it
out of the event ID, whose format is an internal implementation detail.
|
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
This branch has not been deployed
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.
Fixes #2560
Root cause
InMemoryEventStore.getStreamIdFromEventIdderived the stream ID by parsing theevent ID (
eventId.split('_')[0]). Event IDs have the form${streamId}_${timestamp}_${rand}, which is not a reversible encoding when thestream ID itself contains underscores:
_GET_stream(the standalone GET stream) → parsed as''stream_1→ truncated tostreamImpact (measured)
With a real
WebStandardStreamableHTTPServerTransport+ this store, resuming astandalone GET stream via
Last-Event-IDreturns 200text/event-streambut thestream is closed immediately (0 ms) with zero frames, and the successor stream is
never registered under
_GET_stream— every later server-initiated notificationon that connection is silently dropped. The default client retries
(
maxRetries: 2) and then gives up. Replaying already-stored events is alsoempty, because replay filters under the wrong stream ID.
Scope note:
_GET_streamis the only underscore-leading stream ID used by theshipped transport (request streams use
crypto.randomUUID()); the truncationpath only affects direct users of this exported example store whose stream IDs
contain underscores.
Fix
Read the stream ID from the store's own metadata instead of parsing the event ID:
The store already persists
{ streamId, message }, so this is the singleunambiguous source of truth (the "explicit eventId→streamId mapping" option from
the issue). String parsing — including
lastIndexOf— cannot work in general,because the random suffix may itself contain underscores.
Behavior change
replayEventsAfternow returns the correct stream ID and replays subsequentevents for
_GET_streamand any stream ID containing underscores.lastEventId/ empty store still return''(unchanged).EventStorecontract is untouched.Testing (both sides)
expected '' to be '_GET_stream',expected 'stream' to be 'stream_1'.build:all,lint:all,pnpm -r typecheck,transportResumability(3/3),hosting-resumee2e (14/14) all pass.0 ms, no frames,
mappingKeys=[]; fixed → replays the subsequent event,mapping key
_GET_stream, stream stays open >500 ms.Regression protection is unit-level (
examples/shared); the repo'sintegration/e2e doubles use the same
split('_')[0]pattern and never exerciseunderscore stream IDs — intentionally left out of scope.
Known limitations (pre-existing, not introduced by this PR)
sort includes the random suffix); measured locally, deserves its own issue.
@mcp-examples/sharedisprivate: trueand listed in.changeset/config.jsonignore.AI disclosure
This PR was developed with AI assistance (implementation, tests, and independent
reviews by separate AI reviewers under human supervision). All changes were
verified locally before submission (RED→GREEN tests, lint, typecheck, e2e).