tinyhumansai/openhuman · error · anyhow::Error

MCP HTTP {} — {}

Error message

MCP HTTP {} — {}

What it means

The main Streamable-HTTP JSON-RPC POST returned a non-2xx, non-401 status and the client bails with the status code and response body. (401 is handled separately and returned as a typed `McpUnauthorizedError` so the UI can surface 'needs authentication'.) This is the generic transport-level failure for HTTP MCP calls: `initialize`, `tools/list`, `tools/call` all land here on server rejection.

Source

Thrown at src/openhuman/mcp/http_client/client.rs:777

            .get(reqwest::header::CONTENT_TYPE)
            .and_then(|v| v.to_str().ok())
            .unwrap_or("")
            .to_string();
        if status == reqwest::StatusCode::UNAUTHORIZED {
            let resource_metadata = parse_www_authenticate_challenge(&headers)
                .and_then(|challenge| challenge.resource_metadata);
            // Return a TYPED error (not a string `bail!`) so callers can
            // `downcast_ref::<McpUnauthorizedError>()` and surface an
            // actionable "needs authentication" state (#3719) rather than a
            // generic failure. `anyhow` preserves the root type through `?`.
            return Err(anyhow::Error::new(McpUnauthorizedError {
                endpoint: redact_endpoint(&self.endpoint),
                resource_metadata,
            }));
        }
        if !status.is_success() {
            let text = response.text().await.unwrap_or_default();
            anyhow::bail!("MCP HTTP {} — {}", status.as_u16(), text);
        }

        let payload: Value = if content_type.starts_with("text/event-stream") {
            // Read the SSE body incrementally and return as soon as the single
            // JSON-RPC reply frame arrives, instead of buffering to stream-close
            // (`response.text().await`). A server that holds the stream open
            // after replying would otherwise stall this call until the request
            // timeout, and those stalls compound across a skill's tool chain
            // (#4195). The request-level reqwest timeout still bounds the worst
            // case (a server that never replies).
            let mut raw: Vec<u8> = Vec::new();
            let mut stream = response.bytes_stream();
            let mut frame: Option<Value> = None;
            while let Some(chunk) = stream.next().await {
                raw.extend_from_slice(&chunk?);
                // Decode the whole buffer each pass so a multi-byte UTF-8
                // sequence split across chunk boundaries is never corrupted.
                if let Some(data) = first_complete_sse_data(&String::from_utf8_lossy(&raw))? {

View on GitHub (pinned to 7491200858)

Solutions

  1. Read the embedded status: 404 → fix the endpoint URL; 400 → inspect the request body claim in the message; 429 → back off and retry with jitter.
  2. For auth-related 403/expire, re-authenticate the server and retry.
  3. If the session was lost (often after a server restart), re-initialize to get a fresh `Mcp-Session-Id`.
  4. Retry idempotent calls once after a short backoff for transient 5xx.
Defensive patterns

Strategy: retry

Try / catch

match client.post_jsonrpc(/* ... */).await {
    Err(e) if e.to_string().contains("MCP HTTP 429") => {
        tokio::time::sleep(Duration::from_secs(2)).await;
        client.post_jsonrpc(/* ... */).await // retry once after backoff
    }
    Err(e) if e.to_string().contains("MCP HTTP 404") => Err(e), // wrong endpoint: fix config
    other => other,
}

Prevention

When it happens

Trigger: 404 from a wrong endpoint URL; 400 bad request from protocol/gateway validation; 403 forbidden from auth scopes; 5xx from the server or a proxy; 429 rate limiting; session-id invalid after server restart.

Common situations: Endpoint URL typo or missing `/mcp` path; expired bearer/short-lived token for servers without refresh; API rate limits on hosted MCP servers; upstream outage; cloudflare-style WAF blocking JSON-RPC POSTs.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/430360e0dea974e1. Report an issue: GitHub.