Fix stderr forwarding for Jupyter and non-fd environments
Closed
SU
2mo ago+201+201+012345678901234567890123456789-11-11-01234567890123456789
Summary
Fixes #156.
When running MCP servers from Jupyter notebooks, server stderr output silently vanishes because ipykernel.iostream.OutStream lacks a real OS file descriptor. The SDK tries to pass errlog directly to the subprocess, which silently fails.
How it works
Detection: Added _lacks_file_descriptor(errlog) helper that calls errlog.fileno(). Real stderr returns a number; Jupyter streams and io.StringIO raise an error.
Piping: When errlog has no file descriptor, spawns the server with stderr=subprocess.PIPE instead of inheriting errlog.
Forwarding: An async stderr_reader() task reads chunks from the pipe and writes them to errlog. Handles pipe close and errlog close gracefully.
No-op path: When errlog has a real file descriptor, nothing changes.
Changes
src/mcp/client/stdio.py— fd detection, conditional piping, async stderr reader, drain before closesrc/mcp/os/win32/utilities.py—stderrattribute onFallbackProcess, updated type signaturestests/client/test_stdio.py— 5 new tests
Test plan
- All 38 existing tests pass + 5 new ones
- Tested with
io.StringIOas errlog (simulates Jupyter) - Verified real
sys.stderrstill takes the direct inheritance path