Skip to content

Commit 053d904

Browse files
author
g97iulio1609
committed
fix: dup file descriptors in stdio_server to avoid closing real stdin/stdout
TextIOWrapper(sys.stdin.buffer) shares the underlying fd with sys.stdin. When the wrapper is closed (or garbage-collected) after the server exits, it also closes sys.stdin.buffer, making any subsequent stdio operation raise `ValueError: I/O operation on closed file`. Use `os.dup()` to duplicate the fd before wrapping, so closing the wrapper only closes the duplicate while leaving the original process handles intact. Fixes #1933
1 parent 62575ed commit 053d904

1 file changed

Lines changed: 11 additions & 6 deletions

File tree

src/mcp/server/stdio.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ async def run_server():
1717
```
1818
"""
1919

20+
import os
2021
import sys
2122
from contextlib import asynccontextmanager
2223
from io import TextIOWrapper
@@ -34,14 +35,18 @@ async def stdio_server(stdin: anyio.AsyncFile[str] | None = None, stdout: anyio.
3435
"""Server transport for stdio: this communicates with an MCP client by reading
3536
from the current process' stdin and writing to stdout.
3637
"""
37-
# Purposely not using context managers for these, as we don't want to close
38-
# standard process handles. Encoding of stdin/stdout as text streams on
39-
# python is platform-dependent (Windows is particularly problematic), so we
40-
# re-wrap the underlying binary stream to ensure UTF-8.
38+
# Duplicate the file descriptors so that closing the TextIOWrapper does not
39+
# close the real sys.stdin / sys.stdout. Without this, any code that runs
40+
# after the server exits (or after the transport is torn down) gets
41+
# ``ValueError: I/O operation on closed file`` when touching stdio.
4142
if not stdin:
42-
stdin = anyio.wrap_file(TextIOWrapper(sys.stdin.buffer, encoding="utf-8"))
43+
stdin = anyio.wrap_file(
44+
TextIOWrapper(os.fdopen(os.dup(sys.stdin.fileno()), "rb"), encoding="utf-8")
45+
)
4346
if not stdout:
44-
stdout = anyio.wrap_file(TextIOWrapper(sys.stdout.buffer, encoding="utf-8"))
47+
stdout = anyio.wrap_file(
48+
TextIOWrapper(os.fdopen(os.dup(sys.stdout.fileno()), "wb"), encoding="utf-8")
49+
)
4550

4651
read_stream: MemoryObjectReceiveStream[SessionMessage | Exception]
4752
read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception]

0 commit comments

Comments
 (0)