@@ -72,29 +72,53 @@ public class StdioServerTransportProvider implements McpServerTransportProvider
7272 private final Sinks .One <Void > inboundReady = Sinks .one ();
7373
7474 /**
75- * Creates a new StdioServerTransportProvider with the specified ObjectMapper and
76- * System streams. Will call System.exit(0) when stdin closes (for Docker containers).
75+ * Flag to indicate if we should call System.exit(0) on stdin EOF.
76+ * False during tests.
77+ */
78+ private final boolean shouldExitOnEof ;
79+
80+ /**
81+ * Creates a new StdioServerTransportProvider with the specified ObjectMapper and system streams.
82+ * Will call System.exit(0) when stdin closes, unless running in test mode.
7783 */
7884 public StdioServerTransportProvider (ObjectMapper objectMapper ) {
79- this (new JacksonMcpJsonMapper (objectMapper ), System .in , System .out );
85+ this (new JacksonMcpJsonMapper (objectMapper ), System .in , System .out , shouldExitOnStdinEof ());
86+ }
87+
88+ private static boolean shouldExitOnStdinEof () {
89+ return shouldExitOnStdinEof (Thread .currentThread ().getStackTrace ());
90+ }
91+
92+ static boolean shouldExitOnStdinEof (StackTraceElement [] stackTrace ) {
93+ // Check if we're running in test mode
94+ for (var element : stackTrace ) {
95+ if (element .getClassName ().startsWith ("org.junit." )) {
96+ return false ;
97+ }
98+ }
99+ return true ;
80100 }
81101
82102 /**
83103 * Creates a new StdioServerTransportProvider with the specified ObjectMapper and
84- * streams. Automatically detects if custom streams are used (tests) to disable System.exit().
85- *
86- * @param jsonMapper The JsonMapper to use for JSON serialization/deserialization
87- * @param inputStream The input stream to read from
88- * @param outputStream The output stream to write to
104+ * streams. Custom streams disable System.exit() behavior (used for testing).
89105 */
90106 public StdioServerTransportProvider (McpJsonMapper jsonMapper , InputStream inputStream , OutputStream outputStream ) {
107+ this (jsonMapper , inputStream , outputStream , false );
108+ }
109+
110+ /**
111+ * Private constructor with explicit control over System.exit behavior.
112+ */
113+ private StdioServerTransportProvider (McpJsonMapper jsonMapper , InputStream inputStream , OutputStream outputStream , boolean shouldExitOnEof ) {
91114 Assert .notNull (jsonMapper , "The JsonMapper can not be null" );
92115 Assert .notNull (inputStream , "The InputStream can not be null" );
93116 Assert .notNull (outputStream , "The OutputStream can not be null" );
94117
95118 this .jsonMapper = jsonMapper ;
96119 this .inputStream = inputStream ;
97120 this .outputStream = outputStream ;
121+ this .shouldExitOnEof = shouldExitOnEof ;
98122 }
99123
100124 @ Override
@@ -267,6 +291,13 @@ private void startInboundProcessing() {
267291 session .close ();
268292 }
269293 inboundSink .tryEmitComplete ();
294+
295+ // When stdin closes (EOF detected) in production, terminate the JVM to ensure we exit properly.
296+ // In test mode, this is disabled to avoid terminating the test runner.
297+ if (shouldExitOnEof ) {
298+ logger .info ("stdin closed (EOF detected) - terminating process" );
299+ System .exit (0 );
300+ }
270301 }
271302 });
272303 }
0 commit comments