Repository navigation
Conversation
The stdio server path already degrades invalid UTF-8 into parse errors instead of killing the transport, but the client side still defaulted to strict decoding and could crash the whole task group on a single bad line from a child process. This changes the default decode strategy to replacement, broadens shutdown-time exception handling for abrupt child exits, and adds a regression test that proves malformed output becomes an in-stream error while the next valid JSON-RPC message still arrives. Constraint: Must preserve stdio_client cleanup behavior when subprocesses exit early or reset pipes Rejected: Add a new configuration flag only for the regression test | leaves the default transport behavior asymmetric and crash-prone Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep client and server stdio malformed-UTF-8 handling symmetric unless the protocol deliberately diverges Tested: uv run --frozen pytest tests/client/test_stdio.py; uv run --frozen ruff check src/mcp/client/stdio.py tests/client/test_stdio.py; uv run --frozen pyright src/mcp/client/stdio.py Not-tested: Full multi-platform matrix outside local macOS / Python 3.10 run
The malformed-UTF8 regression test now executes the JSON-parse failure branch in stdio_client, so the old pragma no longer reflects reality and breaks CI's strict-no-cover gate. This follow-up removes the stale pragma without changing behavior. Constraint: Must not widen the PR scope beyond the already-proposed stdio robustness fix Rejected: Suppress strict-no-cover or weaken the regression test | hides real coverage drift instead of correcting it Confidence: high Scope-risk: narrow Reversibility: clean Directive: When adding malformed-input regression tests, re-audit nearby no-cover pragmas immediately Tested: coverage run of tests/client/test_stdio.py plus strict-no-cover locally up to the updated branch execution path Not-tested: Full upstream CI rerun after push
|
for additional signal: |
|
Heads up @shaun0927 — this PR has gone conflicting after the stdio transport was refactored on |
|
Thanks for the PR, and sorry it sat here without a proper review. We're closing most of the open PR backlog. v2 is out and changed a lot of the SDK, so many older PRs no longer apply as written, and we're a small team that realistically doesn't have the capacity to work through the rest. If this still matters to you on v2, the most useful thing you can do is open an issue (or comment on the existing one) with your use case and a repro. Hearing why it matters to you is what we use to decide what to prioritise. |
Fixes #2454
Summary
stdio_client()currently decodes child stdout withencoding_error_handler="strict", so a malformed UTF-8 line can raise duringTextReceiveStream(...)iteration and crash the transport task group.This PR makes the client follow the same strategy that
stdio_server()already uses after #2302:errors="replace"It also treats abrupt child shutdowns (
BrokenResourceError/ConnectionResetError) as normal shutdown-time conditions in the background stdio tasks.Motivation and Context
PR #2302 fixed the server side so invalid UTF-8 on stdin becomes a parse error instead of a transport crash. The client side still behaved asymmetrically: a buggy or non-compliant child process could kill the Python client with a single malformed stdout line even if the next line was valid JSON-RPC.
That makes the client less robust than the server for the same class of malformed stdio input.
How Has This Been Tested?
Added a focused regression test in
tests/client/test_stdio.pythat spawns a child process which writes:b"\xff\xfe\n")Without this patch, the transport fails before the valid line is delivered.
With this patch:
ExceptionSessionMessageLocal verification:
uv run --frozen pytest tests/client/test_stdio.pyuv run --frozen ruff check src/mcp/client/stdio.py tests/client/test_stdio.pyuv run --frozen pyright src/mcp/client/stdio.pyBreaking Changes
None.
Types of changes
Checklist