Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/mcp/client/stdio.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class StdioServerParameters(BaseModel):


@asynccontextmanager
async def stdio_client(server: StdioServerParameters, errlog: TextIO = sys.stderr):
async def stdio_client(server: StdioServerParameters, errlog: TextIO | int = sys.stderr):
"""Client transport for stdio: this will connect to a server by spawning a
process and communicating with it over stdin/stdout.
"""
Expand Down Expand Up @@ -230,7 +230,7 @@ async def _create_platform_compatible_process(
command: str,
args: list[str],
env: dict[str, str] | None = None,
errlog: TextIO = sys.stderr,
errlog: TextIO | int = sys.stderr,
cwd: Path | str | None = None,
):
"""Creates a subprocess in a platform-compatible way.
Expand Down
6 changes: 3 additions & 3 deletions src/mcp/os/win32/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ async def create_windows_process(
command: str,
args: list[str],
env: dict[str, str] | None = None,
errlog: TextIO | None = sys.stderr,
errlog: TextIO | int | None = sys.stderr,
cwd: Path | str | None = None,
) -> Process | FallbackProcess:
"""Creates a subprocess in a Windows-compatible way with Job Object support.
Expand All @@ -155,7 +155,7 @@ async def create_windows_process(
command (str): The executable to run
args (list[str]): List of command line arguments
env (dict[str, str] | None): Environment variables
errlog (TextIO | None): Where to send stderr output (defaults to sys.stderr)
errlog (TextIO | int | None): Where to send stderr output (defaults to sys.stderr)
cwd (Path | str | None): Working directory for the subprocess
Returns:
Expand Down Expand Up @@ -196,7 +196,7 @@ async def _create_windows_fallback_process(
command: str,
args: list[str],
env: dict[str, str] | None = None,
errlog: TextIO | None = sys.stderr,
errlog: TextIO | int | None = sys.stderr,
cwd: Path | str | None = None,
) -> FallbackProcess:
"""Create a subprocess using subprocess.Popen as a fallback when anyio fails.
Expand Down
40 changes: 40 additions & 0 deletions tests/client/test_stdio.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import errno
import shutil
import subprocess
import sys
import textwrap
import time
Expand Down Expand Up @@ -70,6 +71,45 @@ async def test_stdio_client():
assert read_messages[1] == JSONRPCResponse(jsonrpc="2.0", id=2, result={})


@pytest.mark.anyio
async def test_stdio_client_devnull_errlog():
"""Test that stdio_client accepts subprocess.DEVNULL for errlog,
allowing callers to suppress stderr output from the child process.

Regression test for https://github.com/modelcontextprotocol/python-sdk/issues/1806
"""
# A script that writes to stderr then echoes stdin to stdout
script_content = textwrap.dedent(
"""
import sys
sys.stderr.write("this goes to devnull\\n")
sys.stderr.flush()
for line in sys.stdin:
sys.stdout.write(line)
sys.stdout.flush()
"""
)

server_params = StdioServerParameters(
command=sys.executable,
args=["-c", script_content],
)

async with stdio_client(server_params, errlog=subprocess.DEVNULL) as (read_stream, write_stream):
message = JSONRPCRequest(jsonrpc="2.0", id=1, method="ping")
session_message = SessionMessage(message)

async with write_stream:
await write_stream.send(session_message)

async with read_stream:
async for received in read_stream:
if isinstance(received, Exception): # pragma: no cover
raise received
assert received.message == message
break


@pytest.mark.anyio
async def test_stdio_client_bad_path():
"""Check that the connection doesn't hang if process errors."""
Expand Down
Loading