{"record":{"id":"430360e0dea974e1","repo":"tinyhumansai/openhuman","slug":"mcp-http","errorCode":null,"errorMessage":"MCP HTTP {} — {}","messagePattern":"MCP HTTP (.+?) — (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/mcp/http_client/client.rs","lineNumber":777,"sourceCode":"            .get(reqwest::header::CONTENT_TYPE)\n            .and_then(|v| v.to_str().ok())\n            .unwrap_or(\"\")\n            .to_string();\n        if status == reqwest::StatusCode::UNAUTHORIZED {\n            let resource_metadata = parse_www_authenticate_challenge(&headers)\n                .and_then(|challenge| challenge.resource_metadata);\n            // Return a TYPED error (not a string `bail!`) so callers can\n            // `downcast_ref::<McpUnauthorizedError>()` and surface an\n            // actionable \"needs authentication\" state (#3719) rather than a\n            // generic failure. `anyhow` preserves the root type through `?`.\n            return Err(anyhow::Error::new(McpUnauthorizedError {\n                endpoint: redact_endpoint(&self.endpoint),\n                resource_metadata,\n            }));\n        }\n        if !status.is_success() {\n            let text = response.text().await.unwrap_or_default();\n            anyhow::bail!(\"MCP HTTP {} — {}\", status.as_u16(), text);\n        }\n\n        let payload: Value = if content_type.starts_with(\"text/event-stream\") {\n            // Read the SSE body incrementally and return as soon as the single\n            // JSON-RPC reply frame arrives, instead of buffering to stream-close\n            // (`response.text().await`). A server that holds the stream open\n            // after replying would otherwise stall this call until the request\n            // timeout, and those stalls compound across a skill's tool chain\n            // (#4195). The request-level reqwest timeout still bounds the worst\n            // case (a server that never replies).\n            let mut raw: Vec<u8> = Vec::new();\n            let mut stream = response.bytes_stream();\n            let mut frame: Option<Value> = None;\n            while let Some(chunk) = stream.next().await {\n                raw.extend_from_slice(&chunk?);\n                // Decode the whole buffer each pass so a multi-byte UTF-8\n                // sequence split across chunk boundaries is never corrupted.\n                if let Some(data) = first_complete_sse_data(&String::from_utf8_lossy(&raw))? {","sourceCodeStart":759,"sourceCodeEnd":795,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/mcp/http_client/client.rs#L759-L795","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","For auth-related 403/expire, re-authenticate the server and retry.","If the session was lost (often after a server restart), re-initialize to get a fresh `Mcp-Session-Id`.","Retry idempotent calls once after a short backoff for transient 5xx."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match client.post_jsonrpc(/* ... */).await {\n    Err(e) if e.to_string().contains(\"MCP HTTP 429\") => {\n        tokio::time::sleep(Duration::from_secs(2)).await;\n        client.post_jsonrpc(/* ... */).await // retry once after backoff\n    }\n    Err(e) if e.to_string().contains(\"MCP HTTP 404\") => Err(e), // wrong endpoint: fix config\n    other => other,\n}","preventionTips":["Validate the endpoint URL at config time with a cheap handshake call.","Retry only idempotent operations; classify by status (429/5xx retry, 4xx fix config/auth).","Re-initialize after server restarts instead of reusing stale session ids."],"tags":["mcp","http","json-rpc","transport"],"backgroundTag":"http-error-response","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}