Hmbown/CodeWhale · error · StreamableSendError::Other

MCP server rejected the request with and refreshing the…

Error message

MCP server {} rejected the request with {status} and refreshing the OAuth session failed: {refresh_error:#}. {hint}

What it means

When an MCP Streamable HTTP POST is rejected with an auth-related status, the client first attempts to force-refresh the OAuth session and retry. If the refresh itself fails, the original rejection and the refresh error are combined into this message with a hint, so the developer knows both that the server refused the request and why the automatic recovery could not proceed.

Solutions

  1. Re-authenticate with the MCP server (run the OAuth login flow) to obtain fresh tokens
  2. Check reachability and configuration of the OAuth token endpoint
  3. Verify the refresh token/client credentials are still valid with the identity provider
  4. Retry after network connectivity to the IdP is restored
Defensive patterns

Strategy: try-catch

Try / catch

match client.send(request).await {
    Err(e) if e.to_string().contains("refreshing the OAuth session failed") => {
        // automatic refresh failed; prompt a full re-authentication flow
        reauthenticate(server)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: send() receives a 401 (or similar auth rejection) on the POST to the MCP server, calls oauth.force_refresh(), and the refresh fails (token endpoint unreachable, refresh token revoked/expired, client credentials invalid).

Common situations: Refresh token revoked by the identity provider; OAuth token endpoint down or misconfigured; network change broke access to the IdP; expired session after long offline use.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/d5fa858a1ee747fd. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp/streamable_http.rs:119

                && self.session_id.as_deref() != Some(sid)
            {
                let session_ref = crate::utils::redacted_identifier_for_log(sid);
                tracing::debug!(target: "mcp", session = %session_ref, "captured MCP session ID");
                self.session_id = Some(sid.to_string());
            }
            if status == StatusCode::ACCEPTED || status == StatusCode::NO_CONTENT {
                return Ok(());
            }

            if status == StatusCode::UNAUTHORIZED || status == StatusCode::FORBIDDEN {
                if !retried && let Some(oauth) = self.auth.oauth.as_ref() {
                    match oauth.force_refresh().await {
                        Ok(()) => {
                            retried = true;
                            continue;
                        }
                        Err(refresh_error) => {
                            return Err(StreamableSendError::Other(anyhow::anyhow!(
                                "MCP server {} rejected the request with {status} and refreshing the OAuth session failed: {refresh_error:#}. {hint}",
                                mask_url_secrets(&self.url),
                                hint = oauth_refresh_failed_hint(),
                            )));
                        }
                    }
                }
                let hint = unauthorized_session_hint(self.auth.oauth_configured);
                return Err(StreamableSendError::Other(anyhow::anyhow!(
                    "MCP server {} rejected the request with {status}; the session is no longer accepted. {hint}",
                    mask_url_secrets(&self.url),
                )));
            }

            if !status.is_success() {
                let body_excerpt = bounded_body_excerpt(response, ERROR_BODY_PREVIEW_BYTES).await;
                let stale_session = self.session_id.is_some()
                    && is_streamable_http_stale_session_status(status, &body_excerpt);

View on GitHub (pinned to 73e0f67d83)