fix: preserve real stdin/stdout after stdio server exits#2323
Open
owendevereaux wants to merge 1 commit intomodelcontextprotocol:mainfrom
Open
fix: preserve real stdin/stdout after stdio server exits#2323owendevereaux wants to merge 1 commit intomodelcontextprotocol:mainfrom
owendevereaux wants to merge 1 commit intomodelcontextprotocol:mainfrom
Conversation
When running the MCP server with transport='stdio', closing the server would also close the process's real stdin/stdout handles. This caused ValueError when trying to use stdio after the server exits. The issue was that wrapping sys.stdin.buffer/sys.stdout.buffer with TextIOWrapper causes the underlying buffer to be closed when the wrapper is garbage collected, even if we don't use a context manager. Fix: Use os.dup() to duplicate the file descriptors before wrapping. When the duplicated descriptors are closed, the original stdin/stdout remain intact and usable. Fallback: For streams without a fileno() (e.g., BytesIO in tests), we fall back to wrapping them directly (previous behavior). Fixes modelcontextprotocol#1933
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1933
When running the MCP server with
transport='stdio', closing the server would also close the process's real stdin/stdout handles. This caused aValueError: I/O operation on closed filewhen trying to use stdio after the server exits.Problem
The issue was that wrapping
sys.stdin.buffer/sys.stdout.bufferwithTextIOWrappercauses the underlying buffer to be closed when the wrapper is garbage collected, even if we don't use a context manager. The old comment said "Purposely not using context managers for these, as we don't want to close standard process handles" but this didn't prevent the closing.Solution
Use
os.dup()to duplicate the file descriptors before wrapping. When the duplicated descriptors are closed (along with the TextIOWrapper), the original stdin/stdout remain intact and usable.For streams without a
fileno()(e.g.,BytesIOin tests), we fall back to wrapping them directly (previous behavior).Test Plan
Added a regression test
test_stdio_server_does_not_close_real_stdiothat:All existing tests continue to pass.
Reproduction (from issue)