Skip to content

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

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

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

g97iulio1609 added 2 commits February 28, 2026 15:41
…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
@giulio-leone
Copy link
Author

Closing in favor of #2172 which has a more comprehensive fix.

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