Skip to content

Fix stdio_server from closing process stdio handles#2142

Closed
aframedelta wants to merge 1 commit intomodelcontextprotocol:mainfrom
aframedelta:fix-1933-stdio-no-close
Closed

Fix stdio_server from closing process stdio handles#2142
aframedelta wants to merge 1 commit intomodelcontextprotocol:mainfrom
aframedelta:fix-1933-stdio-no-close

Conversation

@aframedelta
Copy link

Summary

  • prevent stdio_server() from closing underlying process stdio handles
  • add a regression test that verifies stdio remains usable after server shutdown

Why

Closes #1933.

stdio_server() wraps sys.stdin.buffer / sys.stdout.buffer in a TextIOWrapper. When the async wrapper is closed, the underlying process stdio can be closed too, causing subsequent print() calls to fail with ValueError: I/O operation on closed file.

Changes

  • introduce _NonClosingTextIOWrapper in mcp.server.stdio
  • use it for default process stdio wrapping
  • add test_stdio_server_does_not_close_process_stdio regression test

Validation

  • . .venv/bin/activate && pytest tests/server/test_stdio.py -q
    • 2 passed

@maxisbey
Copy link
Contributor

maxisbey commented Mar 5, 2026

Thanks for working on this — the _NonClosingTextIOWrapper approach does work and appreciate the test being in tests/server/test_stdio.py.

We're going to consolidate on #2040 (opened Feb 12) which uses os.dup(). Two reasons:

  1. Simpler — 1 line of substantive change vs a new subclass + helper.
  2. Avoids shared-buffer state — this approach still wraps the same sys.stdin.buffer object, so two TextIOWrappers share one BufferedReader. Bytes pulled into the MCP wrapper's decode buffer become invisible to sys.stdin after the server exits. os.dup() gives a fully independent fd chain.

Closing in favor of #2040.

AI Disclaimer

@maxisbey maxisbey closed this Mar 5, 2026
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

2 participants