@@ -74,7 +74,7 @@ export class StreamableHttpRunner extends TransportRunnerBase {
7474 jsonrpc : "2.0" ,
7575 error : {
7676 code : JSON_RPC_ERROR_CODE_SESSION_ID_INVALID ,
77- message : ` session id is invalid` ,
77+ message : " session id is invalid" ,
7878 } ,
7979 } ) ;
8080 return ;
@@ -85,7 +85,7 @@ export class StreamableHttpRunner extends TransportRunnerBase {
8585 jsonrpc : "2.0" ,
8686 error : {
8787 code : JSON_RPC_ERROR_CODE_SESSION_NOT_FOUND ,
88- message : ` session not found` ,
88+ message : " session not found" ,
8989 } ,
9090 } ) ;
9191 return ;
@@ -114,12 +114,48 @@ export class StreamableHttpRunner extends TransportRunnerBase {
114114 }
115115
116116 const server = this . setupServer ( ) ;
117+ let keepAliveLoop : NodeJS . Timeout ;
117118 const transport = new StreamableHTTPServerTransport ( {
118119 sessionIdGenerator : ( ) : string => randomUUID ( ) . toString ( ) ,
119120 onsessioninitialized : ( sessionId ) : void => {
120121 server . session . logger . setAttribute ( "sessionId" , sessionId ) ;
121122
122123 this . sessionStore . setSession ( sessionId , transport , server . session . logger ) ;
124+
125+ let failedPings = 0 ;
126+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
127+ keepAliveLoop = setInterval ( async ( ) => {
128+ try {
129+ this . logger . debug ( {
130+ id : LogId . streamableHttpTransportKeepAlive ,
131+ context : "streamableHttpTransport" ,
132+ message : "Sending ping" ,
133+ } ) ;
134+
135+ await transport . send ( {
136+ jsonrpc : "2.0" ,
137+ method : "ping" ,
138+ } ) ;
139+ failedPings = 0 ;
140+ } catch ( err ) {
141+ try {
142+ failedPings ++ ;
143+ this . logger . warning ( {
144+ id : LogId . streamableHttpTransportKeepAliveFailure ,
145+ context : "streamableHttpTransport" ,
146+ message : `Error sending ping (attempt #${ failedPings } ): ${ err instanceof Error ? err . message : String ( err ) } ` ,
147+ } ) ;
148+
149+ if ( failedPings > 3 ) {
150+ clearInterval ( keepAliveLoop ) ;
151+ await transport . close ( ) ;
152+ }
153+ } catch {
154+ // Ignore the error of the transport close as there's nothing else
155+ // we can do at this point.
156+ }
157+ }
158+ } , 30_000 ) ;
123159 } ,
124160 onsessionclosed : async ( sessionId ) : Promise < void > => {
125161 try {
@@ -135,6 +171,8 @@ export class StreamableHttpRunner extends TransportRunnerBase {
135171 } ) ;
136172
137173 transport . onclose = ( ) : void => {
174+ clearInterval ( keepAliveLoop ) ;
175+
138176 server . close ( ) . catch ( ( error ) => {
139177 this . logger . error ( {
140178 id : LogId . streamableHttpTransportCloseFailure ,
0 commit comments