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
- Confirm the URL serves SSE: curl -N must show content-type text/event-stream and events framed by blank lines
- Fix the server to terminate every event block with a blank line (\n\n or \r\n\r\n)
- 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
- Verify servers emit blank-line-terminated event blocks before enabling them (curl -N)
- Never point the SSE transport at endpoints that return raw JSON or file downloads
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
- {} exceeded the {}-page catalogue limit
- {} exceeded the {}-item catalogue limit
- {} exceeded the {}-byte aggregate catalogue limit
- MCP SSE connect cancelled before authentication completed
- MCP SSE connect cancelled before the request completed
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/e575ee14bdf19ee5.
Report an issue: GitHub.