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

  1. Configure the MCP server to send ALL logging/diagnostics to stderr; stdout must carry only JSON-RPC lines.
  2. Check the embedded parse error to see exactly what non-JSON bytes arrived (banners, ANSI codes, or framing mismatch).
  3. Verify the server implements the MCP stdio transport's newline-delimited JSON framing, not another RPC framing.
  4. 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

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


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)