Skip to content

Commit cbaee2e

Browse files
Extract MCP stdio shutdown sequence to dedicated function
Refactor the MCP spec-compliant stdio shutdown sequence into a separate _shutdown_process function to document that this is an MCP specific shutdown sequence. Logic remains unchanged.
1 parent 828e980 commit cbaee2e

File tree

1 file changed

+30
-23
lines changed

1 file changed

+30
-23
lines changed

src/mcp/client/stdio/__init__.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
254261
async 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

Comments
 (0)