Skip to content

fix(core): treat relatedRequestId 0 as present in debounce guard (closes #2117)#2141

Open
adityachilka1 wants to merge 1 commit into
modelcontextprotocol:mainfrom
adityachilka1:fix/notification-debounce-falsy-id
Open

fix(core): treat relatedRequestId 0 as present in debounce guard (closes #2117)#2141
adityachilka1 wants to merge 1 commit into
modelcontextprotocol:mainfrom
adityachilka1:fix/notification-debounce-falsy-id

Conversation

@adityachilka1
Copy link
Copy Markdown

Closes #2117.

The debounce guard in Protocol.notification() treated relatedRequestId: 0
as absent:

// packages/core/src/shared/protocol.ts
const canDebounce =
    debouncedMethods.includes(notification.method) &&
    !notification.params &&
    !options?.relatedRequestId &&    // ✇ falsy-check eats 0
    !options?.relatedTask;

0 is a valid JSON-RPC RequestId (and is also the first id a Protocol
instance uses for requests). The issue author included a standalone repro that
drives notification() directly and shows the asymmetry:

relatedRequestId debounced? (pre-fix)
0 YES (bug)
1 no
"0" no

So two related notifications for request id 0 in the same tick got coalesced
even though related notifications are explicitly supposed to bypass debouncing.

The fix

One-line change: swap the truthy check for a null check (the same fix pattern
the issue author proposed):

         const canDebounce =
-            debouncedMethods.includes(notification.method) && !notification.params && !options?.relatedRequestId && !options?.relatedTask;
+            debouncedMethods.includes(notification.method) && !notification.params && options?.relatedRequestId == null && !options?.relatedTask;

== null catches both null and undefined, and is the idiom the codebase
already uses when preserving 0 as a valid id (e.g. in parallel patches for
#2115 / #2116).

relatedTask is a RelatedTaskMetadata object, so a falsy check is still
correct for it.

Test

Added a regression test should NOT debounce a notification that has relatedRequestId 0 immediately after the existing 'req-1' / 'req-2' test.
It pins that two synchronous calls with 0 each reach the transport (no
coalescing). Without the fix, the test fails (only 1 call reaches the spy).

Verification

cd packages/core && pnpm exec vitest run test/shared/protocol.test.ts

-> 151 / 151 tests passing (the new test is in the 151).

No behavioral regression

The only change is that relatedRequestId: 0 now correctly bypasses debouncing.
All other values (1, "req-1", undefined) hit the same code path as before.


(This is a clean pick-up of the issue author's root-cause analysis and
proposed fix - all credit to @tylerklose for the diagnosis.)

@adityachilka1 adityachilka1 requested a review from a team as a code owner May 21, 2026 21:10
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 21, 2026

⚠️ No Changeset found

Latest commit: d6ef917

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 21, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@2141

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/codemod@2141

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@2141

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@2141

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/fastify@2141

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@2141

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@2141

commit: d6ef917

@adityachilka1 adityachilka1 force-pushed the fix/notification-debounce-falsy-id branch from 7e82177 to 8637c59 Compare May 21, 2026 21:49
`0` is a valid JSON-RPC RequestId (and is also the first id a
Protocol instance uses). The debounce guard in `Protocol.notification`
used a truthy check `!options?.relatedRequestId` which treated 0 as
absent, and incorrectly coalesced related notifications that should
have bypassed debouncing.

Compare the asymmetry:

  | relatedRequestId | debounced? (pre-fix) |
  | 0                | YES (bug)            |
  | 1                | no                   |
  | "0"              | no                   |

Switch to `options?.relatedRequestId == null` - catches anull/undefined, preserves 0 as a valid id.

`relatedTask` is a structured object and cannot be falsy, so its guard
is unchanged.

Closes modelcontextprotocol#2117.
@adityachilka1 adityachilka1 force-pushed the fix/notification-debounce-falsy-id branch from 8637c59 to d6ef917 Compare May 21, 2026 21:54
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.

relatedRequestId 0 is treated as absent by notification debounce guard

2 participants