Kuberwastaken/claurst · error

MCP server ' ': HTTP from legacy SSE transport

Error message

MCP server '{}': HTTP {} from legacy SSE transport: {}

What it means

After the SSE stream is established, JSON-RPC messages are POSTed to the server's message endpoint. This guard accepts 2xx and 202 Accepted, but any other non-success status on a POST response is fatal and reported with the server name, status, and body.

Solutions

  1. Reconnect the MCP session so a fresh endpoint event updates the POST URL
  2. Refresh OAuth credentials (re-run auth) if the status is 401/403
  3. Back off and retry if the status is 429, honoring any Retry-After header
  4. Inspect the body for 5xx and check server/proxy health
Defensive patterns

Strategy: retry

Try / catch

match client.send(msg).await {
    Err(e) if e.to_string().contains("from legacy SSE transport: HTTP") => {
        // 401/403: refresh credentials; 429: back off; 5xx: reconnect session
    }
    other => other?,
}

Prevention

When it happens

Trigger: handle_legacy_sse_http_response (reached from send and the message-queue handlers) receives an HTTP response whose status is neither success nor 202 ACCEPTED.

Common situations: Message endpoint URL became stale after a server restart; auth token expired mid-session (401); server rate-limiting (429); endpoint path wrong in proxy config (404); server crash returning 5xx.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/b68141bbd4896583. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/mcp/src/rmcp_backend.rs:445

    serde_json::from_str(data).map_err(|e| {
        anyhow::anyhow!(
            "MCP server '{}': failed to parse legacy SSE JSON payload: {}",
            server_name,
            e
        )
    })
}

async fn handle_legacy_sse_http_response(
    server_name: String,
    response: reqwest::Response,
    incoming_tx: mpsc::UnboundedSender<rmcp::service::RxJsonRpcMessage<RoleClient>>,
    background_tasks: Arc<StdMutex<Vec<JoinHandle<()>>>>,
) -> anyhow::Result<()> {
    let status = response.status();
    if !status.is_success() && status != reqwest::StatusCode::ACCEPTED {
        let body = response.text().await.unwrap_or_default();
        anyhow::bail!(
            "MCP server '{}': HTTP {} from legacy SSE transport: {}",
            server_name,
            status,
            body
        );
    }

    if status == reqwest::StatusCode::ACCEPTED {
        return Ok(());
    }

    if transport::is_event_stream_response(&response) {
        let server_name_for_task = server_name.clone();
        let task = tokio::spawn(async move {
            if let Err(e) = transport::process_sse_response(response, |_, data| {
                if data.trim().is_empty() {
                    return Ok(());
                }

View on GitHub (pinned to b0637c97ec)