Hmbown/CodeWhale · error · io::Error

MCP stdio line exceeded

Error message

MCP stdio line exceeded {max} bytes

What it means

read_line_capped enforces a per-line byte cap on the MCP stdio transport. As it accumulates bytes it errors with InvalidData once the buffered line exceeds max, preventing a misbehaving or malicious server from exhausting memory with an unterminated line.

Solutions

  1. Fix or update the MCP server so it emits frames within the size cap.
  2. Raise the transport's max line size if your server legitimately sends very large responses.
  3. Inspect server stderr/logs for corrupted output causing unbounded lines.
Defensive patterns

Strategy: retry

Try / catch

match recv().await {
    Err(e) if e.to_string().contains("line exceeded") => {
        eprintln!("server sent an oversized frame; restart server or raise max");
        restart_server_and_retry().await
    }
    other => other,
}

Prevention

When it happens

Trigger: recv (or the tests cancelled_partial_read_preserves_next_frame / aborts_on_newline_free_line_over_cap) reads a line from the MCP stdio server whose length surpasses the configured max before a newline arrives.

Common situations: A server emitting huge JSON-RPC frames on a single line; a broken server writing binary/noise without newlines; a misconfigured max line size that is smaller than legitimate responses.

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/31a37bebcd4ff6db. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp/stdio.rs:434

{
    use tokio::io::AsyncBufReadExt;
    loop {
        let (chunk, consumed, done) = {
            let available = reader.fill_buf().await?;
            if available.is_empty() {
                (Vec::new(), 0usize, true)
            } else if let Some(pos) = available.iter().position(|&b| b == b'\n') {
                (available[..=pos].to_vec(), pos + 1, true)
            } else {
                (available.to_vec(), available.len(), false)
            }
        };
        if consumed > 0 {
            reader.consume(consumed);
        }
        out.extend_from_slice(&chunk);
        if out.len() > max {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("MCP stdio line exceeded {max} bytes"),
            ));
        }
        if done {
            break;
        }
    }
    Ok(out.len())
}

#[cfg(test)]
mod read_cap_tests {
    use super::read_line_capped;

    #[tokio::test]
    async fn cancelled_partial_read_preserves_next_frame() {
        use futures_util::FutureExt;

View on GitHub (pinned to 73e0f67d83)