Skip to content

fix: use os.dup() to prevent stdio transport from closing real stdin/stdout#2166

Open
giulio-leone wants to merge 2 commits intomodelcontextprotocol:mainfrom
giulio-leone:fix/issue-1933-stdio-closes-real-handles
Open

fix: use os.dup() to prevent stdio transport from closing real stdin/stdout#2166
giulio-leone wants to merge 2 commits intomodelcontextprotocol:mainfrom
giulio-leone:fix/issue-1933-stdio-closes-real-handles

Conversation

@giulio-leone
Copy link

Summary

Running an MCP server with transport="stdio" causes subsequent stdio operations to fail with ValueError: I/O operation on closed file after the server exits.

Root Cause

In stdio.py, TextIOWrapper(sys.stdin.buffer) wraps the real stdin buffer directly. When the wrapper is garbage-collected or closed, it also closes sys.stdin.buffer, making sys.stdin unusable.

Fix

Use os.dup() to duplicate the file descriptor before wrapping:

stdin = anyio.wrap_file(
    TextIOWrapper(os.fdopen(os.dup(sys.stdin.fileno()), "rb"), encoding="utf-8")
)

This creates an independent file descriptor that can be safely closed without affecting the original sys.stdin/sys.stdout.

Verification

  • All stdio tests pass
  • Manual verification: print() works after server exit

Fixes #1933

giulio-leone and others added 2 commits February 28, 2026 05:15
…stdout

When stdio_server() wraps sys.stdin.buffer / sys.stdout.buffer in
TextIOWrapper, closing or GC'ing the wrapper also closes the
underlying buffer, making sys.stdin/sys.stdout unusable after
the server exits.

Use os.dup() to duplicate the file descriptors before wrapping,
so the wrappers own independent descriptors that can be safely
closed without affecting the original stdio handles.

Fixes modelcontextprotocol#1933

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Using transport="stdio" closes real stdio, causing ValueError after server exits

1 participant