modelcontextprotocol / modelcontextprotocol/python-sdk

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

Abierto Apto para principiantes
#2,433 9 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

bug fix proposed P2 ready for work
Lenguaje dominante
Python
Estrellas
24.3k
Forks
4k
Merge medio
1 d 1 h
PR fusionados (30 d)
31

Descripción

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.

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Comienza en src/mcp/server/stdio.py, en las llamadas a TextIOWrapper alrededor de las líneas 46–49, y luego reproduce la salida sin procesar del subproceso en Windows. Se considera terminado cuando tanto stdin como stdout conservan bytes LF sin adornos para JSON delimitado por saltos de línea en todas las plataformas.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
backend-api-design
Tipo de issue
Error
Dificultad
1/5
Tiempo estimado
Menos de una hora
Estado de actividad
Activo
Claridad
Bien especificado
Aptitud para principiantes
88/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.