Fix stderr forwarding for Jupyter and non-fd environments

Closed
SU
2mo ago
+201-11

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 close
  • src/mcp/os/win32/utilities.py — stderr attribute on FallbackProcess, updated type signatures
  • tests/client/test_stdio.py — 5 new tests

Test plan

  • All 38 existing tests pass + 5 new ones
  • Tested with io.StringIO as errlog (simulates Jupyter)
  • Verified real sys.stderr still takes the direct inheritance path