anthropics / anthropics/claude-code
[BUG] MCP client shows "token expired" for a 403 insufficient_scope response
- Vorherrschende Sprache
- Python
- Sterne
- 145k
- Forks
- 23.1k
- PR-Merge-Kennzahlen
- PR-Kennzahlen ausstehend
Beschreibung
### What happens
An MCP server returns `403` with `WWW-Authenticate: Bearer error="insufficient_scope"` when a token lacks the scope for a tool. Claude Code shows the user:
```
MCP server "..." requires re-authorization (token expired)
```
The token was **not** expired. It had ~55 minutes of its 1-hour lifetime remaining. The real problem was a missing scope on the grant.
### Why it matters
The message sends you to debug the wrong thing. We spent a full session investigating token lifetimes and refresh handling before reading server logs and finding a 403, not a 401.
### Server response
Read tools need `events:read`, write tools need `events:write`. The grant only carried `events:read`.
```
POST /api/mcp (tools/call: events_list) -> 200
POST /api/mcp (tools/call: events_get) -> 200
POST /api/mcp (tools/call: events_delete) -> 403
```
The 403 carries:
```http
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
scope="events:write",
resource_metadata="https://example.com/.well-known/oauth-protected-resource/api/mcp"
{"jsonrpc":"2.0","id":null,
"error":{"code":-32600,"message":"Insufficient scope: events:write required"}}
```
### What we tested
Server-side logs for the whole session confirm:
1. **No 401 was ever returned.** Only two 403s. The client reported an expiry for a response that never claimed one.
2. **No refresh was attempted.** Zero `POST /oauth/token` with `grant_type=refresh_token`. The only token call was the `authorization_code` exchange *after* manual re-auth.
3. **The token was valid.** Access-token TTL 3600s, token issued ~5 minutes before the failure.
4. **Re-auth "fixed" it by widening scopes, not by refreshing.** The second `/authorize` requested `scope=events:read events:write`; the first had not. The same delete then succeeded.
### Expected
Distinguish the two `WWW-Authenticate` error codes:
- `error="invalid_token"` (401) -> "token expired", re-authorize
- `error="insufficient_scope"` (403) -> say the scope is missing, and name it
Ideally, perform step-up authorization using the `scope` from the challenge. At minimum, don't call it an expiry.
### Environment
- Claude Code 2.1.258
- MCP spec 2026-07-28, OAuth 2.1 AS, TypeScript SDK v2
- Server returns the challenge per [spec §authorization](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization)
### Related
Previously reported and closed without a fix: #19066, #28258, #44652. #28258 got "resolved in the next release" (2026-03); #44652 reported the same behavior six weeks later on 2.1.92, and a commenter confirmed it on 2.1.121. All three are locked, so this is a fresh report. Still reproducing on **2.1.258**.
Beitragsleitfaden
Für dieses Repository ist kein Beitragsleitfaden indexiert
Rechercherichtung
No source file or test is named. Start by locating the MCP client's handling of HTTP 401/403 responses and WWW-Authenticate challenges, then compare invalid_token with insufficient_scope using the provided response. Done means a 403 insufficient_scope response reports the missing scope instead of token expiry, while 401 invalid_token retains the expiry behavior.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- typescript
- Bereich
- api, authentication
- Issue-Typ
- Bug
- Schwierigkeit
- 3/5
- Geschätzter Aufwand
- 1-2 Tage
- Aktivitätsstatus
- Aktiv
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 52/100