fix: disable newline translation in stdio TextIOWrapper to prevent CRLF on Windows
Closed
MA
16d ago+56+56+01234567890123456789-2-2-0123456789
Fixes #2433
Summary
On Windows, TextIOWrapper without newline="" performs universal newline translation (\n → \r\n on write), corrupting the newline-delimited JSON wire format used by MCP's stdio transport.
Root Cause
mcp/server/stdio.py creates TextIOWrapper(sys.stdout.buffer, encoding="utf-8") without specifying newline="". On Windows, the default newline=None causes \n → \r\n translation, so every JSON-RPC message written to stdout ends with \r\n instead of \n.
The comment on line 43 even acknowledges: "Encoding of stdin/stdout as text streams on python is platform-dependent (Windows is particularly problematic)" — but the fix applied (re-wrap to ensure UTF-8) didn't also fix the newline translation mode.
Changes
src/mcp/server/stdio.py
- Added
newline=""to both the stdin and stdoutTextIOWrappercalls newline=""disables universal newline translation while still operating in text mode- This ensures bare
\nis emitted on all platforms, matching the NDJSON wire format
tests/server/test_stdio.py
- Added
test_stdio_server_no_crlf_on_windows— exercises the defaultsys.stdout.buffercode path viamonkeypatch, inspects raw bytes in the output buffer, and asserts no\rbytes are present
Verification
# With newline="":
buf = io.BytesIO()
wrapper = TextIOWrapper(buf, encoding="utf-8", newline="")
wrapper.write('{"jsonrpc":"2.0","result":"ok"}\n')
wrapper.flush()
repr(buf.getvalue())
# b'{"jsonrpc":"2.0","result":"ok"}\n' ← correct on all platforms
Compatibility
- No behavioral change on Linux/macOS (where
\nwas already preserved) - On Windows, fixes the
\r\ncorruption without any new dependencies - The stdin wrapper also gets
newline=""to prevent future issues if a client sends strict LF