modelcontextprotocol / modelcontextprotocol/python-sdk
Windows: TextIOWrapper in stdio_server() emits CRLF instead of LF, corrupting newline-delimited JSON messages
Personne n'a encore pris cette issue.
- Langage dominant
- Python
- Étoiles
- 24.3k
- Forks
- 4k
- Merge moyen
- 1 j 1 h
- PR mergées (30 j)
- 31
Description
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:
- Violates the NDJSON wire format (which specifies LF-only line endings)
- Creates an asymmetry: the Python
stdio_clientsends bare\n, but the Pythonstdio_serverresponds with\r\n - Could break any MCP client that does a strict
split("\n")and then fails toJSON.parsethe 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.
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Piste de recherche
Commencez dans src/mcp/server/stdio.py, au niveau des appels à TextIOWrapper autour des lignes 46–49, puis reproduisez la sortie brute du sous-processus sous Windows. C'est terminé lorsque stdin et stdout préservent tous deux les octets LF bruts pour le JSON délimité par des retours à la ligne sur toutes les plateformes.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- python
- Domaine
- backend-api-design
- Type d'issue
- Bug
- Difficulté
- 1/5
- Temps estimé
- Moins d'une heure
- Activité
- Active
- Clarté
- Clairement spécifiée
- Accessibilité débutants
- 88/100