1- import { log } from "node:console" ;
21import { Transport } from "../shared/transport.js" ;
32import { isJSONRPCNotification , JSONRPCMessage , JSONRPCMessageSchema } from "../types.js" ;
43import { auth , AuthResult , OAuthClientProvider , UnauthorizedError } from "./auth.js" ;
54import { EventSourceParserStream } from "eventsource-parser/stream" ;
65
6+ // Default reconnection options for StreamableHTTP connections
7+ const DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS : StreamableHTTPReconnectionOptions = {
8+ initialReconnectionDelay : 1000 ,
9+ maxReconnectionDelay : 30000 ,
10+ reconnectionDelayGrowFactor : 1.5 ,
11+ maxRetries : 2 ,
12+ } ;
13+
714export class StreamableHTTPError extends Error {
815 constructor (
916 public readonly code : number | undefined ,
@@ -13,6 +20,16 @@ export class StreamableHTTPError extends Error {
1320 }
1421}
1522
23+ /**
24+ * Options for starting or authenticating an SSE connection
25+ */
26+ export interface StartSSEOptions {
27+ /**
28+ * The ID of the last received event, used for resuming a disconnected stream
29+ */
30+ lastEventId ?: string ;
31+ }
32+
1633/**
1734 * Configuration options for reconnection behavior of the StreamableHTTPClientTransport.
1835 */
@@ -37,7 +54,7 @@ export interface StreamableHTTPReconnectionOptions {
3754
3855 /**
3956 * Maximum number of reconnection attempts before giving up.
40- * Default is 0 (unlimited) .
57+ * Default is 2 .
4158 */
4259 maxRetries : number ;
4360}
@@ -102,8 +119,8 @@ export class StreamableHTTPClientTransport implements Transport {
102119 this . _url = url ;
103120 this . _requestInit = opts ?. requestInit ;
104121 this . _authProvider = opts ?. authProvider ;
105- this . _reconnectionOptions = opts ?. reconnectionOptions || this . _defaultReconnectionOptions ;
106122 this . _sessionId = opts ?. sessionId ;
123+ this . _reconnectionOptions = opts ?. reconnectionOptions ?? DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS ;
107124 }
108125
109126 private async _authThenStart ( ) : Promise < void > {
@@ -123,7 +140,7 @@ export class StreamableHTTPClientTransport implements Transport {
123140 throw new UnauthorizedError ( ) ;
124141 }
125142
126- return await this . _startOrAuthSse ( ) ;
143+ return await this . _startOrAuthSse ( { lastEventId : undefined } ) ;
127144 }
128145
129146 private async _commonHeaders ( ) : Promise < Headers > {
@@ -144,7 +161,9 @@ export class StreamableHTTPClientTransport implements Transport {
144161 ) ;
145162 }
146163
147- private async _startOrAuthSse ( lastEventId ?: string ) : Promise < void > {
164+
165+ private async _startOrAuthSse ( options : StartSSEOptions ) : Promise < void > {
166+ const { lastEventId } = options ;
148167 try {
149168 // Try to open an initial SSE stream with GET to listen for server messages
150169 // This is optional according to the spec - server may not support it
@@ -187,19 +206,9 @@ export class StreamableHTTPClientTransport implements Transport {
187206 }
188207 }
189208
190- // Default reconnection options
191- private readonly _defaultReconnectionOptions : StreamableHTTPReconnectionOptions = {
192- initialReconnectionDelay : 1000 ,
193- maxReconnectionDelay : 30000 ,
194- reconnectionDelayGrowFactor : 1.5 ,
195- maxRetries : 2 ,
196- } ;
197-
198- // We no longer need global reconnection state as it will be maintained per stream
199209
200210 /**
201- * Calculates the next reconnection delay using exponential backoff algorithm
202- * with jitter for more effective reconnections in high load scenarios.
211+ * Calculates the next reconnection delay using backoff algorithm
203212 *
204213 * @param attempt Current reconnection attempt count for the specific stream
205214 * @returns Time to wait in milliseconds before next reconnection attempt
@@ -233,12 +242,11 @@ export class StreamableHTTPClientTransport implements Transport {
233242
234243 // Calculate next delay based on current attempt count
235244 const delay = this . _getNextReconnectionDelay ( attemptCount ) ;
236- log ( `Reconnection attempt ${ attemptCount + 1 } in ${ delay } ms...` ) ;
237245
238246 // Schedule the reconnection
239247 setTimeout ( ( ) => {
240248 // Use the last event ID to resume where we left off
241- this . _startOrAuthSse ( lastEventId ) . catch ( error => {
249+ this . _startOrAuthSse ( { lastEventId } ) . catch ( error => {
242250 this . onerror ?.( new Error ( `Failed to reconnect SSE stream: ${ error instanceof Error ? error . message : String ( error ) } ` ) ) ;
243251 // Schedule another attempt if this one failed, incrementing the attempt counter
244252 this . _scheduleReconnection ( lastEventId , attemptCount + 1 ) ;
@@ -253,7 +261,7 @@ export class StreamableHTTPClientTransport implements Transport {
253261
254262 let lastEventId : string | undefined ;
255263 const processStream = async ( ) => {
256- // this is the closest we can get to trying to cath network errors
264+ // this is the closest we can get to trying to catch network errors
257265 // if something happens reader will throw
258266 try {
259267 // Create a pipeline: binary stream -> text decoder -> SSE parser
@@ -286,7 +294,7 @@ export class StreamableHTTPClientTransport implements Transport {
286294 }
287295 } catch ( error ) {
288296 // Handle stream errors - likely a network disconnect
289- this . onerror ?.( new Error ( `SSE stream disconnected: ${ error instanceof Error ? error . message : String ( error ) } ` ) ) ;
297+ this . onerror ?.( new Error ( `SSE stream disconnected: ${ error } ` ) ) ;
290298
291299 // Attempt to reconnect if the stream disconnects unexpectedly and we aren't closing
292300 if ( this . _abortController && ! this . _abortController . signal . aborted ) {
@@ -343,7 +351,7 @@ export class StreamableHTTPClientTransport implements Transport {
343351 const { lastEventId, onLastEventIdUpdate } = options ?? { } ;
344352 if ( lastEventId ) {
345353 // If we have at last event ID, we need to reconnect the SSE stream
346- this . _startOrAuthSse ( lastEventId ) . catch ( err => this . onerror ?.( err ) ) ;
354+ this . _startOrAuthSse ( { lastEventId } ) . catch ( err => this . onerror ?.( err ) ) ;
347355 return ;
348356 }
349357
@@ -390,7 +398,7 @@ export class StreamableHTTPClientTransport implements Transport {
390398 // if it's supported by the server
391399 if ( isJSONRPCNotification ( message ) && message . method === "notifications/initialized" ) {
392400 // Start without a lastEventId since this is a fresh connection
393- this . _startOrAuthSse ( ) . catch ( err => this . onerror ?.( err ) ) ;
401+ this . _startOrAuthSse ( { lastEventId : undefined } ) . catch ( err => this . onerror ?.( err ) ) ;
394402 }
395403 return ;
396404 }
0 commit comments