{"record":{"id":"be8fb1353e2b3d46","repo":"Hmbown/CodeWhale","slug":"stored-mcp-oauth-credential-for-server-was-rejected-by-the","errorCode":null,"errorMessage":"stored MCP OAuth credential for server {} was rejected by the provider ({reason}) and removed; the server requires OAuth login again","messagePattern":"stored MCP OAuth credential for server (.+?) was rejected by the provider \\((.+?)\\) and removed; the server requires OAuth login again","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/oauth.rs","lineNumber":729,"sourceCode":"    }\n\n    async fn refresh_and_persist(&self) -> Result<()> {\n        // A credential the provider definitively rejected is never replayed:\n        // the `AuthorizationManager` still holds it, but every later refresh\n        // with that grant is a guaranteed `invalid_grant`. The only way back\n        // is a credential another process stored since (a completed login),\n        // so adopt that when present and otherwise report auth-required\n        // without touching the token endpoint.\n        if self.is_invalidated().await {\n            if !self.adopt_rotated_on_disk_tokens().await? {\n                let reason = self\n                    .inner\n                    .rejection\n                    .lock()\n                    .await\n                    .clone()\n                    .unwrap_or_else(|| \"unauthorized\".to_string());\n                bail!(\n                    \"stored MCP OAuth credential for server {} was rejected by the provider ({reason}) and removed; the server requires OAuth login again\",\n                    self.inner.server_name\n                );\n            }\n            let adopted_needs_refresh = {\n                let last = self.inner.last_tokens.lock().await;\n                token_needs_refresh(last.as_ref().and_then(|tokens| tokens.expires_at))\n            };\n            if !adopted_needs_refresh {\n                return Ok(());\n            }\n        }\n        // Only this refresh's answer may explain this refresh's failure.\n        self.inner.http_client.take_token_endpoint_receipt();\n        let mut err = match self.try_refresh_and_persist().await {\n            Ok(()) => return Ok(()),\n            Err(err) => err,\n        };","sourceCodeStart":711,"sourceCodeEnd":747,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp/oauth.rs#L711-L747","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// no code fix; re-authenticate\n/mcp login my-server\n// or\ncodewhale mcp login my-server","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match client.refresh_if_needed().await {\n    Err(e) if e.to_string().contains(\"was rejected by the provider\") => {\n        eprintln!(\"stored MCP token is dead; starting interactive login...\");\n        perform_oauth_login_for_server(&server).await?;\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["oauth","mcp","token-expired","refresh"],"backgroundTag":"jwt-token-expired","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}