anthropics / anthropics/claude-code
MCP OAuth: no scope param sent in authorize request -> Cognito issues an unexchangeable code (invalid_grant), even after working around #67258's DCR issue
- Lingua principale
- Python
- Stelle
- 145k
- Fork
- 23.1k
- Metriche di merge delle PR
- Metriche PR in attesa
Descrizione
# MCP OAuth: no `scope` param sent in authorize request → Cognito issues an unexchangeable code (`invalid_grant`), even after working around #67258's DCR issue
## Summary
Filed as a related report to #67258 (DCR fallback) and #12077 (scope omitted even when the protected resource advertises one) — this adds a fully isolated, byte-for-byte proof of the mechanism, plus one new data point: the *authorization server's own* discovery metadata often has usable scopes even when the *protected resource's* metadata doesn't, and Claude Code doesn't fall back to it.
**Environment:** Claude Code 2.1.246, native Windows (PowerShell + Git Bash), Windows 11 Pro. MCP server: an AWS Bedrock AgentCore Gateway with a `CUSTOM_JWT` authorizer backed by Amazon Cognito.
## What's wrong
1. Adding this Gateway with `claude mcp add --transport http` fails immediately with `Incompatible auth server: does not support dynamic client registration` (#67258 — Cognito has no RFC 7591 DCR endpoint).
2. Working around that with `--client-id --callback-port ` gets past DCR — the browser reaches Cognito's real hosted-UI login, "succeeds" there — but the flow then fails with `invalid_grant` at the token-exchange step. `~/.claude/.credentials.json` shows the same empty-token signature reported elsewhere in this bug family: `"accessToken": "", "expiresAt": 0`.
## Root cause, proven (not inferred)
Debug log (`--debug-file`) shows Claude Code never determines a scope to request at all:
```
MCP server "": Fetched OAuth metadata with scope: NONE
...
MCP server "": Authorization URL: https:///oauth2/authorize?response_type=code&client_id=&code_challenge=...&code_challenge_method=S256&redirect_uri=http%3A%2F%2Flocalhost%3A%2Fcallback&state=...&resource=
MCP server "": Scopes in URL: NOT FOUND
MCP server "": No scopes available from URL or metadata
```
No `scope` parameter anywhere in the authorize URL. Captured via an intercepting proxy, the Gateway's own `.well-known/oauth-protected-resource` response is:
```json
{
"authorization_servers": ["https://cognito-idp..amazonaws.com/"],
"resource": "",
"scopes_supported": []
}
```
Empty. So on its own terms Claude Code has nothing to put in `scope=`. But — critically — **the identity provider's own discovery document does have usable scopes**, which Claude Code never consults:
```
$ curl -s https://cognito-idp..amazonaws.com//.well-known/openid-configuration | jq .scopes_supported
["openid", "email", "phone", "profile"]
```
The subsequent token exchange (captured via proxy, same request Claude Code sent, reproduced twice — an apparent internal retry, both attempts byte-identical and both immediately rejected):
```
POST https:///oauth2/token
grant_type=authorization_code
code=
code_verifier=
redirect_uri=http://localhost:/callback
resource=
client_id=
← 400 {"error":"invalid_grant"}
```
**Isolated proof this is specifically the missing `scope`, not `resource` or anything else:** I replayed this exact flow manually, entirely outside Claude Code, three ways, using a fresh authorization code each time (single-use codes, so each needed its own real browser login):
| Variant | Result |
|---|---|
| Same request Claude Code sends (no `scope`, has `resource`) | `400 invalid_grant` |
| Same request with `resource` stripped (proxy-rewritten in flight), no `scope` added | `400 invalid_grant` — rules out `resource` as the cause |
| Same request with `scope=openid email profile` added at the **authorize** step (nothing else changed) | `200 OK`, real `access_token`/`id_token`/`refresh_token` returned |
Adding `scope` at authorize time — and nowhere else — is the only variable that flips this from failure to success.
## What should happen
When the protected resource's `scopes_supported` is empty or absent, Claude Code's MCP OAuth client should fall back to requesting at least `openid` (or whatever the *authorization server's own* discovery document lists, if consulted) rather than sending no `scope` parameter at all. This is the same underlying defect #12077 reports (scope omitted from the authorize URL) — that report shows it happening even when the protected resource *does* advertise a non-empty `scopes_supported`, which suggests the client isn't wiring discovered scopes into the authorize request at all, regardless of source. This report adds: (a) a controlled, isolated proof of exactly which parameter fixes it, and (b) the authorization-server-metadata fallback as a concrete, always-available second source when the protected resource gives nothing.
## Related
- #67258 — DCR fallback with a pre-configured client id (this Gateway hits that too; had to work around it first to even reach the scope bug above).
- #12077 — closed/locked; same missing-scope symptom, different metadata shape.
- #35846 / #17274 — an earlier, unrelated theory for this project's own auth failures (Cognito not advertising `code_challenge_methods_supported`); directly disproven by the debug log above, since PKCE (`code_challenge`/`S256`) is sent correctly. Noted here only so this doesn't get merged with that theory by mistake.
## Steps to reproduce
1. Stand up (or use) an MCP server behind an OAuth-protected resource whose `.well-known/oauth-protected-resource` has an empty `scopes_supported`, backed by an authorization server that requires an explicit `scope` at `/authorize` to issue an exchangeable code (Cognito does).
2. `claude mcp add --transport http --client-id --callback-port `
3. `claude --debug-file mcp login `
4. Complete the browser login (succeeds).
5. Observe `invalid_grant` on completion; debug log shows `Scopes in URL: NOT FOUND` / `No scopes available from URL or metadata`.
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Direzione di ricerca
Start with `claude --debug-file mcp login ` and inspect the MCP OAuth metadata and authorization-URL flow used after `claude mcp add --transport http`. Reproduce against a protected resource with empty `scopes_supported` and Cognito discovery listing scopes; done means the authorize request includes an appropriate scope and the resulting code exchanges successfully instead of returning `invalid_grant`.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- aws, python
- Ambito
- api, authentication, security
- Tipo di issue
- Bug
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Stato di attività
- Attiva
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 55/100