{"record":{"id":"e575ee14bdf19ee5","repo":"Hmbown/CodeWhale","slug":"mcp-sse-frame-exceeded-bytes-without-a-separato","errorCode":null,"errorMessage":"MCP SSE frame exceeded {} bytes without a separator — aborting","messagePattern":"MCP SSE frame exceeded (.+?) bytes without a separator — aborting","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/sse.rs","lineNumber":158,"sourceCode":"                tracing::debug!(\"SSE loop cancelled\");\n                break;\n            }\n            let item = tokio::select! {\n                _ = cancel_token.cancelled() => {\n                    tracing::debug!(\"SSE loop shutting down\");\n                    break;\n                }\n                item = stream.next() => {\n                    match item {\n                        Some(i) => i,\n                        None => break,\n                    }\n                }\n            };\n            let chunk = item?;\n            buffer.extend_from_slice(&chunk);\n            if buffer.len() > MAX_SSE_FRAME_BYTES {\n                anyhow::bail!(\n                    \"MCP SSE frame exceeded {} bytes without a separator — aborting\",\n                    MAX_SSE_FRAME_BYTES\n                );\n            }\n\n            while let Some((pos, separator_len)) = find_sse_event_separator_bytes(&buffer) {\n                // Complete block: decoding cannot split a multi-byte char.\n                let event_block = String::from_utf8_lossy(&buffer[..pos]).into_owned();\n                buffer.drain(..pos + separator_len);\n\n                let mut event_type = \"message\";\n                let mut data = String::new();\n\n                for line in event_block.lines() {\n                    if let Some(value) = sse_field_value(line, \"event:\") {\n                        event_type = value;\n                    } else if let Some(value) = sse_field_value(line, \"data:\") {\n                        if !data.is_empty() {","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/mcp/sse.rs#L140-L176","documentation":"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.","triggerScenarios":"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.","commonSituations":"Endpoint misconfiguration returning non-SSE content; buggy server forgetting the trailing \\n\\n; a server or intermediary dribbling bytes indefinitely (slow-loris shape).","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"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"```rust\n// Smoke test: does the endpoint frame SSE events (blank-line separators) at all?\nasync fn sse_has_separator(client: &reqwest::Client, url: &str, cap: usize) -> bool {\n    use futures_util::StreamExt;\n    let mut stream = match client.get(url).send().await { Ok(r) => r.bytes_stream(), Err(_) => return false };\n    let mut buf = Vec::new();\n    while let Some(Ok(chunk)) = stream.next().await {\n        buf.extend_from_slice(&chunk);\n        if buf.windows(2).any(|w| w == b\"\\n\\n\") { return true; }\n        if buf.len() > cap { return false; }\n    }\n    false\n}\n```","typeGuard":null,"tryCatchPattern":"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.","preventionTips":["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"],"tags":["mcp","sse","memory-safety","limits"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}