Hmbown/CodeWhale · error

MCP response Content-Length {len} exceeds {} bytes — abortin

Error message

MCP response Content-Length {len} exceeds {} bytes — aborting

What it means

Fast-path guard: the HTTP response declares a Content-Length larger than MAX_MCP_RESPONSE_BYTES (16 MiB, crates/tui/src/mcp/wire.rs), so the body is rejected before any byte is read. This stops a misbehaving or malicious server from OOM-ing the client at transport-read time with an oversized response.

Source

Thrown at crates/tui/src/mcp/streamable_http.rs:124

                mask_url_secrets(&self.url),
                status,
                body_excerpt,
            )));
        }

        let content_type = response
            .headers()
            .get(CONTENT_TYPE)
            .and_then(|value| value.to_str().ok())
            .map(str::to_string);
        // Reject an over-large declared body before reading anything (fast
        // path), then bound the read itself so chunked / length-less
        // responses cannot OOM us either — Content-Length alone does not
        // protect against a server that streams without declaring a length.
        if let Some(len) = response.content_length()
            && len > MAX_MCP_RESPONSE_BYTES as u64
        {
            return Err(StreamableSendError::Other(anyhow::anyhow!(
                "MCP response Content-Length {len} exceeds {} bytes — aborting",
                MAX_MCP_RESPONSE_BYTES
            )));
        }
        let body = read_body_capped(response, MAX_MCP_RESPONSE_BYTES)
            .await
            .map_err(StreamableSendError::Other)?;
        self.store_response_body(content_type.as_deref(), &body)
            .map_err(StreamableSendError::Other)
    }

    pub(super) async fn recv(&mut self) -> Result<Vec<u8>> {
        self.pending_messages
            .pop_front()
            .context("MCP Streamable HTTP response queue is empty")
    }

    fn store_response_body(&mut self, content_type: Option<&str>, body: &str) -> Result<()> {

View on GitHub (pinned to 8880682c63)

Solutions

  1. Shrink the server response: paginate tool results, cap file reads, return references instead of inlining content
  2. Narrow the tool arguments (offset/limit, path filters, query terms) so each response stays under 16 MiB
  3. Split the work into multiple smaller tool calls when neither side is under your control

Example fix

// before: server inlines the whole file in one result
content = fs.read_text(path).await?;

// after: page the content
content = fs.read_text_range(path, offset, limit).await?;
Defensive patterns

Strategy: fallback

Try / catch

On this deterministic cap rejection, fall back to a narrower request rather than repeating the call:
```rust
match tool_call(&args).await {
    Err(e) if e.to_string().contains("Content-Length") && e.to_string().contains("exceeds") => {
        tool_call(&args.with_page(next_page())).await // paginate
    }
    other => other?,
}
```

Prevention

When it happens

Trigger: A tool result or resource read returns a declared body over 16 MiB — e.g. a filesystem or grep MCP tool inlining an entire large file in one response.

Common situations: Tools without pagination reading big files or logs; servers embedding large base64 blobs; hostile servers probing client limits.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/d09585e2cd2be006. Report an issue: GitHub.