@@ -235,7 +235,7 @@ public Object handleGet(HttpClassicServerRequest request, HttpClassicServerRespo
235235 // TODO emitter.onTimeout() logger.info()
236236
237237 DefaultStreamableMcpSessionTransport sessionTransport = new DefaultStreamableMcpSessionTransport (
238- sessionId , emitter );
238+ sessionId , emitter , response );
239239
240240 // Check if this is a replay request
241241 if (request .headers ().contains (HttpHeaders .LAST_EVENT_ID )) {
@@ -278,17 +278,23 @@ public void onEmittedData(TextEvent data) {
278278 @ Override
279279 public void onCompleted () {
280280 logger .info ("[SSE] Completed SSE emitting for session: {}" , sessionId );
281+ try {
282+ listeningStream .close ();
283+ } catch (Exception e ) {
284+ logger .warn ("[SSE] Error closing listeningStream on complete: {}" , e .getMessage ());
285+ }
281286 }
282287
283288 @ Override
284289 public void onFailed (Exception cause ) {
285290 logger .warn ("[SSE] SSE failed for session: {}, cause: {}" , sessionId , cause .getMessage ());
291+ try {
292+ listeningStream .close ();
293+ } catch (Exception e ) {
294+ logger .warn ("[SSE] Error closing listeningStream on failure: {}" , e .getMessage ());
295+ }
286296 }
287297 });
288-
289- // Add connection monitoring to detect client disconnection
290- // This is a workaround to ensure listeningStream.close() is called when client disconnects
291- startConnectionMonitoring (sessionId , listeningStream , response );
292298 }
293299 });
294300 }
@@ -299,43 +305,6 @@ public void onFailed(Exception cause) {
299305 }
300306 }
301307
302- /**
303- * Starts connection monitoring to detect client disconnection and ensure proper cleanup.
304- * This is a workaround to ensure listeningStream.close() is called when client disconnects.
305- *
306- * @param sessionId The session ID
307- * @param listeningStream The listening stream to close when connection is lost
308- * @param response The HTTP response to check for connection status
309- */
310- private void startConnectionMonitoring (String sessionId ,
311- McpStreamableServerSession .McpStreamableServerSessionStream listeningStream ,
312- HttpClassicServerResponse response ) {
313- // Use a separate thread to periodically check connection status
314- Thread monitoringThread = new Thread (() -> {
315- try {
316- while (!Thread .currentThread ().isInterrupted ()) {
317- Thread .sleep (1000 ); // Check every second
318-
319- // Check if the HTTP response is still active
320- if (!response .isActive ()) {
321- logger .info ("[SSE] Connection lost for session, completing emitter to trigger cleanup" );
322- listeningStream .close ();
323- break ;
324- }
325- }
326- } catch (InterruptedException e ) {
327- logger .debug ("[SSE] Connection monitoring interrupted for session" );
328- Thread .currentThread ().interrupt ();
329- } catch (Exception e ) {
330- logger .warn ("[SSE] Error in connection monitoring: {}" , e .getMessage ());
331- }
332- });
333-
334- monitoringThread .setDaemon (true );
335- monitoringThread .setName ("sse-connection-monitor-" + sessionId );
336- monitoringThread .start ();
337- }
338-
339308 /**
340309 * Handles POST requests for incoming JSON-RPC messages from clients.
341310 *
@@ -449,11 +418,11 @@ public void onCompleted() {
449418
450419 @ Override
451420 public void onFailed (Exception e ) {
452- // No action needed
421+ logger . warn ( "[SSE] SSE failed for session: {}, cause: {}" , sessionId , e . getMessage ());
453422 }
454423 });
455424
456- DefaultStreamableMcpSessionTransport sessionTransport = new DefaultStreamableMcpSessionTransport (sessionId , emitter );
425+ DefaultStreamableMcpSessionTransport sessionTransport = new DefaultStreamableMcpSessionTransport (sessionId , emitter , response );
457426
458427 try {
459428 session .responseStream (jsonrpcRequest , sessionTransport )
@@ -571,15 +540,20 @@ private class DefaultStreamableMcpSessionTransport implements McpStreamableServe
571540 private final ReentrantLock lock = new ReentrantLock ();
572541
573542 private volatile boolean closed = false ;
543+
544+ private final HttpClassicServerResponse response ;
574545
575546 /**
576547 * Creates a new session transport with the specified ID and SSE builder.
577548 *
578549 * @param sessionId The unique identifier for this session
550+ * @param emitter The emitter for sending events
551+ * @param response The HTTP response for checking connection status
579552 */
580- DefaultStreamableMcpSessionTransport (String sessionId , Emitter <TextEvent > emitter ) {
553+ DefaultStreamableMcpSessionTransport (String sessionId , Emitter <TextEvent > emitter , HttpClassicServerResponse response ) {
581554 this .sessionId = sessionId ;
582555 this .emitter = emitter ;
556+ this .response = response ;
583557 logger .info ("[SSE] Building SSE for session: {} " , sessionId );
584558 }
585559
@@ -616,6 +590,13 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message, String messageId
616590 logger .info ("Session {} was closed during message send attempt" , this .sessionId );
617591 return ;
618592 }
593+
594+ // Check if connection is still active before sending
595+ if (!this .response .isActive ()) {
596+ logger .warn ("[SSE] Connection inactive detected while sending message for session: {}" , this .sessionId );
597+ this .close ();
598+ return ;
599+ }
619600
620601 String jsonText = objectMapper .writeValueAsString (message );
621602 TextEvent textEvent = TextEvent .custom ()
0 commit comments