Bug: stdio transport fails to handle Content-Length header format from MCP clients
Description
The stdio_server transport in mcp/server/stdio.py reads stdin line-by-line and attempts to parse each line as JSON. This works for NDJSON (newline-delimited JSON) format but fails completely when MCP clients send messages using the Content-Length header format.
Several popular MCP clients (including those built on the TypeScript SDK @modelcontextprotocol/sdk) send stdio messages with Content-Length headers, following the pattern established by LSP (Language Server Protocol). This causes a compatibility gap where Python SDK-based servers cannot be initialized by these clients.
Steps to Reproduce
- Create a minimal MCP server using the Python SDK:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("test-server")
mcp.run(transport="stdio")
- Send an MCP
initialize request using Content-Length header format (as used by TypeScript SDK-based clients):
Content-Length: 157\r\n\r\n{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}
- Observe the server response.
Expected Behavior
The server should either:
- (a) Properly parse the
Content-Length header and read the specified number of bytes as the message body, OR
- (b) Gracefully skip non-JSON lines (like
Content-Length headers) and process valid JSON messages
Actual Behavior
The server reads the input line-by-line via async for line in stdin: and tries to parse each line as JSON:
# mcp/server/stdio.py, line 60-68
async def stdin_reader():
async with read_stream_writer:
async for line in stdin:
try:
message = types.JSONRPCMessage.model_validate_json(line)
except Exception as exc:
await read_stream_writer.send(exc)
continue
When receiving Content-Length header format:
- Line
Content-Length: 157 → JSON parse error → exception sent to stream → error notification emitted
- Empty line `` → JSON parse error → exception sent to stream → error notification emitted
- JSON body without trailing newline → never read (stdin reader blocks waiting for
\n)
The server outputs two error notifications:
{"method":"notifications/message","params":{"level":"error","logger":"mcp.server.exception_handler","data":"Internal Server Error"},"jsonrpc":"2.0"}
{"method":"notifications/message","params":{"level":"error","logger":"mcp.server.exception_handler","data":"Internal Server Error"},"jsonrpc":"2.0"}
And the actual initialize response is never sent, causing the client to timeout with:
failed to initialize MCP client: transport error: context deadline exceeded
Verification
I verified this with a Node.js test script that spawns the Python MCP server and sends messages in both formats:
| Format |
Result |
NDJSON (raw JSON + \n) |
? Success - correct initialize response |
Content-Length header + body (no trailing \n) |
? Failed - 2 error notifications, no response (body never read) |
Content-Length header + body + \n |
?? Partial - 2 error notifications + correct response (error notifications may confuse client) |
Environment
- mcp package version: 1.27.0 (latest)
- Python version: 3.14
- OS: Windows 11
- MCP client: Qoder IDE (uses TypeScript SDK)
Suggested Fix
The stdin_reader in mcp/server/stdio.py should be updated to handle both formats:
Option A: Skip non-JSON lines silently
Instead of sending parse exceptions to the stream, silently skip lines that don't parse as valid JSON-RPC messages. This would allow Content-Length header lines and empty lines to be ignored while processing the actual JSON body.
async def stdin_reader():
async with read_stream_writer:
async for line in stdin:
stripped = line.strip()
if not stripped:
continue # Skip empty lines
try:
message = types.JSONRPCMessage.model_validate_json(stripped)
except Exception:
continue # Skip non-JSON lines (e.g., Content-Length headers)
session_message = SessionMessage(message)
await read_stream_writer.send(session_message)
Option B: Full Content-Length header support
Implement proper Content-Length header parsing (read header, extract length, read body of that length), falling back to NDJSON for backward compatibility.
Additional Context
- The MCP specification (2025-03-26) states stdio messages are "delimited by newlines" (NDJSON format).
- However, the TypeScript SDK (
@modelcontextprotocol/sdk) uses Content-Length headers for stdio transport, creating a real-world compatibility gap.
- Other projects (e.g., Switchboard) have documented this issue and implemented dual-format support: "Looks ahead to detect Content-Length headers, Falls back to line-delimited parsing if no header found, Skips non-JSON lines."
- This affects any Python SDK-based MCP server when used with clients that send Content-Length headers (Qoder, and potentially others built on the TypeScript SDK).
Bug: stdio transport fails to handle Content-Length header format from MCP clients
Description
The
stdio_servertransport inmcp/server/stdio.pyreads stdin line-by-line and attempts to parse each line as JSON. This works for NDJSON (newline-delimited JSON) format but fails completely when MCP clients send messages using theContent-Lengthheader format.Several popular MCP clients (including those built on the TypeScript SDK
@modelcontextprotocol/sdk) send stdio messages withContent-Lengthheaders, following the pattern established by LSP (Language Server Protocol). This causes a compatibility gap where Python SDK-based servers cannot be initialized by these clients.Steps to Reproduce
initializerequest using Content-Length header format (as used by TypeScript SDK-based clients):Expected Behavior
The server should either:
Content-Lengthheader and read the specified number of bytes as the message body, ORContent-Lengthheaders) and process valid JSON messagesActual Behavior
The server reads the input line-by-line via
async for line in stdin:and tries to parse each line as JSON:When receiving
Content-Lengthheader format:Content-Length: 157→ JSON parse error → exception sent to stream → error notification emitted\n)The server outputs two error notifications:
{"method":"notifications/message","params":{"level":"error","logger":"mcp.server.exception_handler","data":"Internal Server Error"},"jsonrpc":"2.0"} {"method":"notifications/message","params":{"level":"error","logger":"mcp.server.exception_handler","data":"Internal Server Error"},"jsonrpc":"2.0"}And the actual
initializeresponse is never sent, causing the client to timeout with:Verification
I verified this with a Node.js test script that spawns the Python MCP server and sends messages in both formats:
\n)initializeresponse\n)\nEnvironment
Suggested Fix
The
stdin_readerinmcp/server/stdio.pyshould be updated to handle both formats:Option A: Skip non-JSON lines silently
Instead of sending parse exceptions to the stream, silently skip lines that don't parse as valid JSON-RPC messages. This would allow Content-Length header lines and empty lines to be ignored while processing the actual JSON body.
Option B: Full Content-Length header support
Implement proper Content-Length header parsing (read header, extract length, read body of that length), falling back to NDJSON for backward compatibility.
Additional Context
@modelcontextprotocol/sdk) usesContent-Lengthheaders for stdio transport, creating a real-world compatibility gap.