@@ -20,7 +20,7 @@ async def run_server():
2020import os
2121import sys
2222from contextlib import asynccontextmanager
23- from io import TextIOWrapper
23+ from io import TextIOWrapper , UnsupportedOperation
2424
2525import anyio
2626import anyio .lowlevel
@@ -30,6 +30,33 @@ async def run_server():
3030from mcp .shared .message import SessionMessage
3131
3232
33+ def _wrap_stdin () -> tuple [anyio .AsyncFile [str ], bool ]:
34+ """Wrap stdin as UTF-8 text without closing process stdio on exit."""
35+ try :
36+ stdin_fd = os .dup (sys .stdin .fileno ())
37+ except (AttributeError , OSError , UnsupportedOperation ):
38+ # Some tests and embedders replace sys.stdin with fileno-less in-memory
39+ # streams. Keep supporting that shape by falling back to the existing
40+ # buffer-wrapping behavior.
41+ return anyio .wrap_file (TextIOWrapper (sys .stdin .buffer , encoding = "utf-8" , errors = "replace" )), False
42+
43+ stdin_buffer = os .fdopen (stdin_fd , "rb" , closefd = True )
44+ return anyio .wrap_file (TextIOWrapper (stdin_buffer , encoding = "utf-8" , errors = "replace" )), True
45+
46+
47+ def _wrap_stdout () -> tuple [anyio .AsyncFile [str ], bool ]:
48+ """Wrap stdout as UTF-8 text without closing process stdio on exit."""
49+ try :
50+ stdout_fd = os .dup (sys .stdout .fileno ())
51+ except (AttributeError , OSError , UnsupportedOperation ):
52+ # Match the fileno-less stdin fallback for in-memory test streams and
53+ # embedders that provide file-like stdout objects.
54+ return anyio .wrap_file (TextIOWrapper (sys .stdout .buffer , encoding = "utf-8" )), False
55+
56+ stdout_buffer = os .fdopen (stdout_fd , "wb" , closefd = True )
57+ return anyio .wrap_file (TextIOWrapper (stdout_buffer , encoding = "utf-8" )), True
58+
59+
3360@asynccontextmanager
3461async def stdio_server (stdin : anyio .AsyncFile [str ] | None = None , stdout : anyio .AsyncFile [str ] | None = None ):
3562 """Server transport for stdio: this communicates with an MCP client by reading
@@ -42,15 +69,9 @@ async def stdio_server(stdin: anyio.AsyncFile[str] | None = None, stdout: anyio.
4269 close_stdin = False
4370 close_stdout = False
4471 if not stdin :
45- stdin_fd = os .dup (sys .stdin .fileno ())
46- stdin_buffer = os .fdopen (stdin_fd , "rb" , closefd = True )
47- stdin = anyio .wrap_file (TextIOWrapper (stdin_buffer , encoding = "utf-8" , errors = "replace" ))
48- close_stdin = True
72+ stdin , close_stdin = _wrap_stdin ()
4973 if not stdout :
50- stdout_fd = os .dup (sys .stdout .fileno ())
51- stdout_buffer = os .fdopen (stdout_fd , "wb" , closefd = True )
52- stdout = anyio .wrap_file (TextIOWrapper (stdout_buffer , encoding = "utf-8" ))
53- close_stdout = True
74+ stdout , close_stdout = _wrap_stdout ()
5475
5576 read_stream_writer , read_stream = create_context_streams [SessionMessage | Exception ](0 )
5677 write_stream , write_stream_reader = create_context_streams [SessionMessage ](0 )
0 commit comments