[BUG] MCP OAuth token exchange sends wrong resource value on first authentication against a newly-added server
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Environment:
- Claude Code 2.1.281 (latest on npm at time of testing)
- macOS (Darwin 24.6.0)
- Server: a hand-rolled RFC 9728/8707-compliant MCP resource server (Django + django-oauth-toolkit), one instance per account, e.g. https://<account>.api.<domain>/mcp
Summary:
The first time Claude Code completes OAuth against a newly-registered MCP server, the token exchange (POST /o/token) sends a resource parameter that doesn't match the one correctly sent moments earlier at the authorization step (GET /o/authorize). A retry against the same server succeeds. Confirmed reproducible against two independent MCP resource servers on the same deployment — both failed identically on their first authenticated attempt, both succeeded on retry.
Actual behavior: Token exchange sends a resource value derived from the authorization server's issuer URL rather than reusing the resource indicator already used successfully at the authorization step — but only on the first authenticated attempt against a given server; a retry succeeds.
Reproduction (2 for 2):
- Server A (sponsor-cloud): failed on first authenticated attempt with the above error; succeeded on retry.
- Server B (admin-qb-int-dev): had previously only been used anonymously (tools/list, which needs no token). Its first real authenticated attempt (tools/call) failed identically; succeeded on retry.
Both first-time failures show the identical symptom — correct resource at /o/authorize, wrong bare-origin resource at token exchange — and both recovered on a second attempt against the same server. This points to client-side state (issuer/metadata resolution, most likely) that isn't fully populated on the very first token exchange against a given resource, causing a fallback to something derived from the authorization server's issuer URL rather than the resource indicator already used successfully one step earlier.
Server-side context (for triage): the error text comes from the resource server correctly rejecting a resource indicator whose path doesn't match the registered resource's canonical path (/mcp). The server is behaving per spec; the mismatch originates in the client's token-exchange request.
Caveat: the raw /o/token request body wasn't captured directly (a --debug-based capture attempt wasn't completed), so "sends a bare-origin resource" is inferred from the error text rather than observed on the wire. The failure symptom and the retry-recovers pattern are both directly and repeatedly observed; the specific wrong value is the one part still inferred.
What Should Happen?
Expected behavior: Claude Code sends the same resource value at token exchange that it correctly sent at the authorization step, so the first authenticated attempt against a new MCP server succeeds without needing a retry. At minimum, omitting resource= entirely at token exchange would suffice — RFC 8707 requires the authorization server to fall back to the resource already bound to the grant in that case, which this server does.
Error Messages/Logs
Steps to Reproduce
Steps to reproduce:
- claude mcp add --transport http <name> https://<account>.api.<domain>/mcp
- Run /mcp, select the server, complete the browser consent flow.
- Observe the /o/authorize redirect URL: it correctly includes resource=https%3A%2F%2F<account>.api.<domain>%2Fmcp (percent-encoded, with the /mcp path).
- Consent succeeds.
- Claude Code reports a failure from the token exchange:
The resource 'https://<account>.api.<domain>' is not a registered MCP resource for this environment.
The resource in this error has no path — /mcp is missing, unlike the value sent at step 3.
- Retry the same server's auth flow (fresh consent). It succeeds.
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.281
Platform
AWS Bedrock
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
_No response_
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗