modelcontextprotocol / modelcontextprotocol/python-sdk

Windows: TextIOWrapper in stdio_server() emits CRLF instead of LF, corrupting newline-delimited JSON messages

Offen Anfängerfreundlich
#2,433 9 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

bug fix proposed P2 ready for work
Vorherrschende Sprache
Python
Sterne
24.3k
Forks
4k
Ø Merge
1 T. 1 Std.
Gemergte PRs (30 T.)
31

Beschreibung

Summary

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 MCP spec uses newline-delimited JSON with \n as the delimiter. Emitting \r\n is a protocol-level impurity.

Affected file

mcp/server/stdio.py lines 46–49

# Current (buggy on Windows)
if not stdin:
    stdin = anyio.wrap_file(TextIOWrapper(sys.stdin.buffer, encoding="utf-8"))
if not stdout:
    stdout = anyio.wrap_file(TextIOWrapper(sys.stdout.buffer, encoding="utf-8"))

Reproduction

On Windows, spawn a Python subprocess that uses this code and read the raw bytes:

import subprocess, sys

script = r'''
import sys
from io import TextIOWrapper
stdout = TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
stdout.write('{"jsonrpc":"2.0","result":"ok"}\n')
stdout.flush()
'''

proc = subprocess.Popen([sys.executable, "-c", script], stdout=subprocess.PIPE)
out, _ = proc.communicate(timeout=5)
print(repr(out))
# Output on Windows: b'{"jsonrpc":"2.0","result":"ok"}\r\n'
# Output on Linux:   b'{"jsonrpc":"2.0","result":"ok"}\n'

Verified on:

  • OS: Windows 11 Pro (10.0.26200)
  • Python: 3.11
  • mcp: 1.26.0

Why this matters

While the current JS MCP SDK client (StdioClientTransport) strips trailing \r via .replace(/\r$/, "") before parsing, this is a server-side bug that:

  1. Violates the NDJSON wire format (which specifies LF-only line endings)
  2. Creates an asymmetry: the Python stdio_client sends bare \n, but the Python stdio_server responds with \r\n
  3. Could break any MCP client that does a strict split("\n") and then fails to JSON.parse the line with a trailing \r

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) doesn't also fix the newline translation mode.

Fix

Add newline="" to both TextIOWrapper calls. newline="" disables translation while still operating in text mode:

if not stdin:
    stdin = anyio.wrap_file(TextIOWrapper(sys.stdin.buffer, encoding="utf-8", newline=""))
if not stdout:
    stdout = anyio.wrap_file(TextIOWrapper(sys.stdout.buffer, encoding="utf-8", newline=""))

With this fix:

# newline="" result:
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

The same fix should be applied to the stdin wrapper so that incoming messages with bare \n are not translated either (avoiding any future issues if a client sends strict LF).

Context

This was discovered while debugging Windows MCP tool timeouts with mem0-mcp-selfhosted. The eager-init approach fixed the actual timeout, but this CRLF emission was identified as a secondary protocol-level issue during investigation.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne in src/mcp/server/stdio.py bei den TextIOWrapper-Aufrufen um die Zeilen 46–49 und gib dann die rohe Subprozessausgabe unter Windows wieder. Erledigt ist es, wenn sowohl stdin als auch stdout plattformübergreifend reine LF-Bytes für newline-delimited JSON beibehalten.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
backend-api-design
Issue-Typ
Bug
Schwierigkeit
1/5
Geschätzter Aufwand
Unter einer Stunde
Aktivitätsstatus
Aktiv
Klarheit
Klar beschrieben
Anfängerfreundlichkeit
88/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.