Skip to content

Fix stdio transport closing sys.stdin.buffer/sys.stdout.buffer on exit#2722

Open
TheChyeahhh wants to merge 1 commit into
modelcontextprotocol:mainfrom
TheChyeahhh:fix/stdio-close-propagation
Open

Fix stdio transport closing sys.stdin.buffer/sys.stdout.buffer on exit#2722
TheChyeahhh wants to merge 1 commit into
modelcontextprotocol:mainfrom
TheChyeahhh:fix/stdio-close-propagation

Conversation

@TheChyeahhh
Copy link
Copy Markdown

Description

Fixes #1933

When using transport="stdio", anyio.wrap_file() wraps a TextIOWrapper around sys.stdin.buffer/sys.stdout.buffer. When the server exits, the async file close propagates through TextIOWrapper.close() and closes the underlying buffer, making subsequent stdio operations fail with ValueError: underlying buffer has been closed.

Root Cause

The code comment at line 37-38 says:

Purposely not using context managers for these, as we dont want to close standard process handles.

But anyio.wrap_file() creates a context manager that calls close() on the TextIOWrapper, which in turn closes the underlying sys.stdin.buffer — exactly what the comment says should not happen.

Fix

Added _DetachingTextIOWrapper — a thin subclass of TextIOWrapper that overrides close() to detach the underlying buffer first, preventing close propagation. The buffer survives, and subsequent stdio operations work as expected after the server exits.

Changes

src/mcp/server/stdio.py — +13/-2 lines

When using transport='stdio', anyio.wrap_file() wraps a TextIOWrapper
around sys.stdin.buffer/sys.stdout.buffer. When the server exits and the
async file is closed, TextIOWrapper.close() propagates to close the
underlying buffer, making subsequent stdio operations fail with:
  ValueError: underlying buffer has been closed

This adds a _DetachingTextIOWrapper that detaches from the buffer on
close, preventing the propagation. The original comment in the code
stated 'Purposely not using context managers for these, as we don't want
to close standard process handles' — this fix aligns the implementation
with that intent.

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