diff --git a/src/shared/protocol.ts b/src/shared/protocol.ts index ab8421bf..559d2c20 100644 --- a/src/shared/protocol.ts +++ b/src/shared/protocol.ts @@ -37,6 +37,11 @@ export type ProtocolOptions = { enforceStrictCapabilities?: boolean; }; +/** + * The default request timeout, in miliseconds. + */ +export const DEFAULT_REQUEST_TIMEOUT_MSEC = 60000; + /** * Options that can be given per request. */ @@ -50,6 +55,13 @@ export type RequestOptions = { * Can be used to cancel an in-flight request. This will cause an AbortError to be raised from request(). */ signal?: AbortSignal; + + /** + * A timeout (in milliseconds) for this request. If exceeded, an McpError with code `RequestTimeout` will be raised from request(). + * + * If not specified, `DEFAULT_REQUEST_TIMEOUT_MSEC` will be used as the timeout. + */ + timeout?: number; }; /** @@ -379,7 +391,13 @@ export abstract class Protocol< }; } + let timeoutId: ReturnType | undefined = undefined; + this._responseHandlers.set(messageId, (response) => { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + if (options?.signal?.aborted) { return; } @@ -396,8 +414,7 @@ export abstract class Protocol< } }); - options?.signal?.addEventListener("abort", () => { - const reason = options?.signal?.reason; + const cancel = (reason: unknown) => { this._responseHandlers.delete(messageId); this._progressHandlers.delete(messageId); @@ -411,9 +428,34 @@ export abstract class Protocol< }); reject(reason); + }; + + options?.signal?.addEventListener("abort", () => { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + + cancel(options?.signal?.reason); }); - this._transport.send(jsonrpcRequest).catch(reject); + const timeout = options?.timeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC; + timeoutId = setTimeout( + () => + cancel( + new McpError(ErrorCode.RequestTimeout, "Request timed out", { + timeout, + }), + ), + timeout, + ); + + this._transport.send(jsonrpcRequest).catch((error) => { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + + reject(error); + }); }); } diff --git a/src/types.ts b/src/types.ts index 5b9fb664..44a44d8f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -105,6 +105,7 @@ export const JSONRPCResponseSchema = z export enum ErrorCode { // SDK error codes ConnectionClosed = -1, + RequestTimeout = -2, // Standard JSON-RPC error codes ParseError = -32700,