From b944265ff75a30e41bea8c762cc7e768ab7979f9 Mon Sep 17 00:00:00 2001 From: Rayan Salhab Date: Thu, 26 Mar 2026 22:20:32 +0000 Subject: [PATCH] fix: handle JSON-RPC request IDs exceeding MAX_SAFE_INTEGER Previously, the SDK used Number() to convert request/response IDs, which causes precision loss for values exceeding Number.MAX_SAFE_INTEGER (9007199254740991). This led to server hangs when receiving requests with large IDs because the response handler lookup would fail. Changes: - Changed Map key types from number to RequestId (string | number) - Removed Number() conversions on request/response IDs - Added undefined check for response.id in _onresponse - Updated TaskManagerHost.removeProgressHandler signature - Updated processInboundResponse and related methods Fixes #1765 --- packages/core/src/shared/protocol.ts | 21 +++++++++++++-------- packages/core/src/shared/taskManager.ts | 14 +++++++++----- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/core/src/shared/protocol.ts b/packages/core/src/shared/protocol.ts index d82d67da3..38f91526b 100644 --- a/packages/core/src/shared/protocol.ts +++ b/packages/core/src/shared/protocol.ts @@ -302,9 +302,9 @@ export abstract class Protocol { private _requestHandlers: Map Promise> = new Map(); private _requestHandlerAbortControllers: Map = new Map(); private _notificationHandlers: Map Promise> = new Map(); - private _responseHandlers: Map void> = new Map(); - private _progressHandlers: Map = new Map(); - private _timeoutInfo: Map = new Map(); + private _responseHandlers: Map void> = new Map(); + private _progressHandlers: Map = new Map(); + private _timeoutInfo: Map = new Map(); private _pendingDebouncedNotifications = new Set(); private _taskManager: TaskManager; @@ -406,7 +406,7 @@ export abstract class Protocol { } private _setupTimeout( - messageId: number, + messageId: RequestId, timeout: number, maxTotalTimeout: number | undefined, onTimeout: () => void, @@ -422,7 +422,7 @@ export abstract class Protocol { }); } - private _resetTimeout(messageId: number): boolean { + private _resetTimeout(messageId: RequestId): boolean { const info = this._timeoutInfo.get(messageId); if (!info) return false; @@ -440,7 +440,7 @@ export abstract class Protocol { return true; } - private _cleanupTimeout(messageId: number) { + private _cleanupTimeout(messageId: RequestId) { const info = this._timeoutInfo.get(messageId); if (info) { clearTimeout(info.timeoutId); @@ -648,7 +648,7 @@ export abstract class Protocol { private _onprogress(notification: ProgressNotification): void { const { progressToken, ...params } = notification.params; - const messageId = Number(progressToken); + const messageId = progressToken as RequestId; const handler = this._progressHandlers.get(messageId); if (!handler) { @@ -676,7 +676,12 @@ export abstract class Protocol { } private _onresponse(response: JSONRPCResponse | JSONRPCErrorResponse): void { - const messageId = Number(response.id); + // Handle responses without an ID (shouldn't happen for valid protocol messages) + if (response.id === undefined) { + this._onerror(new Error(`Received a response without an ID: ${JSON.stringify(response)}`)); + return; + } + const messageId = response.id; // Delegate to TaskManager for task-related response handling const taskResult = this._taskManager.processInboundResponse(response, messageId); diff --git a/packages/core/src/shared/taskManager.ts b/packages/core/src/shared/taskManager.ts index 28460d1d9..b13bf4210 100644 --- a/packages/core/src/shared/taskManager.ts +++ b/packages/core/src/shared/taskManager.ts @@ -42,7 +42,7 @@ export interface TaskManagerHost { request(request: Request, resultSchema: T, options?: RequestOptions): Promise>; notification(notification: Notification, options?: NotificationOptions): Promise; reportError(error: Error): void; - removeProgressHandler(token: number): void; + removeProgressHandler(token: RequestId): void; registerHandler(method: string, handler: (request: JSONRPCRequest, ctx: BaseContext) => Promise): void; sendOnResponseStream(message: JSONRPCNotification | JSONRPCRequest, relatedRequestId: RequestId): Promise; enforceStrictCapabilities: boolean; @@ -195,7 +195,7 @@ export function extractTaskManagerOptions(tasksCapability: TaskManagerOptions | export class TaskManager { private _taskStore?: TaskStore; private _taskMessageQueue?: TaskMessageQueue; - private _taskProgressTokens: Map = new Map(); + private _taskProgressTokens: Map = new Map(); private _requestResolvers: Map void> = new Map(); private _options: TaskManagerOptions; private _host?: TaskManagerHost; @@ -584,7 +584,11 @@ export class TaskManager { } private handleResponse(response: JSONRPCResponse | JSONRPCErrorResponse): boolean { - const messageId = Number(response.id); + // Skip responses without an ID + if (response.id === undefined) { + return false; + } + const messageId = response.id; const resolver = this._requestResolvers.get(messageId); if (resolver) { this._requestResolvers.delete(messageId); @@ -598,7 +602,7 @@ export class TaskManager { return false; } - private shouldPreserveProgressHandler(response: JSONRPCResponse | JSONRPCErrorResponse, messageId: number): boolean { + private shouldPreserveProgressHandler(response: JSONRPCResponse | JSONRPCErrorResponse, messageId: RequestId): boolean { if (isJSONRPCResultResponse(response) && response.result && typeof response.result === 'object') { const result = response.result as Record; if (result.task && typeof result.task === 'object') { @@ -764,7 +768,7 @@ export class TaskManager { processInboundResponse( response: JSONRPCResponse | JSONRPCErrorResponse, - messageId: number + messageId: RequestId ): { consumed: boolean; preserveProgress: boolean } { const consumed = this.handleResponse(response); if (consumed) {