[BUG] MCP OAuth token exchange sends wrong resource value on first authentication against a newly-added server

Status Open
Reported on v2.1.281
Maintainer reply None cached
Activity 2 comments · opened Sep 24, 2026

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:

  1. claude mcp add --transport http <name> https://<account>.api.<domain>/mcp
  2. Run /mcp, select the server, complete the browser consent flow.
  3. Observe the /o/authorize redirect URL: it correctly includes resource=https%3A%2F%2F<account>.api.<domain>%2Fmcp (percent-encoded, with the /mcp path).
  4. Consent succeeds.
  5. 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.

  1. 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_

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗