-
Notifications
You must be signed in to change notification settings - Fork 1.9k
fix(protocol): progress-notification race; preserve pre-dispatch abort reason #2378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+214
−13
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@modelcontextprotocol/core-internal': patch | ||
| --- | ||
|
|
||
| Fix a client-side race where `notifications/progress` emitted immediately before a response could surface as a spurious "unknown progress token" `onerror`: notification handlers now dispatch synchronously in arrival order (matching responses), and progress for a request that has already settled is dropped silently — never-issued tokens still error. A new `ProtocolOptions.onLateProgress` hook lets callers observe the dropped late notifications. Also: aborts (pre-dispatch and in-flight) now carry the original `signal.reason` at `SdkError.data.reason` in addition to the stringified message, so callers can recover the abort reason structurally. |
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
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
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 The new public
ProtocolOptions.onLateProgresshook has no concrete consumer in this PR — only the new tests use it — and the core fix (tombstoning retired tokens + silently dropping late progress) works without it; per the repo's minimalism conventions, consider shipping the silent-drop fix without the hook or justifying it with a real consumer. If the hook (and the new silent-drop semantics) is kept, prose docs should be updated as well — docs/client.md's "Tracking progress" section doesn't mention either; the PR adds only JSDoc and a changeset.Extended reasoning...
What this is. The PR adds a new public option,
ProtocolOptions.onLateProgress, alongside the actual fix for the progress-notification race. The fix itself has two independent parts: (1) notification handlers now dispatch synchronously in arrival order, and (2) progress tokens retired by a response/cancel/timeout/send-failure are tombstoned so a genuinely-latenotifications/progressis dropped instead of surfacing as a spurious unknown-tokenonerror. Neither part requires the hook — it is purely an observability escape hatch for the dropped notifications.No concrete consumer. A grep across the repo shows the only callers of
onLateProgressare the new tests inprotocol.test.ts; there is no production callsite, example, or cookbook usage anywhere in the diff. The repository's review conventions are explicit on this: burden of proof is on addition, every new export is intentional, and new abstractions have at least one concrete callsite in the PR. The hook fails all three as it stands.Why this matters more than a typical option.
ProtocolOptionsis not internal-only surface: it is re-exported frompackages/core-internal/src/exports/public/index.tsand bothClientOptions(packages/client/src/client/client.ts:160) andServerOptions(packages/server/src/server/server.ts:72) extend it. SoonLateProgressbecomes user-facing public API on theClientandServerconstructors the moment this merges, and removing it later is a breaking change. That asymmetry — easy to add now, hard to remove later — is exactly what the minimalism convention is guarding against.The strongest argument for keeping it, and why it doesn't carry on its own. Before this PR, late progress surfaced via
onerror, so the hook arguably preserves observability that the silent-drop change removes. But the PR's own framing (and the changeset) treats thatonerroras spurious noise — that's the bug being fixed — and no real consumer asking to observe these dropped notifications is cited. A caller receiving the bareProgressNotificationcouldn't act on it meaningfully anyway (the request has already settled). If a maintainer or downstream user has a concrete need, that justification belongs in the PR; absent one, the simpler shape is to ship the tombstone + silent drop without the hook.Concrete walk-through. A user constructs
new Client({...}, { onLateProgress: ... })— this works today only becauseClientOptions extends ProtocolOptions. Once published in a release, any later decision that the hook was unnecessary requires a deprecation cycle on the public client/server option surface, even though nothing in the SDK or its examples ever exercised it. Compare with shipping just the fix: the only externally visible change is that the spuriousonerrorstops firing for tombstoned tokens, which is the behavior the PR description advertises.Documentation gap (if the hook stays).
docs/client.md's "Tracking progress" section (~line 424) documentsonprogress,resetTimeoutOnProgress, andmaxTotalTimeout, anddocs/server.mdhas a "Progress" section — neither mentions the new late-progress drop semantics oronLateProgress, and the PR adds only JSDoc and a changeset. The repo conventions ask for prose documentation (not just JSDoc) for new user-visible features. To be fair, no existing prose described the old unknown-tokenonerrorbehavior, so nothing in the docs is now incorrect — this is a missing-new-docs point, and it is contingent on the hook surviving the design question above. If the hook is dropped, a one-line note about the silent-drop behavior change is optional.Suggested resolution. Either remove
onLateProgressand ship the tombstone/silent-drop fix on its own (preferred per the conventions), or keep it with (a) a concrete consumer or stated real-world need and (b) a paragraph in the "Tracking progress" docs covering the new late-progress semantics and the hook.