2727import io .modelcontextprotocol .util .Assert ;
2828import io .modelcontextprotocol .util .KeepAliveScheduler ;
2929import jakarta .servlet .AsyncContext ;
30+ import jakarta .servlet .AsyncEvent ;
31+ import jakarta .servlet .AsyncListener ;
3032import jakarta .servlet .ServletException ;
3133import jakarta .servlet .annotation .WebServlet ;
3234import jakarta .servlet .http .HttpServlet ;
@@ -328,6 +330,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
328330 // Check if this is a replay request
329331 if (request .getHeader (HttpHeaders .LAST_EVENT_ID ) != null ) {
330332 String lastId = request .getHeader (HttpHeaders .LAST_EVENT_ID );
333+ registerAsyncLifecycle (asyncContext , sessionId , sessionTransport ::close );
331334
332335 try {
333336 session .replay (lastId )
@@ -341,44 +344,21 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
341344 }
342345 catch (Exception e ) {
343346 logger .error ("Failed to replay message: {}" , e .getMessage ());
344- asyncContext . complete ();
347+ sessionTransport . close ();
345348 }
346349 });
347350 }
348351 catch (Exception e ) {
349352 logger .error ("Failed to replay messages: {}" , e .getMessage ());
350- asyncContext . complete ();
353+ sessionTransport . close ();
351354 }
352355 }
353356 else {
354357 // Establish new listening stream
355358 McpStreamableServerSession .McpStreamableServerSessionStream listeningStream = session
356359 .listeningStream (sessionTransport );
357360
358- asyncContext .addListener (new jakarta .servlet .AsyncListener () {
359- @ Override
360- public void onComplete (jakarta .servlet .AsyncEvent event ) throws IOException {
361- logger .debug ("SSE connection completed for session: {}" , sessionId );
362- listeningStream .close ();
363- }
364-
365- @ Override
366- public void onTimeout (jakarta .servlet .AsyncEvent event ) throws IOException {
367- logger .debug ("SSE connection timed out for session: {}" , sessionId );
368- listeningStream .close ();
369- }
370-
371- @ Override
372- public void onError (jakarta .servlet .AsyncEvent event ) throws IOException {
373- logger .debug ("SSE connection error for session: {}" , sessionId );
374- listeningStream .close ();
375- }
376-
377- @ Override
378- public void onStartAsync (jakarta .servlet .AsyncEvent event ) throws IOException {
379- // No action needed
380- }
381- });
361+ registerAsyncLifecycle (asyncContext , sessionId , listeningStream ::close );
382362 }
383363 }
384364 catch (Exception e ) {
@@ -529,6 +509,7 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
529509
530510 HttpServletStreamableMcpSessionTransport sessionTransport = new HttpServletStreamableMcpSessionTransport (
531511 sessionId , asyncContext , response .getWriter ());
512+ registerAsyncLifecycle (asyncContext , sessionId , sessionTransport ::close );
532513
533514 try {
534515 session .responseStream (jsonrpcRequest , sessionTransport )
@@ -537,7 +518,7 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
537518 }
538519 catch (Exception e ) {
539520 logger .error ("Failed to handle request stream: {}" , e .getMessage ());
540- asyncContext . complete ();
521+ sessionTransport . close ();
541522 }
542523 }
543524 else {
@@ -570,6 +551,32 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
570551 }
571552 }
572553
554+ private void registerAsyncLifecycle (AsyncContext asyncContext , String sessionId , Runnable onClose ) {
555+ asyncContext .addListener (new AsyncListener () {
556+ @ Override
557+ public void onComplete (AsyncEvent event ) throws IOException {
558+ logger .debug ("SSE async context completed for session: {}" , sessionId );
559+ onClose .run ();
560+ }
561+
562+ @ Override
563+ public void onTimeout (AsyncEvent event ) throws IOException {
564+ logger .debug ("SSE async context timed out for session: {}" , sessionId );
565+ onClose .run ();
566+ }
567+
568+ @ Override
569+ public void onError (AsyncEvent event ) throws IOException {
570+ logger .debug ("SSE async context errored for session: {}" , sessionId );
571+ onClose .run ();
572+ }
573+
574+ @ Override
575+ public void onStartAsync (AsyncEvent event ) throws IOException {
576+ }
577+ });
578+ }
579+
573580 /**
574581 * Handles DELETE requests for session deletion.
575582 * @param request The HTTP servlet request
@@ -760,8 +767,7 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message, String messageId
760767 }
761768 catch (Exception e ) {
762769 logger .error ("Failed to send message to session {}: {}" , this .sessionId , e .getMessage ());
763- HttpServletStreamableServerTransportProvider .this .sessions .remove (this .sessionId );
764- this .asyncContext .complete ();
770+ this .close ();
765771 }
766772 finally {
767773 lock .unlock ();
@@ -805,8 +811,6 @@ public void close() {
805811 }
806812
807813 this .closed = true ;
808-
809- // HttpServletStreamableServerTransportProvider.this.sessions.remove(this.sessionId);
810814 this .asyncContext .complete ();
811815 logger .debug ("Successfully completed async context for session {}" , sessionId );
812816 }
0 commit comments