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

  1. Retry the MCP operation — this is often a transient connection drop.
  2. Check proxy/load-balancer timeouts between client and MCP server and raise them.
  3. Check MCP server logs for a crash or premature exit during request handling.
  4. 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

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


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::Rmcp

View on GitHub (pinned to b0637c97ec)