anthropics / anthropics/claude-code
[BUG] MCP client shows "token expired" for a 403 insufficient_scope response
- Lenguaje dominante
- Python
- Estrellas
- 145k
- Forks
- 23.1k
- Métricas de merge de PR
- Métricas de PR pendientes
Descripción
### 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**.
Guía de contribución
No hay ninguna guía de contribución indexada para este repositorio
Línea de trabajo
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.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- typescript
- Área
- api, authentication
- Tipo de issue
- Error
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Estado de actividad
- Activo
- Claridad
- Bastante claro
- Aptitud para principiantes
- 52/100