Hmbown/CodeWhale · error

MCP SSE frame exceeded {} bytes without a separator — aborti

Error message

MCP SSE frame exceeded {} bytes without a separator — aborting

What it means

The SSE reader accumulates raw bytes until it finds an event separator (blank line). The buffer is capped at MAX_SSE_FRAME_BYTES (8 MiB, crates/tui/src/mcp/wire.rs) to prevent a separator-less server from growing it without bound; exceeding the cap aborts the transport as an OOM/DoS guard.

Source

Thrown at crates/tui/src/mcp/sse.rs:158

                tracing::debug!("SSE loop cancelled");
                break;
            }
            let item = tokio::select! {
                _ = cancel_token.cancelled() => {
                    tracing::debug!("SSE loop shutting down");
                    break;
                }
                item = stream.next() => {
                    match item {
                        Some(i) => i,
                        None => break,
                    }
                }
            };
            let chunk = item?;
            buffer.extend_from_slice(&chunk);
            if buffer.len() > MAX_SSE_FRAME_BYTES {
                anyhow::bail!(
                    "MCP SSE frame exceeded {} bytes without a separator — aborting",
                    MAX_SSE_FRAME_BYTES
                );
            }

            while let Some((pos, separator_len)) = find_sse_event_separator_bytes(&buffer) {
                // Complete block: decoding cannot split a multi-byte char.
                let event_block = String::from_utf8_lossy(&buffer[..pos]).into_owned();
                buffer.drain(..pos + separator_len);

                let mut event_type = "message";
                let mut data = String::new();

                for line in event_block.lines() {
                    if let Some(value) = sse_field_value(line, "event:") {
                        event_type = value;
                    } else if let Some(value) = sse_field_value(line, "data:") {
                        if !data.is_empty() {

View on GitHub (pinned to 8880682c63)

Solutions

  1. Confirm the URL serves SSE: curl -N must show content-type text/event-stream and events framed by blank lines
  2. Fix the server to terminate every event block with a blank line (\n\n or \r\n\r\n)
  3. Keep individual SSE events under 8 MiB, or switch the server entry to streamable-http transport if the payloads are legitimately huge
Defensive patterns

Strategy: fallback

Validate before calling

```rust
// Smoke test: does the endpoint frame SSE events (blank-line separators) at all?
async fn sse_has_separator(client: &reqwest::Client, url: &str, cap: usize) -> bool {
    use futures_util::StreamExt;
    let mut stream = match client.get(url).send().await { Ok(r) => r.bytes_stream(), Err(_) => return false };
    let mut buf = Vec::new();
    while let Some(Ok(chunk)) = stream.next().await {
        buf.extend_from_slice(&chunk);
        if buf.windows(2).any(|w| w == b"\n\n") { return true; }
        if buf.len() > cap { return false; }
    }
    false
}
```

Try / catch

Do not retry the identical connect — the failure is deterministic for the same response body. On this error, switch the server entry to streamable-http transport or fix the server's SSE framing.

Prevention

When it happens

Trigger: The server streams more than 8 MiB without one blank-line separator: a single giant event, raw JSON-RPC lines with no SSE framing, or a 200 response that is actually a file/HTML page rather than text/event-stream.

Common situations: Endpoint misconfiguration returning non-SSE content; buggy server forgetting the trailing \n\n; a server or intermediary dribbling bytes indefinitely (slow-loris shape).

Related errors


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