Skip to content

Commit ad87b3c

Browse files
author
Developer
committed
fix: Ensure stdio server exits when stdin reaches EOF
When the parent process dies or stdin is closed, the stdio server now properly detects EOF and shuts down gracefully instead of becoming an orphan process. Changes: - Explicitly close read_stream_writer on EOF to signal shutdown - Add test coverage for EOF handling and parent death simulation Fixes #2231
1 parent b33c811 commit ad87b3c

2 files changed

Lines changed: 45 additions & 0 deletions

File tree

src/mcp/server/stdio.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ async def stdin_reader():
6464

6565
session_message = SessionMessage(message)
6666
await read_stream_writer.send(session_message)
67+
# EOF reached - stdin closed, parent process likely died
68+
# Signal shutdown by closing the write stream
69+
await read_stream_writer.aclose()
6770
except anyio.ClosedResourceError: # pragma: no cover
6871
await anyio.lowlevel.checkpoint()
6972

tests/test_stdio_eof.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Test that stdio server exits when stdin reaches EOF."""
2+
import asyncio
3+
import sys
4+
from io import StringIO
5+
6+
import anyio
7+
import pytest
8+
9+
from mcp.server.stdio import stdio_server
10+
11+
12+
@pytest.mark.anyio
13+
async def test_stdio_server_exits_on_eof():
14+
"""Server should exit gracefully when stdin is closed (EOF)."""
15+
# Create a closed stdin (simulating parent death)
16+
closed_stdin = StringIO() # Empty, immediate EOF
17+
18+
# This should complete without hanging
19+
with anyio.move_on_after(5): # 5 second timeout
20+
async with stdio_server() as (read_stream, write_stream):
21+
# Try to read from stream - should get EOF quickly
22+
try:
23+
await read_stream.receive()
24+
except anyio.EndOfStream:
25+
pass # Expected - stream closed
26+
27+
# If we get here without timeout, test passes
28+
29+
30+
@pytest.mark.anyio
31+
async def test_stdio_server_parent_death_simulation():
32+
"""Simulate parent process death by closing stdin."""
33+
# Create pipes to simulate stdin/stdout
34+
stdin_reader, stdin_writer = anyio.create_memory_object_stream[str](10)
35+
36+
async with stdio_server() as (read_stream, write_stream):
37+
# Close the input to simulate parent death
38+
await stdin_writer.aclose()
39+
40+
# Server should detect EOF and exit gracefully
41+
with pytest.raises(anyio.EndOfStream):
42+
await asyncio.wait_for(read_stream.receive(), timeout=5.0)

0 commit comments

Comments
 (0)