Skip to content

Commit 553b6b6

Browse files
committed
fix: handle cancellation for request id zero
1 parent 5fc42e9 commit 553b6b6

3 files changed

Lines changed: 41 additions & 2 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@modelcontextprotocol/core": patch
3+
---
4+
5+
fix: handle cancellation for request id zero

packages/core/src/shared/protocol.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,11 +409,12 @@ export abstract class Protocol<ContextT extends BaseContext> {
409409
protected abstract buildContext(ctx: BaseContext, transportInfo?: MessageExtraInfo): ContextT;
410410

411411
private async _oncancel(notification: CancelledNotification): Promise<void> {
412-
if (!notification.params.requestId) {
412+
const requestId = notification.params.requestId;
413+
if (requestId === undefined) {
413414
return;
414415
}
415416
// Handle request cancellation
416-
const controller = this._requestHandlerAbortControllers.get(notification.params.requestId);
417+
const controller = this._requestHandlerAbortControllers.get(requestId);
417418
controller?.abort(notification.params.reason);
418419
}
419420

packages/core/test/shared/protocol.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2319,6 +2319,39 @@ describe('Request Cancellation vs Task Cancellation', () => {
23192319
expect(wasAborted).toBe(true);
23202320
});
23212321

2322+
test('should abort request handler for requestId 0', async () => {
2323+
await protocol.connect(transport);
2324+
2325+
let wasAborted = false;
2326+
protocol.setRequestHandler('ping', async (_request, ctx) => {
2327+
await new Promise(resolve => setTimeout(resolve, 100));
2328+
wasAborted = ctx.mcpReq.signal.aborted;
2329+
return {};
2330+
});
2331+
2332+
transport.onmessage?.({
2333+
jsonrpc: '2.0',
2334+
id: 0,
2335+
method: 'ping',
2336+
params: {}
2337+
});
2338+
2339+
await new Promise(resolve => setTimeout(resolve, 10));
2340+
2341+
transport.onmessage?.({
2342+
jsonrpc: '2.0',
2343+
method: 'notifications/cancelled',
2344+
params: {
2345+
requestId: 0,
2346+
reason: 'User cancelled'
2347+
}
2348+
});
2349+
2350+
await new Promise(resolve => setTimeout(resolve, 150));
2351+
2352+
expect(wasAborted).toBe(true);
2353+
});
2354+
23222355
test('should NOT automatically cancel associated tasks when notifications/cancelled is received', async () => {
23232356
await protocol.connect(transport);
23242357

0 commit comments

Comments
 (0)