{"record":{"id":"5aaa83a776a8b39c","repo":"tinyhumansai/openhuman","slug":"mcp-unauthorized-for-http-401","errorCode":null,"errorMessage":"MCP unauthorized for `{}` (HTTP 401)","messagePattern":"MCP unauthorized for `(.+?)` \\(HTTP 401\\)","errorType":"http","errorClass":"McpUnauthorizedError","httpStatus":401,"severity":"error","filePath":"src/openhuman/mcp/http_client/client.rs","lineNumber":770,"sourceCode":"};\n\nimpl McpHttpClient {\n    async fn read_response(&self, response: reqwest::Response) -> anyhow::Result<ResponseEnvelope> {\n        let status = response.status();\n        let headers = response.headers().clone();\n        let content_type = headers\n            .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();","sourceCodeStart":752,"sourceCodeEnd":788,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/mcp/http_client/client.rs#L752-L788","documentation":"The MCP HTTP client received HTTP 401 from a remote MCP server, meaning the stored OAuth/session credential was rejected or is missing/expired. The client parses the WWW-Authenticate challenge (RFC 9728 resource metadata) to support re-auth. This error maps to McpUnauthorizedError, which observability classifies as McpServerNeedsAuth so the UI can prompt re-authorization rather than showing a generic failure.","triggerScenarios":"Thrown at src/openhuman/mcp/http_client/client.rs:770 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Re-run the OAuth/connect flow for that MCP server from connections settings to obtain a fresh token","Check the server's token expiry requirements and refresh-token rotation; revoke stale tokens server-side if needed","Verify the resource-metadata URL in the WWW-Authenticate challenge is reachable if the client must discover the authorization server"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}