Hmbown/CodeWhale · error
MCP server ' ': invalid child stdout while awaiting
Error message
MCP server '{}': invalid child stdout while awaiting {}: {} What it means
While awaiting a response, the reader thread parses each child stdout line as JSON-RPC. A line that cannot be parsed is forwarded as `ChildStdoutMessage::Invalid(error)`, and `request` converts that into this bail. The MCP stdio transport requires every stdout line to be a valid JSON-RPC message; anything else (log output, banners, ANSI escapes) poisons the stream. The parse error text is embedded in the message.
Solutions
- Configure the MCP server to send ALL logging/diagnostics to stderr; stdout must carry only JSON-RPC lines.
- Check the embedded parse error to see exactly what non-JSON bytes arrived (banners, ANSI codes, or framing mismatch).
- Verify the server implements the MCP stdio transport's newline-delimited JSON framing, not another RPC framing.
- Update or replace the server binary; many older MCP servers had stdout-pollution bugs fixed in later releases.
Example fix
// before (server code): logging to stdout
console.log("server started");
// after: stderr for logs, stdout for JSON-RPC only
console.error("server started"); Defensive patterns
Strategy: try-catch
Try / catch
match conn.request(method, params, timeout).await {
Err(e) if e.to_string().contains("invalid child stdout") => {
eprintln!("server polluted stdout with non-JSON: {e}; route logs to stderr");
}
other => other,
} Prevention
- In server code, never print/log to stdout; use stderr exclusively for diagnostics.
- Verify the server uses newline-delimited JSON-RPC framing on stdout, not another protocol.
- Run a handshake/list smoke test per server version; stdout pollution usually shows up immediately.
- Strip or reject ANSI escapes and banners in any wrapper you control.
When it happens
Trigger: `request` is awaiting `{method}` when the reader thread receives a stdout line from the child that fails JSON parsing, e.g. plain text, a partial/truncated JSON line, or non-JSON debug output interleaved with responses.
Common situations: A server that prints human-readable logs or ASCII banners to stdout instead of stderr; a server using a different framing (e.g. Content-Length headers, one JSON blob without newline); locale/encoding output corrupting lines; a response so large it is split across writes without a trailing newline yet.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- MCP server ' ': invalid child stdout while awaiting
- child returned a malformed MCP CallToolResult
- child stdout was not valid UTF-8
- connection stdin poisoned by an earlier panic
- failed to read bounded stdio JSON-RPC frame
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/d1ff063a9141162f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/mcp/src/stdio_client.rs:893
bail!(
"MCP server '{server}': process closed stdin before answering {method}{}",
self.exit_note()
);
}
return Err(err)
.with_context(|| format!("MCP server '{server}': failed to send {method}"));
}
let deadline = Instant::now() + timeout;
loop {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
bail!("MCP server '{server}': {method} timed out after {timeout:?}");
}
let line = match self.responses.recv_timeout(remaining) {
Ok(ChildStdoutMessage::Line(line)) => line,
Ok(ChildStdoutMessage::Invalid(error)) => {
bail!(
"MCP server '{server}': invalid child stdout while awaiting {method}: {error}"
);
}
Err(RecvTimeoutError::Timeout) => {
bail!("MCP server '{server}': {method} timed out after {timeout:?}");
}
Err(RecvTimeoutError::Disconnected) => {
bail!(
"MCP server '{server}': process closed stdout before answering {method}{}",
self.exit_note()
);
}
};
// Servers occasionally emit banners or log lines on stdout. They
// are skipped; only the matching response ends the wait. Valid
// requests and notifications are handled by the reader before
// they reach this rendezvous.View on GitHub (pinned to 433685b202)