@@ -37,6 +37,11 @@ export type ProtocolOptions = {
3737 enforceStrictCapabilities ?: boolean ;
3838} ;
3939
40+ /**
41+ * The default request timeout, in miliseconds.
42+ */
43+ export const DEFAULT_REQUEST_TIMEOUT_MSEC = 60000 ;
44+
4045/**
4146 * Options that can be given per request.
4247 */
@@ -48,10 +53,15 @@ export type RequestOptions = {
4853
4954 /**
5055 * Can be used to cancel an in-flight request. This will cause an AbortError to be raised from request().
51- *
52- * Use abortAfterTimeout() to easily implement timeouts using this signal.
5356 */
5457 signal ?: AbortSignal ;
58+
59+ /**
60+ * A timeout (in milliseconds) for this request. If exceeded, an McpError with code `RequestTimeout` will be raised from request().
61+ *
62+ * If not specified, `DEFAULT_REQUEST_TIMEOUT_MSEC` will be used as the timeout.
63+ */
64+ timeout ?: number ;
5565} ;
5666
5767/**
@@ -381,7 +391,13 @@ export abstract class Protocol<
381391 } ;
382392 }
383393
394+ let timeoutId : ReturnType < typeof setTimeout > | undefined = undefined ;
395+
384396 this . _responseHandlers . set ( messageId , ( response ) => {
397+ if ( timeoutId !== undefined ) {
398+ clearTimeout ( timeoutId ) ;
399+ }
400+
385401 if ( options ?. signal ?. aborted ) {
386402 return ;
387403 }
@@ -398,24 +414,52 @@ export abstract class Protocol<
398414 }
399415 } ) ;
400416
401- options ?. signal ?. addEventListener ( "abort" , ( ) => {
402- const reason = options ?. signal ?. reason ;
417+ const cancel = ( reason : unknown ) => {
403418 this . _responseHandlers . delete ( messageId ) ;
404419 this . _progressHandlers . delete ( messageId ) ;
405420
406- this . _transport ?. send ( {
407- jsonrpc : "2.0" ,
408- method : "cancelled" ,
409- params : {
410- requestId : messageId ,
411- reason : String ( reason ) ,
412- } ,
413- } ) ;
421+ this . _transport
422+ ?. send ( {
423+ jsonrpc : "2.0" ,
424+ method : "cancelled" ,
425+ params : {
426+ requestId : messageId ,
427+ reason : String ( reason ) ,
428+ } ,
429+ } )
430+ . catch ( ( error ) =>
431+ this . _onerror ( new Error ( `Failed to send cancellation: ${ error } ` ) ) ,
432+ ) ;
414433
415434 reject ( reason ) ;
435+ } ;
436+
437+ options ?. signal ?. addEventListener ( "abort" , ( ) => {
438+ if ( timeoutId !== undefined ) {
439+ clearTimeout ( timeoutId ) ;
440+ }
441+
442+ cancel ( options ?. signal ?. reason ) ;
416443 } ) ;
417444
418- this . _transport . send ( jsonrpcRequest ) . catch ( reject ) ;
445+ const timeout = options ?. timeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC ;
446+ timeoutId = setTimeout (
447+ ( ) =>
448+ cancel (
449+ new McpError ( ErrorCode . RequestTimeout , "Request timed out" , {
450+ timeout,
451+ } ) ,
452+ ) ,
453+ timeout ,
454+ ) ;
455+
456+ this . _transport . send ( jsonrpcRequest ) . catch ( ( error ) => {
457+ if ( timeoutId !== undefined ) {
458+ clearTimeout ( timeoutId ) ;
459+ }
460+
461+ reject ( error ) ;
462+ } ) ;
419463 } ) ;
420464 }
421465
0 commit comments