Title: Don't close real process stdio in stdio_server (#1933) by steps-re · Pull Request #3090 · modelcontextprotocol/python-sdk · GitHub
Open Graph Title: Don't close real process stdio in stdio_server (#1933) by steps-re · Pull Request #3090 · modelcontextprotocol/python-sdk
X Title: Don't close real process stdio in stdio_server (#1933) by steps-re · Pull Request #3090 · modelcontextprotocol/python-sdk
Description: The bug transport="stdio" closes the real process stdin/stdout. Any code that runs after the server exits raises: ValueError: I/O operation on closed file. A minimal reproduction is just a print() after run(transport="stdio") returns. Root cause src/mcp/server/stdio.py re-wraps sys.stdin.buffer / sys.stdout.buffer in a TextIOWrapper to force UTF-8: stdin = anyio.wrap_file(TextIOWrapper(sys.stdin.buffer, encoding="utf-8", errors="replace")) stdout = anyio.wrap_file(TextIOWrapper(sys.stdout.buffer, encoding="utf-8")) The comment says these purposely avoid a context manager so the standard handles stay open. But a TextIOWrapper closes the buffer it wraps in its __del__ finalizer. Once the wrapper is garbage-collected, sys.stdin.buffer / sys.stdout.buffer are closed, so the real sys.stdin / sys.stdout are dead and every later read or write fails. The wrapper defeats the very thing the comment intends. The fix Dup the underlying fd and wrap the copy, so the wrapper only ever closes the duplicate, never the real handle. os.dup is Windows-safe. The dup'd wrappers are closed on exit so their fds don't leak. binary = open(os.dup(std.fileno()), mode + "b", closefd=True) wrapper = TextIOWrapper(binary, encoding="utf-8", errors=errors) When sys.std* has no real fd (pytest capture, embedded interpreters, injected in-memory streams), it falls back to wrapping .buffer directly and detach()es the wrapper on exit, which severs it from the buffer without closing it, so the finalizer can no longer close the real handle either. This keeps the existing in-memory / monkeypatched test paths working. This matches the approach the issue author proposed. Test Adds a regression test in tests/server/test_stdio.py: after async with stdio_server(): ... exits, the real sys.stdin / sys.stdout buffers must not be closed, and a write to stdout after exit must succeed. It fails on main (AssertionError: stdio_server closed the real stdin buffer) and passes with the fix. All stdio tests pass (pytest tests/ -k stdio → 51 passed, 5 Windows-only skipped), including the real-subprocess round-trip interaction test. ruff check, ruff format --check, and pyright are clean on the changed files. Fixes #1933 This was developed with AI assistance (Claude Code). I understand and have tested the change; happy to walk through any part.
Open Graph Description: The bug transport="stdio" closes the real process stdin/stdout. Any code that runs after the server exits raises: ValueError: I/O operation on closed file. A minimal reproduction is just...
X Description: The bug transport="stdio" closes the real process stdin/stdout. Any code that runs after the server exits raises: ValueError: I/O operation on closed file. A minimal reproduction...
Opengraph URL: https://github.com/modelcontextprotocol/python-sdk/pull/3090
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:9e905453-6045-72a1-577d-6a46eda1e503 |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | CD8E:389F43:11DC616:17D2BFC:6A5AAF64 |
| html-safe-nonce | f2af5361a0e9eeb33b2d6580195412241fe17a5690ac2e4da2bf554ee28c7934 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDRDhFOjM4OUY0MzoxMURDNjE2OjE3RDJCRkM6NkE1QUFGNjQiLCJ2aXNpdG9yX2lkIjoiMjgyMTY2MDIzMzgzMTE5ODU2NCIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | dc7673d03cda0b991b086b0d651277a2a567bbc3f55af9008b34d903e6d47adb |
| hovercard-subject-tag | pull_request:4046815685 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/modelcontextprotocol/python-sdk/pull/3090/files |
| twitter:image | https://avatars.githubusercontent.com/u/91017100?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/91017100?s=400&v=4 |
| og:image:alt | The bug transport="stdio" closes the real process stdin/stdout. Any code that runs after the server exits raises: ValueError: I/O operation on closed file. A minimal reproduction is just... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 21c53da6db0c40484841ba32fe19ecf485e7e734334faab487cd59d884d2bb33 |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/modelcontextprotocol/python-sdk git https://github.com/modelcontextprotocol/python-sdk.git |
| octolytics-dimension-user_id | 182288589 |
| octolytics-dimension-user_login | modelcontextprotocol |
| octolytics-dimension-repository_id | 862584018 |
| octolytics-dimension-repository_nwo | modelcontextprotocol/python-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 862584018 |
| octolytics-dimension-repository_network_root_nwo | modelcontextprotocol/python-sdk |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 6e85f20f54e1147d8162094880590864abefff6b |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width