[BUG] MCP OAuth: token exchange silently fails for Robinhood MCP servers -- empty accessToken persisted, empty error from complete_authentication (manual exchange with identical params succeeds)
Preflight Checklist
- [x] I have searched existing issues -- closest are #65656, #59725, #60260 (same symptom family); this report adds root-cause evidence from a manual reproduction of the exchange
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code (2.1.167)
What's Wrong?
Native MCP OAuth against Robinhood's MCP servers (https://agent.robinhood.com/mcp/trading, https://banking-agent.robinhood.com/mcp/banking, both type: http) never produces a usable token. The browser authorize leg succeeds every time, but Claude Code persists "accessToken": "" (empty string) into ~/.claude/.credentials.json under mcpOAuth, and the server stays in "needs authentication".
Two failure presentations:
- Remote session (paste-callback path):
complete_authenticationreturnsAuthentication failed for robinhood-trading:-- error detail is empty, hiding the real failure. - Local session (localhost listener path): flow appears to succeed silently, but the stored
accessTokenis still"".
Evidence the failure is in Claude Code's exchange/persist step, not the provider
I reproduced the exact flow in a standalone Python script: same client_id (from Claude Code's own dynamic registration), same authorize endpoint, same token endpoint, PKCE S256, token_endpoint_auth_method: none, same resource parameter -- and it succeeds:
POST https://api.robinhood.com/oauth2/token/returns 200 with a valid Bearer token (expires_in~344000) + refresh token- The token works: MCP
initializeagainst the server returns 200 - Manually writing that token into the existing
mcpOAuthentry in.credentials.jsonfully restores function -- after restart both servers list and serve all tools
One provider quirk that may be relevant to the parser: Robinhood's token response includes non-standard extra fields alongside the standard ones:
keys: ['access_token', 'backup_code', 'expires_in', 'mfa_code', 'refresh_token', 'scope', 'token_type', 'user_uuid']
Provider metadata details (path-inserted RFC 8414 form; the suffix form 404s):
https://agent.robinhood.com/.well-known/oauth-authorization-server/mcp/trading- token endpoint
https://api.robinhood.com/oauth2/token/, grant typesauthorization_code+refresh_token, PKCES256, auth methodnone - scopes differ per server: trading
internal, bankingcredit-card
What Should Happen?
- The token exchange should succeed (it does when driven manually with identical parameters), and the access token should be persisted.
- If the exchange fails, the error message should include the failure detail --
Authentication failed for <server>:with nothing after the colon makes diagnosis impossible. - An empty-string token should arguably never be persisted as if auth completed.
Steps to Reproduce
claude mcp add --transport http robinhood-trading https://agent.robinhood.com/mcp/trading(requires a Robinhood account)- Run the
authenticatetool, complete the browser flow (succeeds) - Observe: server still needs auth;
~/.claude/.credentials.jsonmcpOAuthentry has"accessToken": "" - Paste-callback variant:
complete_authenticationfails with empty error detail
Environment
- Claude Code 2.1.167, native Windows terminal (PowerShell), Windows 11 Pro
- Reproduced across multiple sessions and multiple days (entries for both servers show the same empty-token state from independent attempts)
Claude Model
Opus 4.8
Is this a regression?
I don't know -- first attempt to use these servers.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗