Skip to content

fix: pipe stderr asynchronously to support Jupyter notebook environments#2786

Open
michaelxer wants to merge 2 commits into
modelcontextprotocol:mainfrom
michaelxer:fix/jupyter-stderr-logging
Open

fix: pipe stderr asynchronously to support Jupyter notebook environments#2786
michaelxer wants to merge 2 commits into
modelcontextprotocol:mainfrom
michaelxer:fix/jupyter-stderr-logging

Conversation

@michaelxer
Copy link
Copy Markdown

Summary

Fixes stderr handling in stdio_client to work correctly in Jupyter notebook environments by always piping stderr and reading it asynchronously, rather than passing sys.stderr directly as a subprocess file descriptor.

Fixes #156

Problem

In Jupyter notebook environments, sys.stderr is not a regular file descriptor — it's a rich text widget that cannot be passed to subprocess.Popen or anyio.open_process. This causes stdio_client to fail with an error when trying to start MCP servers from within Jupyter notebooks.

Solution

Instead of forwarding sys.stderr (or a custom errlog stream) directly to the subprocess, we now:

  1. Always pipe stderr (subprocess.PIPE) — both on Unix and Windows
  2. Read stderr asynchronously in a new stderr_reader task that runs alongside stdout_reader and stdin_writer
  3. Detect Jupyter environments via _is_jupyter_notebook() which uses isinstance(get_ipython(), ZMQInteractiveShell) to check for ZMQ-based IPython kernels
  4. Output with appropriate formatting — ANSI red coloring via print() in Jupyter, plain text via the errlog stream elsewhere

Changes

  • src/mcp/client/stdio.py:

    • Added _is_jupyter_notebook() helper function
    • Added stderr_reader() async task in stdio_client()
    • Removed errlog parameter from _create_platform_compatible_process()
    • Always set stderr=subprocess.PIPE in process creation
  • src/mcp/os/win32/utilities.py:

    • Removed errlog parameter usage, always use stderr=subprocess.PIPE
  • tests/issues/test_1027_win_unreachable_cleanup.py:

    • Updated call to _create_platform_compatible_process() to match new signature (removed errlog arg)

Testing

  • All existing tests pass
  • Pyright type checking passes with 0 errors
  • Ruff linting passes

In Jupyter notebook environments, sys.stderr cannot reliably be used as
a subprocess file descriptor. This causes stdio_client to fail when
trying to start MCP servers from within Jupyter notebooks.

Changes:
- Always pipe stderr (subprocess.PIPE) instead of passing sys.stderr
  directly to the subprocess
- Add async stderr_reader task that reads stderr output and forwards it
  to the errlog stream (or ANSI-colored print output in Jupyter)
- Add _is_jupyter_notebook() helper to detect Jupyter environments via
  isinstance check against ZMQInteractiveShell
- Remove errlog parameter from _create_platform_compatible_process and
  create_windows_process since stderr is now always piped
- Update win32 utilities to always use subprocess.PIPE for stderr

Fixes modelcontextprotocol#156
Three tests to cover the _is_jupyter_notebook() and stderr_reader()
Jupyter-specific branches introduced in the stderr piping fix:

- _is_jupyter_notebook returns False without ipykernel
- _is_jupyter_notebook returns True with mocked Jupyter shell
- stderr_reader writes ANSI-colored output when in Jupyter

Fixes 100% coverage requirement for src/mcp/client/stdio.py.
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.

support logging to stderr in Jupyter Notebook Environments.

1 participant