Skip to content

Commit b944265

Browse files
committed
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
1 parent 01954e6 commit b944265

2 files changed

Lines changed: 22 additions & 13 deletions

File tree

packages/core/src/shared/protocol.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,9 @@ export abstract class Protocol<ContextT extends BaseContext> {
302302
private _requestHandlers: Map<string, (request: JSONRPCRequest, ctx: ContextT) => Promise<Result>> = new Map();
303303
private _requestHandlerAbortControllers: Map<RequestId, AbortController> = new Map();
304304
private _notificationHandlers: Map<string, (notification: JSONRPCNotification) => Promise<void>> = new Map();
305-
private _responseHandlers: Map<number, (response: JSONRPCResultResponse | Error) => void> = new Map();
306-
private _progressHandlers: Map<number, ProgressCallback> = new Map();
307-
private _timeoutInfo: Map<number, TimeoutInfo> = new Map();
305+
private _responseHandlers: Map<RequestId, (response: JSONRPCResultResponse | Error) => void> = new Map();
306+
private _progressHandlers: Map<RequestId, ProgressCallback> = new Map();
307+
private _timeoutInfo: Map<RequestId, TimeoutInfo> = new Map();
308308
private _pendingDebouncedNotifications = new Set<string>();
309309

310310
private _taskManager: TaskManager;
@@ -406,7 +406,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
406406
}
407407

408408
private _setupTimeout(
409-
messageId: number,
409+
messageId: RequestId,
410410
timeout: number,
411411
maxTotalTimeout: number | undefined,
412412
onTimeout: () => void,
@@ -422,7 +422,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
422422
});
423423
}
424424

425-
private _resetTimeout(messageId: number): boolean {
425+
private _resetTimeout(messageId: RequestId): boolean {
426426
const info = this._timeoutInfo.get(messageId);
427427
if (!info) return false;
428428

@@ -440,7 +440,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
440440
return true;
441441
}
442442

443-
private _cleanupTimeout(messageId: number) {
443+
private _cleanupTimeout(messageId: RequestId) {
444444
const info = this._timeoutInfo.get(messageId);
445445
if (info) {
446446
clearTimeout(info.timeoutId);
@@ -648,7 +648,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
648648

649649
private _onprogress(notification: ProgressNotification): void {
650650
const { progressToken, ...params } = notification.params;
651-
const messageId = Number(progressToken);
651+
const messageId = progressToken as RequestId;
652652

653653
const handler = this._progressHandlers.get(messageId);
654654
if (!handler) {
@@ -676,7 +676,12 @@ export abstract class Protocol<ContextT extends BaseContext> {
676676
}
677677

678678
private _onresponse(response: JSONRPCResponse | JSONRPCErrorResponse): void {
679-
const messageId = Number(response.id);
679+
// Handle responses without an ID (shouldn't happen for valid protocol messages)
680+
if (response.id === undefined) {
681+
this._onerror(new Error(`Received a response without an ID: ${JSON.stringify(response)}`));
682+
return;
683+
}
684+
const messageId = response.id;
680685

681686
// Delegate to TaskManager for task-related response handling
682687
const taskResult = this._taskManager.processInboundResponse(response, messageId);

packages/core/src/shared/taskManager.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export interface TaskManagerHost {
4242
request<T extends AnySchema>(request: Request, resultSchema: T, options?: RequestOptions): Promise<SchemaOutput<T>>;
4343
notification(notification: Notification, options?: NotificationOptions): Promise<void>;
4444
reportError(error: Error): void;
45-
removeProgressHandler(token: number): void;
45+
removeProgressHandler(token: RequestId): void;
4646
registerHandler(method: string, handler: (request: JSONRPCRequest, ctx: BaseContext) => Promise<Result>): void;
4747
sendOnResponseStream(message: JSONRPCNotification | JSONRPCRequest, relatedRequestId: RequestId): Promise<void>;
4848
enforceStrictCapabilities: boolean;
@@ -195,7 +195,7 @@ export function extractTaskManagerOptions(tasksCapability: TaskManagerOptions |
195195
export class TaskManager {
196196
private _taskStore?: TaskStore;
197197
private _taskMessageQueue?: TaskMessageQueue;
198-
private _taskProgressTokens: Map<string, number> = new Map();
198+
private _taskProgressTokens: Map<string, RequestId> = new Map();
199199
private _requestResolvers: Map<RequestId, (response: JSONRPCResultResponse | Error) => void> = new Map();
200200
private _options: TaskManagerOptions;
201201
private _host?: TaskManagerHost;
@@ -584,7 +584,11 @@ export class TaskManager {
584584
}
585585

586586
private handleResponse(response: JSONRPCResponse | JSONRPCErrorResponse): boolean {
587-
const messageId = Number(response.id);
587+
// Skip responses without an ID
588+
if (response.id === undefined) {
589+
return false;
590+
}
591+
const messageId = response.id;
588592
const resolver = this._requestResolvers.get(messageId);
589593
if (resolver) {
590594
this._requestResolvers.delete(messageId);
@@ -598,7 +602,7 @@ export class TaskManager {
598602
return false;
599603
}
600604

601-
private shouldPreserveProgressHandler(response: JSONRPCResponse | JSONRPCErrorResponse, messageId: number): boolean {
605+
private shouldPreserveProgressHandler(response: JSONRPCResponse | JSONRPCErrorResponse, messageId: RequestId): boolean {
602606
if (isJSONRPCResultResponse(response) && response.result && typeof response.result === 'object') {
603607
const result = response.result as Record<string, unknown>;
604608
if (result.task && typeof result.task === 'object') {
@@ -764,7 +768,7 @@ export class TaskManager {
764768

765769
processInboundResponse(
766770
response: JSONRPCResponse | JSONRPCErrorResponse,
767-
messageId: number
771+
messageId: RequestId
768772
): { consumed: boolean; preserveProgress: boolean } {
769773
const consumed = this.handleResponse(response);
770774
if (consumed) {

0 commit comments

Comments
 (0)