Hmbown/CodeWhale · error
stored MCP OAuth credential for server
Error message
stored MCP OAuth credential for server {} was rejected by the provider ({reason}) and removed; the server requires OAuth login again What it means
Raised in McpOAuth refresh_and_persist when the provider rejects a stored refresh/access token (rejection reason defaults to "unauthorized"). The stored credential is removed as a side effect and the user is told to log in again, because an invalid grant cannot be recovered programmatically.
Solutions
- Run the MCP OAuth login flow again for that server (e.g. /mcp login <server> or `codewhale mcp login <server>`); the stale token has already been deleted
- Check the {reason} in the message: "unauthorized"/"invalid_grant" confirms the token is dead and re-login is the only fix
- If tokens were rotated on another machine, that is expected — re-login here
- If re-login immediately fails with the same rejection, verify the server's OAuth client registration (client_id/redirect URL) with the provider
Example fix
// no code fix; re-authenticate /mcp login my-server // or codewhale mcp login my-server
Defensive patterns
Strategy: try-catch
Try / catch
match client.refresh_if_needed().await {
Err(e) if e.to_string().contains("was rejected by the provider") => {
eprintln!("stored MCP token is dead; starting interactive login...");
perform_oauth_login_for_server(&server).await?;
}
other => other?,
} Prevention
- Re-login periodically; refresh grants do not live forever
- Avoid logging the same account in from many machines that rotate each other's refresh tokens
- Watch for provider-side revocations when changing passwords or scopes
- Handle this error by triggering the interactive login flow, not by retrying the refresh
When it happens
Trigger: Calling refresh_if_needed or force_refresh when the OAuth provider answers a token refresh with a rejection (e.g. HTTP 400 invalid_grant / 401) for a server's stored tokens.
Common situations: Tokens revoked by the user from the provider's dashboard; refresh token expired or rotated elsewhere (the same account logged in from another machine); provider-side session/consent revocation; server or provider policy changed invalidating old grants.
Related errors
- invalid MCP OAuth callback port 0
- MCP OAuth setup cancelled after plugin authority changed
- MCP server ' ' already has bearer/static Authorization…
- MCP server ' ' not found
- MCP server ' ' not found
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/be8fb1353e2b3d46.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/mcp/oauth.rs:729
}
async fn refresh_and_persist(&self) -> Result<()> {
// A credential the provider definitively rejected is never replayed:
// the `AuthorizationManager` still holds it, but every later refresh
// with that grant is a guaranteed `invalid_grant`. The only way back
// is a credential another process stored since (a completed login),
// so adopt that when present and otherwise report auth-required
// without touching the token endpoint.
if self.is_invalidated().await {
if !self.adopt_rotated_on_disk_tokens().await? {
let reason = self
.inner
.rejection
.lock()
.await
.clone()
.unwrap_or_else(|| "unauthorized".to_string());
bail!(
"stored MCP OAuth credential for server {} was rejected by the provider ({reason}) and removed; the server requires OAuth login again",
self.inner.server_name
);
}
let adopted_needs_refresh = {
let last = self.inner.last_tokens.lock().await;
token_needs_refresh(last.as_ref().and_then(|tokens| tokens.expires_at))
};
if !adopted_needs_refresh {
return Ok(());
}
}
// Only this refresh's answer may explain this refresh's failure.
self.inner.http_client.take_token_endpoint_receipt();
let mut err = match self.try_refresh_and_persist().await {
Ok(()) => return Ok(()),
Err(err) => err,
};View on GitHub (pinned to 73e0f67d83)