Kuberwastaken/claurst · error
MCP server ' ': failed to read legacy SSE HTTP response body
Error message
MCP server '{}': failed to read legacy SSE HTTP response body: {} What it means
Raised in `handle_legacy_sse_http_response` when the POST to a legacy SSE MCP server succeeded at the HTTP-status level (success or 202) and the response is not an event-stream, but `response.text().await` fails while reading the plain JSON body. This means the connection broke or the body could not be buffered before a complete response was received. The server name and the reqwest error are wrapped so the failing MCP server can be identified.
Solutions
- Retry the MCP operation — this is often a transient connection drop.
- Check proxy/load-balancer timeouts between client and MCP server and raise them.
- Check MCP server logs for a crash or premature exit during request handling.
- If it recurs, switch the MCP server config to the streamable-HTTP transport, which handles connection churn better.
Example fix
// before: single-shot call that surfaces the read failure to the user
let tools = backend.list_tools().await?;
// after: bounded retry for transient body-read failures
let tools = match backend.list_tools().await {
Ok(t) => t,
Err(e) if e.to_string().contains("failed to read legacy SSE HTTP response body") => {
tokio::time::sleep(Duration::from_millis(250)).await;
backend.list_tools().await?
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: retry
Try / catch
match backend.call_tool(name, args).await {
Ok(r) => r,
Err(e) if e.to_string().contains("failed to read legacy SSE HTTP response body") => {
tokio::time::sleep(Duration::from_millis(250)).await;
backend.call_tool(name, args).await?
}
Err(e) => return Err(e),
} Prevention
- Raise proxy/load-balancer read timeouts for long-running MCP tool calls.
- Prefer the streamable-HTTP transport when the server supports it; plain SSE POST bodies are more fragile.
- Retry transient body-read failures with backoff — they are usually not deterministic.
- Monitor MCP server stability; a server that drops connections mid-response will also drop SSE streams.
When it happens
Trigger: Calling any MCP operation whose POST response is a plain JSON body (not SSE, not 202) and the HTTP connection drops while the body is being read: server closes the socket early, network interruption, proxy terminating the connection, or response body larger than allowed and the connection aborted.
Common situations: An intermediary (nginx/Envoy) timing out the request and cutting the connection; server crashing mid-response; flaky network to a remote MCP host; keep-alive race where the server closed an idle connection just as the client reused it.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- failed to resolve legacy SSE endpoint
- MCP server ' ': legacy SSE POST request failed
- legacy SSE endpoint event did not include a POST endpoint
- MCP server ' ': legacy SSE stream returned HTTP
- MCP server ' ': HTTP from legacy SSE transport
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/517bf7296f9c6087.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/mcp/src/rmcp_backend.rs:478
if let Err(e) = transport::process_sse_response(response, |_, data| {
if data.trim().is_empty() {
return Ok(());
}
let message = parse_server_message(&server_name_for_task, data)?;
let _ = incoming_tx.send(message);
Ok(())
})
.await
{
tracing::warn!(server = %server_name_for_task, error = %e, "legacy SSE POST stream closed with error");
}
});
lock_recover(&background_tasks).push(task);
return Ok(());
}
let text = response.text().await.map_err(|e| {
anyhow::anyhow!(
"MCP server '{}': failed to read legacy SSE HTTP response body: {}",
server_name,
e
)
})?;
if text.trim().is_empty() {
return Ok(());
}
let message = parse_server_message(&server_name, &text)?;
let _ = incoming_tx.send(message);
Ok(())
}
#[async_trait]
impl McpClientBackend for RmcpClientBackend {
fn kind(&self) -> McpBackendKind {
McpBackendKind::RmcpView on GitHub (pinned to b0637c97ec)