1
- import { log } from "node:console" ;
2
1
import { Transport } from "../shared/transport.js" ;
3
2
import { isJSONRPCNotification , JSONRPCMessage , JSONRPCMessageSchema } from "../types.js" ;
4
3
import { auth , AuthResult , OAuthClientProvider , UnauthorizedError } from "./auth.js" ;
5
4
import { EventSourceParserStream } from "eventsource-parser/stream" ;
6
5
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
+
7
14
export class StreamableHTTPError extends Error {
8
15
constructor (
9
16
public readonly code : number | undefined ,
@@ -13,6 +20,16 @@ export class StreamableHTTPError extends Error {
13
20
}
14
21
}
15
22
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
+
16
33
/**
17
34
* Configuration options for reconnection behavior of the StreamableHTTPClientTransport.
18
35
*/
@@ -37,7 +54,7 @@ export interface StreamableHTTPReconnectionOptions {
37
54
38
55
/**
39
56
* Maximum number of reconnection attempts before giving up.
40
- * Default is 0 (unlimited) .
57
+ * Default is 2 .
41
58
*/
42
59
maxRetries : number ;
43
60
}
@@ -102,8 +119,8 @@ export class StreamableHTTPClientTransport implements Transport {
102
119
this . _url = url ;
103
120
this . _requestInit = opts ?. requestInit ;
104
121
this . _authProvider = opts ?. authProvider ;
105
- this . _reconnectionOptions = opts ?. reconnectionOptions || this . _defaultReconnectionOptions ;
106
122
this . _sessionId = opts ?. sessionId ;
123
+ this . _reconnectionOptions = opts ?. reconnectionOptions ?? DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS ;
107
124
}
108
125
109
126
private async _authThenStart ( ) : Promise < void > {
@@ -123,7 +140,7 @@ export class StreamableHTTPClientTransport implements Transport {
123
140
throw new UnauthorizedError ( ) ;
124
141
}
125
142
126
- return await this . _startOrAuthSse ( ) ;
143
+ return await this . _startOrAuthSse ( { lastEventId : undefined } ) ;
127
144
}
128
145
129
146
private async _commonHeaders ( ) : Promise < Headers > {
@@ -144,7 +161,9 @@ export class StreamableHTTPClientTransport implements Transport {
144
161
) ;
145
162
}
146
163
147
- private async _startOrAuthSse ( lastEventId ?: string ) : Promise < void > {
164
+
165
+ private async _startOrAuthSse ( options : StartSSEOptions ) : Promise < void > {
166
+ const { lastEventId } = options ;
148
167
try {
149
168
// Try to open an initial SSE stream with GET to listen for server messages
150
169
// This is optional according to the spec - server may not support it
@@ -187,19 +206,9 @@ export class StreamableHTTPClientTransport implements Transport {
187
206
}
188
207
}
189
208
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
199
209
200
210
/**
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
203
212
*
204
213
* @param attempt Current reconnection attempt count for the specific stream
205
214
* @returns Time to wait in milliseconds before next reconnection attempt
@@ -233,12 +242,11 @@ export class StreamableHTTPClientTransport implements Transport {
233
242
234
243
// Calculate next delay based on current attempt count
235
244
const delay = this . _getNextReconnectionDelay ( attemptCount ) ;
236
- log ( `Reconnection attempt ${ attemptCount + 1 } in ${ delay } ms...` ) ;
237
245
238
246
// Schedule the reconnection
239
247
setTimeout ( ( ) => {
240
248
// Use the last event ID to resume where we left off
241
- this . _startOrAuthSse ( lastEventId ) . catch ( error => {
249
+ this . _startOrAuthSse ( { lastEventId } ) . catch ( error => {
242
250
this . onerror ?.( new Error ( `Failed to reconnect SSE stream: ${ error instanceof Error ? error . message : String ( error ) } ` ) ) ;
243
251
// Schedule another attempt if this one failed, incrementing the attempt counter
244
252
this . _scheduleReconnection ( lastEventId , attemptCount + 1 ) ;
@@ -253,7 +261,7 @@ export class StreamableHTTPClientTransport implements Transport {
253
261
254
262
let lastEventId : string | undefined ;
255
263
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
257
265
// if something happens reader will throw
258
266
try {
259
267
// Create a pipeline: binary stream -> text decoder -> SSE parser
@@ -286,7 +294,7 @@ export class StreamableHTTPClientTransport implements Transport {
286
294
}
287
295
} catch ( error ) {
288
296
// 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 } ` ) ) ;
290
298
291
299
// Attempt to reconnect if the stream disconnects unexpectedly and we aren't closing
292
300
if ( this . _abortController && ! this . _abortController . signal . aborted ) {
@@ -343,7 +351,7 @@ export class StreamableHTTPClientTransport implements Transport {
343
351
const { lastEventId, onLastEventIdUpdate } = options ?? { } ;
344
352
if ( lastEventId ) {
345
353
// 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 ) ) ;
347
355
return ;
348
356
}
349
357
@@ -390,7 +398,7 @@ export class StreamableHTTPClientTransport implements Transport {
390
398
// if it's supported by the server
391
399
if ( isJSONRPCNotification ( message ) && message . method === "notifications/initialized" ) {
392
400
// 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 ) ) ;
394
402
}
395
403
return ;
396
404
}
0 commit comments