Skip to content

fix: dup file descriptors in stdio_server to avoid closing real stdin/stdout#2172

Open
giulio-leone wants to merge 2 commits intomodelcontextprotocol:mainfrom
giulio-leone:fix/stdio-close-real-stdin-stdout
Open

fix: dup file descriptors in stdio_server to avoid closing real stdin/stdout#2172
giulio-leone wants to merge 2 commits intomodelcontextprotocol:mainfrom
giulio-leone:fix/stdio-close-real-stdin-stdout

Conversation

@giulio-leone
Copy link

Problem

Running a server with transport="stdio" closes the real sys.stdin / sys.stdout when the server exits. Any subsequent stdio operation raises:

ValueError: I/O operation on closed file.

Root Cause

TextIOWrapper(sys.stdin.buffer) shares the underlying file descriptor with sys.stdin. When the TextIOWrapper is closed (or garbage-collected) after the server exits, it also closes sys.stdin.buffer, making any subsequent stdio operation fail.

Solution

Use os.dup() to duplicate the file descriptor before wrapping it in TextIOWrapper. Closing the wrapper then only closes the duplicate fd while leaving the original process handles intact.

# Before (broken)
stdin = anyio.wrap_file(TextIOWrapper(sys.stdin.buffer, encoding="utf-8"))

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

Testing

  • All 15 stdio-related tests pass across 2 consecutive runs
  • Verified manually that sys.stdin.fileno() remains valid after closing the duplicated wrapper

Fixes #1933

giulio-leone and others added 2 commits February 28, 2026 09:07
…/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 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