{"record":{"id":"82ea1f210275097d","repo":"Hmbown/CodeWhale","slug":"mcp-streamable-http-rejected-transport-http-url","errorCode":null,"errorMessage":"MCP Streamable HTTP rejected (transport=http url={} status={}): {}","messagePattern":"MCP Streamable HTTP rejected \\(transport=http url=(.+?) status=(.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/streamable_http.rs","lineNumber":104,"sourceCode":"            return Ok(());\n        }\n\n        if !status.is_success() {\n            let body_excerpt = bounded_body_excerpt(response, ERROR_BODY_PREVIEW_BYTES).await;\n            let stale_session = self.session_id.is_some()\n                && is_streamable_http_stale_session_status(status, &body_excerpt);\n            let body_excerpt = self.auth.server_error_preview(&body_excerpt);\n            if stale_session {\n                return Err(StreamableSendError::StaleSession(format!(\n                    \"status={status} body={body_excerpt}\"\n                )));\n            }\n            if is_streamable_http_incompatible_status(status) {\n                return Err(StreamableSendError::Incompatible(format!(\n                    \"status={status} body={body_excerpt}\"\n                )));\n            }\n            return Err(StreamableSendError::Other(anyhow::anyhow!(\n                \"MCP Streamable HTTP rejected (transport=http url={} status={}): {}\",\n                mask_url_secrets(&self.url),\n                status,\n                body_excerpt,\n            )));\n        }\n\n        let content_type = response\n            .headers()\n            .get(CONTENT_TYPE)\n            .and_then(|value| value.to_str().ok())\n            .map(str::to_string);\n        // Reject an over-large declared body before reading anything (fast\n        // path), then bound the read itself so chunked / length-less\n        // responses cannot OOM us either — Content-Length alone does not\n        // protect against a server that streams without declaring a length.\n        if let Some(len) = response.content_length()\n            && len > MAX_MCP_RESPONSE_BYTES as u64","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/mcp/streamable_http.rs#L86-L122","documentation":"A streamable-HTTP send returned a non-success status that is neither stale-session (404 with session-expired body text) nor an incompatible endpoint (404/405/406 become StreamableSendError::Incompatible), so it lands in the generic Other bucket. The message includes the masked URL, the status, and a scrubbed body excerpt. Sibling variants StaleSession and Incompatible carry their own signals.","triggerScenarios":"A request to the streamable /mcp endpoint answers 400 (invalid JSON-RPC), 401/403 (auth), 409, or 5xx — any rejection not classified as stale-session or wrong-endpoint.","commonSituations":"Expired OAuth token; Mcp-Session-Id header invalid after a server restart but the body not matching stale heuristics; server bugs on specific tools; gateway error pages replacing MCP responses.","solutions":["Read the status and body excerpt in the message — they carry the server's reason","On 401/403: re-authenticate via MCP OAuth login and retry","On 400: validate the request JSON-RPC payload against the server's expectations","On 5xx: check server logs; retry with backoff after recovery"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"```rust\n// Prefer the typed variant when StreamableSendError is in scope:\nfn as_streamable_error(err: &anyhow::Error) -> Option<&StreamableSendError> {\n    err.downcast_ref::<StreamableSendError>()\n}\n// Otherwise match the message prefix:\nfn is_streamable_rejected(msg: &str) -> bool {\n    msg.starts_with(\"MCP Streamable HTTP rejected\")\n}\n```","tryCatchPattern":"```rust\nmatch send_streamable(&mut t, request).await {\n    Err(e) => match e.downcast_ref::<StreamableSendError>() {\n        Some(StreamableSendError::Incompatible(_)) => switch_transport_to_sse().await, // wrong endpoint\n        Some(StreamableSendError::StaleSession(_)) => reinitialize(&mut t).await,     // fresh session\n        _ => { log_status_and_body(&e); return Err(e); }                              // this error\n    },\n    ok => ok?,\n}\n```","preventionTips":["Confirm the endpoint speaks streamable HTTP; sibling 404/405/406 (Incompatible) errors mean wrong endpoint/transport","Re-auth after token revocation rather than assuming refresh will succeed"],"tags":["mcp","http","streamable","send-failure"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}