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

未关闭
#90,190 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
area:auth area:mcp bug has repro platform:windows
主要语言
Python
星标
145k
派生
23.1k
PR 合并指标
PR 指标待抓取

描述

# 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`.

贡献指南

这个仓库没有索引到贡献指南

调研方向

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`.

由索引模型根据 Issue 内容生成。

评估

技术栈
aws, python
领域
api, authentication, security
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
活跃
描述清晰度
基本清楚
新手友好度
55/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。