@@ -181,29 +181,7 @@ async def stdin_writer():
181181 try:
182182 yield read_stream, write_stream
183183 finally:
184- # MCP spec: stdio shutdown sequence
185- # 1. Close input stream to server
186- # 2. Wait for server to exit, or send SIGTERM if it doesn't exit in time
187- # 3. Send SIGKILL if still not exited (forcibly kill on Windows)
188- if process.stdin:
189- try:
190- await process.stdin.aclose()
191- except Exception:
192- # stdin might already be closed, which is fine
193- pass
194-
195- try:
196- # Give the process time to exit gracefully after stdin closes
197- with anyio.fail_after(2.0):
198- await process.wait()
199- except TimeoutError:
200- # Process didn't exit from stdin closure, use our termination function
201- # that handles child processes properly
202- await _terminate_process_with_children(process)
203- except ProcessLookupError:
204- # Process already exited, which is fine
205- pass
206-
184+ await _shutdown_process(process)
207185 await read_stream.aclose()
208186 await write_stream.aclose()
209187 await read_stream_writer.aclose()
@@ -251,6 +229,35 @@ async def _create_platform_compatible_process(
251229 return process
252230
253231
232+ async def _shutdown_process(process: Process | FallbackProcess) -> None:
233+ """
234+ MCP spec: stdio shutdown sequence
235+ 1. Close input stream to server
236+ 2. Wait for server to exit, or send SIGTERM if it doesn't exit in time
237+ 3. Send SIGKILL if still not exited (forcibly kill on Windows)
238+ """
239+
240+ # Close input stream to server
241+ if process.stdin:
242+ try:
243+ await process.stdin.aclose()
244+ except Exception:
245+ # stdin might already be closed, which is fine
246+ pass
247+
248+ try:
249+ # Wait for server to exit gracefully after stdin closes
250+ with anyio.fail_after(2.0):
251+ await process.wait()
252+ except TimeoutError:
253+ # 2. send SIGTERM if it doesn't exit in time
254+ # 3. Send SIGKILL if still not exited (forcibly kill on Windows)
255+ await _terminate_process_with_children(process)
256+ except ProcessLookupError:
257+ # Process already exited, which is fine
258+ pass
259+
260+
254261async def _terminate_process_with_children(process: Process | FallbackProcess, timeout: float = 2.0) -> None:
255262 """
256263 Terminate a process and all its children using psutil.
0 commit comments