Windows: Python stdio MCP servers fail handshake — stdout response never received by rmcp client

Resolved 💬 2 comments Opened Jun 20, 2026 by kevinmo3 Closed Jun 30, 2026

What version of Codex is running?

codex-cli v0.142.0-alpha.6 (also tested v0.141.0)

What subscription do you have?

N/A — using Ollama as backend (glm-5.2:cloud)

Which model were you using?

glm-5.2:cloud (via Ollama OpenAI-compatible endpoint)

What platform is your computer?

Microsoft Windows NT 10.0.26200.0 x64

What issue are you seeing?

Python-based stdio MCP servers fail the JSON-RPC handshake on Windows. The server starts successfully, loads all tools (visible in stderr logs), and writes the initialize response to stdout — but Codex's Rust MCP client (rmcp) never receives it.

Error:

MCP startup failed: handshaking with MCP server failed: connection closed: initialize response

Key Evidence

  • ✅ The Python MCP server (workspace-mcp / FastMCP) starts and loads 9 tools correctly — all visible in stderr
  • ✅ The initialize JSON-RPC response IS written to stdout (verified via manual shell pipe test)
  • ✅ Node.js-based MCP servers work fine (e.g., node_repl) — this is Python-specific
  • ❌ Codex's rmcp client never logs Service initialized as client for the Python server
  • ❌ Only node_repl gets Service initialized; the Python server is silently dropped

What steps can reproduce the bug?

  1. Configure a Python-based MCP server in ~/.codex/config.toml:
[mcp_servers.gdrive]
command = 'C:\path\to\python.exe'
args = ['-u', 'path/to/launcher.py']
startup_timeout_sec = 120
  1. The Python launcher imports FastMCP/MCP SDK and runs in stdio mode:
sys.argv = ['workspace-mcp', '--tools', 'drive', '--transport', 'stdio']
from main import main
main()
  1. Start Codex (Desktop or codex exec)
  1. Observe:
  • Server stderr shows successful startup (tools loaded, auth configured)
  • Service initialized as client is logged for node_repl but NOT for the Python server
  • Error: handshaking with MCP server failed: connection closed: initialize response

Debug log output

Running with RUST_LOG=debug codex exec shows:

INFO codex_rmcp_client::stdio_server_launcher: MCP server stderr (python.exe): INFO:auth.google_auth:Using credentials directory...
INFO codex_rmcp_client::stdio_server_launcher: MCP server stderr (python.exe): [TOOLS] Tool tier 'core' loaded: 9 tools across 1 services [['drive']]
INFO codex_rmcp_client::stdio_server_launcher: MCP server stderr (python.exe): [REGISTRY] Tool filtering: removed 13 tools, 9 enabled. Mode: Read-Only
INFO serve_inner: rmcp::service: Service initialized as client peer_info=Some(InitializeResult { ... server_info: Implementation { name: "rmcp" ...})  ← only node_repl

WARN codex_mcp::rmcp_client: failed to initialize MCP client during shutdown: MCP startup failed: handshaking with MCP server failed: connection closed: initialize response

What was tried (all failed)

| Approach | Result |
|----------|--------|
| uvx workspace-mcp | Same — sandbox also blocks AppData |
| Direct workspace-mcp.exe (pip wrapper) | Same |
| python.exe -u + PYTHONUNBUFFERED=1 | Same |
| .bat wrapper around python | Same |
| cmd.exe /c python.exe | Same |
| Node.js wrapper spawning Python with stdio: inherit | Same |
| Python sys.stdout.reconfigure(line_buffering=True) | Same |

Root Cause Hypothesis

This appears to be a Windows pipe compatibility issue between Python's stdout and Codex's Rust MCP client (rmcp). The MCP SDK's stdio writer (mcp/server/stdio.py) uses:

stdout = anyio.wrap_file(TextIOWrapper(sys.stdout.buffer, encoding="utf-8"))
# ...
await stdout.write(json + "\n")
await stdout.flush()

On Windows, Python's sys.stdout.buffer may not properly flush to the pipe created by Codex's Rust process, even with PYTHONUNBUFFERED=1 and -u. The anyio async wrapper adds another layer of buffering.

This is related to #7155 (Windows stderr MCP issue) and #24439 (diagnostic command request), but specifically affects Python stdout to Rust stdin pipe on Windows during the initial handshake.

Node.js-based MCP servers don't have this issue because Node handles Windows pipes differently.

Workaround

Use a direct CLI script (shell command) instead of MCP — Codex calls Python via codex exec shell tool, bypassing the MCP protocol entirely. This works but loses MCP's benefits (tool auto-discovery, persistent connection, etc.).

Expected behavior

Python stdio MCP servers should complete the handshake on Windows, same as Node.js servers.

Related issues

  • #7155 — Windows stderr MCP Transport closed (same rmcp client, stderr blocking)
  • #24439 — Request for codex mcp check diagnostic command
  • #6020 — Broad initialize response failures (42 comments)
  • #3510 — MCP servers not working on Windows (closed)

View original on GitHub ↗

2 Comments

Necmttn · 2 months ago

This needs handshake diagnostics at the stdio boundary. For each MCP startup, capture command/cwd, resolved executable, env diff, child pid, first stdout bytes, stderr tail, close reason, and whether the client saw no bytes, invalid JSON, or EOF after a partial frame.

On Windows/Python specifically, add fixtures for buffered stdout, CRLF framing, binary/text mode, and explicit unbuffered Python. That should separate an rmcp read issue from a server flushing issue.

---

_Generated with ax._

kevinmo3 · 2 months ago

Thanks for the suggestions. I've already done extensive testing on the stdio boundary — here's what I've found:

Diagnostics already captured:

  • Resolved executable: C:\Python311\python.exe (also tested C:\Python314\python.exe)
  • Child PID: visible in Task Manager during startup; process alive and running
  • Stderr tail: server logs successful startup — "Loaded 9 tools", "Auth configured", "Server ready"
  • First stdout bytes: verified via manual pipe test (python launcher.py | head) — the initialize JSON-RPC response IS written to stdout
  • Close reason: Codex client closes the connection after the startup timeout — "connection closed: initialize response"

What was tested (all failed):

  1. python.exe -u (unbuffered stdout)
  2. PYTHONUNBUFFERED=1 env var
  3. .bat wrapper calling python.exe
  4. cmd.exe /c python.exe
  5. uvx.exe — additionally blocked by Codex sandbox (can't write to AppData\Roaming\uv\python)
  6. Node.js wrapper with stdio: ['inherit', 'inherit', 'inherit'] — Rust→Node bridge works, but Node→Python stdout still doesn't flush to Codex's reader

Key findings:

  • Node.js MCP servers work fine (e.g., node_repl) — this is Python-specific
  • The Python server's stdout response never reaches Codex's rmcp client — the Rust process reads nothing (not invalid JSON, not partial frames — no bytes at all)
  • This points to a Windows pipe buffering issue between Python's stdout and Rust's stdin reader, not a server-side flushing problem (since unbuffered mode didn't help)

Hypothesis: The issue is on the Rust (rmcp) side — its stdin reader on Windows may not be correctly reading from a Python child process's stdout pipe. Node.js child processes don't exhibit this because Node handles stdout differently (different default buffering behavior on Windows pipes).

Workaround I'm using: Bypassed MCP entirely — gave Codex a direct Python CLI script it calls via shell. Works reliably on Windows. Would still prefer MCP to work natively.