11import { Transport } from "../shared/transport.js" ;
2- import { isJSONRPCNotification , JSONRPCMessage , JSONRPCMessageSchema } from "../types.js" ;
2+ import { isJSONRPCNotification , isJSONRPCRequest , isJSONRPCResponse , JSONRPCMessage , JSONRPCMessageSchema } from "../types.js" ;
33import { auth , AuthResult , OAuthClientProvider , UnauthorizedError } from "./auth.js" ;
44import { EventSourceParserStream } from "eventsource-parser/stream" ;
55
@@ -28,6 +28,14 @@ export interface StartSSEOptions {
2828 * The ID of the last received event, used for resuming a disconnected stream
2929 */
3030 lastEventId ?: string ;
31+ /**
32+ * The callback function that is invoked when the last event ID changes
33+ */
34+ onLastEventIdUpdate ?: ( event : string ) => void
35+ /**
36+ * When reconnecting to a long-running SSE stream, we need to make sure that message id matches
37+ */
38+ replayMessageId ?: string | number ;
3139}
3240
3341/**
@@ -200,7 +208,7 @@ export class StreamableHTTPClientTransport implements Transport {
200208 ) ;
201209 }
202210
203- this . _handleSseStream ( response . body ) ;
211+ this . _handleSseStream ( response . body , options ) ;
204212 } catch ( error ) {
205213 this . onerror ?.( error as Error ) ;
206214 throw error ;
@@ -231,7 +239,7 @@ export class StreamableHTTPClientTransport implements Transport {
231239 * @param lastEventId The ID of the last received event for resumability
232240 * @param attemptCount Current reconnection attempt count for this specific stream
233241 */
234- private _scheduleReconnection ( lastEventId : string , attemptCount = 0 ) : void {
242+ private _scheduleReconnection ( options : StartSSEOptions , attemptCount = 0 ) : void {
235243 // Use provided options or default options
236244 const maxRetries = this . _reconnectionOptions . maxRetries ;
237245
@@ -247,18 +255,19 @@ export class StreamableHTTPClientTransport implements Transport {
247255 // Schedule the reconnection
248256 setTimeout ( ( ) => {
249257 // Use the last event ID to resume where we left off
250- this . _startOrAuthSse ( { lastEventId } ) . catch ( error => {
258+ this . _startOrAuthSse ( options ) . catch ( error => {
251259 this . onerror ?.( new Error ( `Failed to reconnect SSE stream: ${ error instanceof Error ? error . message : String ( error ) } ` ) ) ;
252260 // Schedule another attempt if this one failed, incrementing the attempt counter
253- this . _scheduleReconnection ( lastEventId , attemptCount + 1 ) ;
261+ this . _scheduleReconnection ( options , attemptCount + 1 ) ;
254262 } ) ;
255263 } , delay ) ;
256264 }
257265
258- private _handleSseStream ( stream : ReadableStream < Uint8Array > | null , onLastEventIdUpdate ?: ( event : string ) => void ) : void {
266+ private _handleSseStream ( stream : ReadableStream < Uint8Array > | null , options : StartSSEOptions ) : void {
259267 if ( ! stream ) {
260268 return ;
261269 }
270+ const { onLastEventIdUpdate, replayMessageId } = options ;
262271
263272 let lastEventId : string | undefined ;
264273 const processStream = async ( ) => {
@@ -287,6 +296,9 @@ export class StreamableHTTPClientTransport implements Transport {
287296 if ( ! event . event || event . event === "message" ) {
288297 try {
289298 const message = JSONRPCMessageSchema . parse ( JSON . parse ( event . data ) ) ;
299+ if ( replayMessageId !== undefined && isJSONRPCResponse ( message ) ) {
300+ message . id = replayMessageId ;
301+ }
290302 this . onmessage ?.( message ) ;
291303 } catch ( error ) {
292304 this . onerror ?.( error as Error ) ;
@@ -302,7 +314,7 @@ export class StreamableHTTPClientTransport implements Transport {
302314 // Use the exponential backoff reconnection strategy
303315 if ( lastEventId !== undefined ) {
304316 try {
305- this . _scheduleReconnection ( lastEventId , 0 ) ;
317+ this . _scheduleReconnection ( options , 0 ) ;
306318 }
307319 catch ( error ) {
308320 this . onerror ?.( new Error ( `Failed to reconnect: ${ error instanceof Error ? error . message : String ( error ) } ` ) ) ;
@@ -352,8 +364,9 @@ export class StreamableHTTPClientTransport implements Transport {
352364 const lastEventId = options ?. resumptionToken
353365 const onLastEventIdUpdate = options ?. onresumptiontoken ;
354366 if ( lastEventId ) {
367+
355368 // If we have at last event ID, we need to reconnect the SSE stream
356- this . _startOrAuthSse ( { lastEventId } ) . catch ( err => this . onerror ?.( err ) ) ;
369+ this . _startOrAuthSse ( { lastEventId, replayMessageId : isJSONRPCRequest ( message ) ? message . id : undefined } ) . catch ( err => this . onerror ?.( err ) ) ;
357370 return ;
358371 }
359372
@@ -418,7 +431,7 @@ export class StreamableHTTPClientTransport implements Transport {
418431 // Handle SSE stream responses for requests
419432 // We use the same handler as standalone streams, which now supports
420433 // reconnection with the last event ID
421- this . _handleSseStream ( response . body , onLastEventIdUpdate ) ;
434+ this . _handleSseStream ( response . body , { onLastEventIdUpdate } ) ;
422435 } else if ( contentType ?. includes ( "application/json" ) ) {
423436 // For non-streaming servers, we might get direct JSON responses
424437 const data = await response . json ( ) ;
0 commit comments