Hmbown/CodeWhale · error

OAuth provider did not return credentials

Error message

OAuth provider did not return credentials

What it means

Thrown after an MCP OAuth callback completes but no token credentials are available. The code first calls handle_callback() (authorization-code exchange), then get_credentials(); the latter returns Option and None means the flow finished its redirect yet never persisted a token response. It signals a provider-side anomaly (token exchange skipped or failed silently) or flow state consumed/cleared between callback and read.

Source

Thrown at crates/tui/src/mcp/oauth.rs:870

                })?
                .context("OAuth callback was cancelled")?;
            let OauthCallbackResult { code, state } = match callback {
                CallbackResult::Success(callback) => callback,
                CallbackResult::Error(error) => return Err(anyhow!(error)),
            };

            self.oauth_state
                .handle_callback(&code, &state)
                .await
                .context("handling MCP OAuth callback")?;

            let (client_id, credentials) = self
                .oauth_state
                .get_credentials()
                .await
                .context("reading MCP OAuth credentials")?;
            let credentials =
                credentials.ok_or_else(|| anyhow!("OAuth provider did not return credentials"))?;
            let stored = StoredMcpOAuthTokens {
                server_name: self.server_name.clone(),
                url: self.server_url.clone(),
                client_id,
                expires_at: compute_expires_at_millis(&credentials),
                token_response: WrappedOAuthTokenResponse(credentials),
            };
            save_oauth_tokens(&stored)
        }
        .await;

        drop(self.guard);
        result
    }
}

async fn start_authorization(
    server_url: &str,

View on GitHub (pinned to 8880682c63)

Solutions

  1. Restart the MCP OAuth login from the beginning to rebuild the flow state
  2. Verify the server's OAuth metadata advertises a working token endpoint that returns an access_token on exchange
  3. Ensure only one browser/tab completes the callback for a single login attempt
  4. Inspect earlier log lines for a token-exchange failure inside handle_callback that preceded this invariant break
Defensive patterns

Strategy: retry

Try / catch

Match on the message after the callback step; on this error restart the entire authorize flow (not just get_credentials) and cap attempts:
```rust
match complete_mcp_oauth_callback().await {
    Err(e) if e.to_string().contains("OAuth provider did not return credentials") => {
        restart_authorize_flow().await? // rebuild flow state; max 2 attempts
    }
    other => other?,
}
```

Prevention

When it happens

Trigger: Completing the localhost OAuth redirect for an MCP server (handle_callback succeeds) while get_credentials() returns None: a provider that redirects with a code but stores no tokens, or two concurrent callbacks where the first consumed the flow's credentials.

Common situations: MCP servers whose OAuth implementation performs the redirect but not a usable token exchange; a stale browser tab finishing an old authorize request after a new login attempt started; providers with unusual PKCE or token-endpoint behavior.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/ea3435a43798c1b1. Report an issue: GitHub.