Skip to content

Fix #1933: Prevent closing real stdio when server exits#2391

Open
717986230 wants to merge 2 commits intomodelcontextprotocol:mainfrom
717986230:main
Open

Fix #1933: Prevent closing real stdio when server exits#2391
717986230 wants to merge 2 commits intomodelcontextprotocol:mainfrom
717986230:main

Conversation

@717986230
Copy link
Copy Markdown

Fixes #1933

Problem

When using transport="stdio", the server closes sys.stdin.buffer and sys.stdout.buffer when exiting, causing subsequent stdio operations to fail with:

ValueError: I/O operation on closed file.

Root Cause

The stdio_server() function wraps sys.stdin.buffer and sys.stdout.buffer directly. When these wrappers are closed (when the context manager exits), they also close the original system streams.

Solution

Use os.dup() to create duplicate file descriptors before wrapping:

  • Duplicate sys.stdin.fileno() and sys.stdout.fileno()
  • Open new file handles from the duplicated descriptors
  • Wrap these duplicated handles instead of the originals

This ensures the original stdin/stdout remain open when the wrapper streams are closed.

Testing

Tested with the reproduction case from the issue:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Demo")
mcp.run(transport="stdio")
print("?")  # This now works without error

Pressing Ctrl+D to exit the server no longer causes "I/O operation on closed file" error.


This fix implements the proposed solution from the original issue.

… exits

When using transport="stdio", the server was closing sys.stdin.buffer
and sys.stdout.buffer when exiting, causing subsequent stdio operations
to fail with ValueError: I/O operation on closed file.

This fix uses os.dup() to create duplicate file descriptors, so the
original stdin/stdout remain open when the wrapper streams are closed.

Fixes modelcontextprotocol#1933
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