@@ -12,7 +12,12 @@ import {
1212 McpServer ,
1313 ResourceTemplate
1414} from '@modelcontextprotocol/sdk/server/mcp.js' ;
15- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' ;
15+ import {
16+ StreamableHTTPServerTransport ,
17+ EventStore ,
18+ EventId ,
19+ StreamId
20+ } from '@modelcontextprotocol/sdk/server/streamableHttp.js' ;
1621import {
1722 ElicitResultSchema ,
1823 ListToolsRequestSchema ,
@@ -33,6 +38,41 @@ const watchedResourceContent = 'Watched resource content';
3338const transports : { [ sessionId : string ] : StreamableHTTPServerTransport } = { } ;
3439const servers : { [ sessionId : string ] : McpServer } = { } ;
3540
41+ // In-memory event store for SEP-1699 resumability
42+ const eventStoreData = new Map <
43+ string ,
44+ { eventId : string ; message: any ; streamId: string }
45+ > ( ) ;
46+
47+ function createEventStore ( ) : EventStore {
48+ return {
49+ async storeEvent ( streamId : StreamId , message : any ) : Promise < EventId > {
50+ const eventId = `${ streamId } ::${ Date . now ( ) } _${ randomUUID ( ) } ` ;
51+ eventStoreData . set ( eventId , { eventId, message, streamId } ) ;
52+ return eventId ;
53+ } ,
54+ async replayEventsAfter (
55+ lastEventId : EventId ,
56+ { send } : { send : ( eventId : EventId , message : any ) = > Promise < void > }
57+ ) : Promise < StreamId > {
58+ const streamId = lastEventId . split ( '::' ) [ 0 ] ;
59+ const eventsToReplay : Array < [ string , { message : any } ] > = [ ] ;
60+ for ( const [ eventId , data ] of eventStoreData . entries ( ) ) {
61+ if ( data . streamId === streamId && eventId > lastEventId ) {
62+ eventsToReplay . push ( [ eventId , data ] ) ;
63+ }
64+ }
65+ eventsToReplay . sort ( ( [ a ] , [ b ] ) => a . localeCompare ( b ) ) ;
66+ for ( const [ eventId , { message } ] of eventsToReplay ) {
67+ if ( Object . keys ( message ) . length > 0 ) {
68+ await send ( eventId , message ) ;
69+ }
70+ }
71+ return streamId ;
72+ }
73+ } ;
74+ }
75+
3676// Sample base64 encoded 1x1 red PNG pixel for testing
3777const TEST_IMAGE_BASE64 =
3878 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg == ';
@@ -309,6 +349,46 @@ function createMcpServer() {
309349 }
310350 ) ;
311351
352+ // SEP-1699: Reconnection test tool - closes SSE stream mid-call to test client reconnection
353+ mcpServer . registerTool (
354+ 'test_reconnection' ,
355+ {
356+ description :
357+ 'Tests SSE stream disconnection and client reconnection (SEP-1699). Server will close the stream mid-call and send the result after client reconnects.' ,
358+ inputSchema : { }
359+ } ,
360+ async ( _args , { sessionId, requestId } ) => {
361+ const sleep = ( ms : number ) =>
362+ new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
363+
364+ console . log ( `[${ sessionId } ] Starting test_reconnection tool...` ) ;
365+
366+ // Get the transport for this session
367+ const transport = sessionId ? transports [ sessionId ] : undefined ;
368+ if ( transport && requestId ) {
369+ // Close the SSE stream to trigger client reconnection
370+ console . log (
371+ `[${ sessionId } ] Closing SSE stream to trigger client polling...`
372+ ) ;
373+ transport . closeSSEStream ( requestId ) ;
374+ }
375+
376+ // Wait for client to reconnect (should respect retry field)
377+ await sleep ( 100 ) ;
378+
379+ console . log ( `[${ sessionId } ] test_reconnection tool complete` ) ;
380+
381+ return {
382+ content : [
383+ {
384+ type : 'text' ,
385+ text : 'Reconnection test completed successfully. If you received this, the client properly reconnected after stream closure.'
386+ }
387+ ]
388+ } ;
389+ }
390+ ) ;
391+
312392 // Sampling tool - requests LLM completion from client
313393 mcpServer . registerTool (
314394 'test_sampling' ,
@@ -1006,6 +1086,8 @@ app.post('/mcp', async (req, res) => {
10061086
10071087 transport = new StreamableHTTPServerTransport ( {
10081088 sessionIdGenerator : ( ) => randomUUID ( ) ,
1089+ eventStore : createEventStore ( ) ,
1090+ retryInterval : 5000 , // 5 second retry interval for SEP-1699
10091091 onsessioninitialized : ( newSessionId ) => {
10101092 transports [ newSessionId ] = transport ;
10111093 servers [ newSessionId ] = mcpServer ;
0 commit comments