From 2e949f81ee24ef742981e7d788de58d4f8f48205 Mon Sep 17 00:00:00 2001 From: Elliot Williams Date: Fri, 29 May 2026 20:05:36 +0000 Subject: [PATCH] Fix stdio transport closing sys.stdin.buffer/sys.stdout.buffer on exit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using transport='stdio', anyio.wrap_file() wraps a TextIOWrapper around sys.stdin.buffer/sys.stdout.buffer. When the server exits and the async file is closed, TextIOWrapper.close() propagates to close the underlying buffer, making subsequent stdio operations fail with: ValueError: underlying buffer has been closed This adds a _DetachingTextIOWrapper that detaches from the buffer on close, preventing the propagation. The original comment in the code stated 'Purposely not using context managers for these, as we don't want to close standard process handles' — this fix aligns the implementation with that intent. Fixes #1933 --- src/mcp/server/stdio.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/mcp/server/stdio.py b/src/mcp/server/stdio.py index 5c1459dff6..0559cb0126 100644 --- a/src/mcp/server/stdio.py +++ b/src/mcp/server/stdio.py @@ -21,6 +21,17 @@ async def run_server(): from contextlib import asynccontextmanager from io import TextIOWrapper + +class _DetachingTextIOWrapper(TextIOWrapper): + """TextIOWrapper that detaches from its buffer on close, preventing + the underlying buffer (e.g. sys.stdin.buffer) from being closed.""" + + def close(self): + try: + self.detach() + except (ValueError, OSError): + pass + import anyio import anyio.lowlevel @@ -39,9 +50,9 @@ async def stdio_server(stdin: anyio.AsyncFile[str] | None = None, stdout: anyio. # python is platform-dependent (Windows is particularly problematic), so we # re-wrap the underlying binary stream to ensure UTF-8. if not stdin: - stdin = anyio.wrap_file(TextIOWrapper(sys.stdin.buffer, encoding="utf-8", errors="replace")) + stdin = anyio.wrap_file(_DetachingTextIOWrapper(sys.stdin.buffer, encoding="utf-8", errors="replace")) if not stdout: - stdout = anyio.wrap_file(TextIOWrapper(sys.stdout.buffer, encoding="utf-8")) + stdout = anyio.wrap_file(_DetachingTextIOWrapper(sys.stdout.buffer, encoding="utf-8")) read_stream_writer, read_stream = create_context_streams[SessionMessage | Exception](0) write_stream, write_stream_reader = create_context_streams[SessionMessage](0)