2828import io .modelcontextprotocol .util .Assert ;
2929import io .modelcontextprotocol .util .KeepAliveScheduler ;
3030import jakarta .servlet .AsyncContext ;
31+ import jakarta .servlet .AsyncEvent ;
32+ import jakarta .servlet .AsyncListener ;
3133import jakarta .servlet .ServletException ;
3234import jakarta .servlet .annotation .WebServlet ;
3335import jakarta .servlet .http .HttpServlet ;
@@ -329,6 +331,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
329331 // Check if this is a replay request
330332 if (request .getHeader (HttpHeaders .LAST_EVENT_ID ) != null ) {
331333 String lastId = request .getHeader (HttpHeaders .LAST_EVENT_ID );
334+ registerAsyncLifecycle (asyncContext , sessionId , sessionTransport ::close );
332335
333336 try {
334337 session .replay (lastId )
@@ -342,44 +345,21 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
342345 }
343346 catch (Exception e ) {
344347 logger .error ("Failed to replay message: {}" , e .getMessage ());
345- asyncContext . complete ();
348+ sessionTransport . close ();
346349 }
347350 });
348351 }
349352 catch (Exception e ) {
350353 logger .error ("Failed to replay messages: {}" , e .getMessage ());
351- asyncContext . complete ();
354+ sessionTransport . close ();
352355 }
353356 }
354357 else {
355358 // Establish new listening stream
356359 McpStreamableServerSession .McpStreamableServerSessionStream listeningStream = session
357360 .listeningStream (sessionTransport );
358361
359- asyncContext .addListener (new jakarta .servlet .AsyncListener () {
360- @ Override
361- public void onComplete (jakarta .servlet .AsyncEvent event ) throws IOException {
362- logger .debug ("SSE connection completed for session: {}" , sessionId );
363- listeningStream .close ();
364- }
365-
366- @ Override
367- public void onTimeout (jakarta .servlet .AsyncEvent event ) throws IOException {
368- logger .debug ("SSE connection timed out for session: {}" , sessionId );
369- listeningStream .close ();
370- }
371-
372- @ Override
373- public void onError (jakarta .servlet .AsyncEvent event ) throws IOException {
374- logger .debug ("SSE connection error for session: {}" , sessionId );
375- listeningStream .close ();
376- }
377-
378- @ Override
379- public void onStartAsync (jakarta .servlet .AsyncEvent event ) throws IOException {
380- // No action needed
381- }
382- });
362+ registerAsyncLifecycle (asyncContext , sessionId , listeningStream ::close );
383363 }
384364 }
385365 catch (Exception e ) {
@@ -530,6 +510,7 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
530510
531511 HttpServletStreamableMcpSessionTransport sessionTransport = new HttpServletStreamableMcpSessionTransport (
532512 sessionId , asyncContext , response .getWriter ());
513+ registerAsyncLifecycle (asyncContext , sessionId , sessionTransport ::close );
533514
534515 try {
535516 session .responseStream (jsonrpcRequest , sessionTransport )
@@ -538,7 +519,7 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
538519 }
539520 catch (Exception e ) {
540521 logger .error ("Failed to handle request stream: {}" , e .getMessage ());
541- asyncContext . complete ();
522+ sessionTransport . close ();
542523 }
543524 }
544525 else {
@@ -571,6 +552,32 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
571552 }
572553 }
573554
555+ private void registerAsyncLifecycle (AsyncContext asyncContext , String sessionId , Runnable onClose ) {
556+ asyncContext .addListener (new AsyncListener () {
557+ @ Override
558+ public void onComplete (AsyncEvent event ) throws IOException {
559+ logger .debug ("SSE async context completed for session: {}" , sessionId );
560+ onClose .run ();
561+ }
562+
563+ @ Override
564+ public void onTimeout (AsyncEvent event ) throws IOException {
565+ logger .debug ("SSE async context timed out for session: {}" , sessionId );
566+ onClose .run ();
567+ }
568+
569+ @ Override
570+ public void onError (AsyncEvent event ) throws IOException {
571+ logger .debug ("SSE async context errored for session: {}" , sessionId );
572+ onClose .run ();
573+ }
574+
575+ @ Override
576+ public void onStartAsync (AsyncEvent event ) throws IOException {
577+ }
578+ });
579+ }
580+
574581 /**
575582 * Handles DELETE requests for session deletion.
576583 * @param request The HTTP servlet request
@@ -761,8 +768,7 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message, String messageId
761768 }
762769 catch (Exception e ) {
763770 logger .error ("Failed to send message to session {}: {}" , this .sessionId , e .getMessage ());
764- HttpServletStreamableServerTransportProvider .this .sessions .remove (this .sessionId );
765- this .asyncContext .complete ();
771+ this .close ();
766772 }
767773 finally {
768774 lock .unlock ();
@@ -806,8 +812,6 @@ public void close() {
806812 }
807813
808814 this .closed = true ;
809-
810- // HttpServletStreamableServerTransportProvider.this.sessions.remove(this.sessionId);
811815 this .asyncContext .complete ();
812816 logger .debug ("Successfully completed async context for session {}" , sessionId );
813817 }
0 commit comments