Remote MCP (OAuth): expired access token forces interactive re-auth instead of silent refresh_token grant
- Lenguaje dominante
- Shell
- Estrellas
- 11.2k
- Forks
- 1.9k
- Merge medio
- 14 h 16 min
- PR fusionados (30 d)
- 6
Descripción
## Summary
When a remote OAuth-protected MCP server's cached **access token** is expired at session start, the Copilot CLI treats the server as needing a fresh **interactive** OAuth login and drops its tools — even though a valid **refresh token** is cached. It never attempts an [RFC 6749 §6](https://www.rfc-editor.org/rfc/rfc6749#section-6) `refresh_token` grant. In non-interactive/automation sessions the interactive flow can't complete, so the server's tools silently never load.
## Environment
- GitHub Copilot CLI `1.0.71`, running inside VS Code
- macOS (Darwin); credential store = Apple Keychain (`copilot-mcp-oauth`)
- MCP transport: `rmcp` streamable HTTP (`rmcp::transport::worker` / `::streamable`), `reqwest 0.13.4`
## Configuration
- Remote HTTP MCP server in `~/.copilot/mcp-config.json`:
```json
"notion": { "type": "http", "url": "https://mcp.notion.com/mcp" }
```
- Cached grant in Keychain (`service copilot-mcp-oauth`, account ``) and `~/.copilot/mcp-oauth-config/.tokens.json` — contains `accessToken`, `expiresAt`, `refreshToken`.
- Server authorization-server metadata ([RFC 8414](https://www.rfc-editor.org/rfc/rfc8414)) advertises refresh support:
`token_endpoint: https://mcp.notion.com/token`, `grant_types_supported: ["authorization_code", "refresh_token"]`
## Steps to reproduce
1. Authenticate a remote OAuth MCP server (e.g. Notion) so a valid access+refresh grant is cached.
2. Let the **access** token expire while the **refresh** token stays valid (Notion access tokens last ~8h — e.g. leave it overnight).
3. Start a new Copilot CLI session (especially a non-interactive/scheduled one).
4. Observe: the server's tools are absent from the session.
## Observed behavior
From `~/.copilot/logs/process-<...>.log` (redacted):
```
[ERROR] Starting remote MCP client for notion with url: https://mcp.notion.com/mcp
[ERROR] Connecting MCP client for notion...
-> connects to mcp.notion.com:443 using the cached (expired) access token
[ERROR] [rust:rmcp::transport::worker] worker quit with fatal: Transport channel closed,
when Client(OAuthChallenge { www_authenticate_header:
"...OAuth, resource_metadata=\".../.well-known/oauth-protected-resource/mcp\",
error=\"invalid_token\", error_description=\"Mis...\"" })
[ERROR] Server notion requires authentication, initiating OAuth flow
[ERROR] OAuth authentication required for notion
```
The runtime then reads the cached OAuth entry from Keychain but still ends at **"OAuth authentication required."**
### Evidence that no refresh was attempted
Grep of the entire session-start process log:
```
POSTs to mcp.notion.com/token : 0
```
Zero `refresh_token` grants were sent, despite a valid refresh token being cached.
## Expected behavior
On a `401` / `WWW-Authenticate: … error="invalid_token"` for a server that has a cached refresh token:
1. Silently POST `grant_type=refresh_token` (+ `client_id`) to the discovered `token_endpoint`.
2. Persist the rotated access/refresh tokens.
3. Reconnect and load the server's tools.
4. Only fall back to the interactive OAuth flow if the refresh itself fails (`invalid_grant`, expired/revoked).
## Impact
- Any remote OAuth MCP server silently loses its tools once the access token expires, until a manual re-auth — even though the refresh token is valid and the whole point of storing it is to avoid exactly this.
- Breaks unattended/scheduled sessions (e.g. a daily automation), where the interactive prompt can never be completed.
## Likely root cause / where to look
- The `rmcp` streamable transport surfaces the OAuth challenge as a **fatal** transport error ("worker quit with fatal … `OAuthChallenge`"), and the CLI's handler maps "auth required" directly to the interactive flow without first attempting a token refresh.
- Secondary: the session's tool list appears to be snapshotted at startup, so even a later successful (re)auth doesn't surface the tools into an already-running session.
## Suggested fix
- Insert a refresh step in the auth-challenge path: if a `refresh_token` is cached for the server, perform the refresh grant against the discovered `token_endpoint`, persist rotated tokens, and reconnect **before** considering interactive auth.
- Optionally refresh **proactively** when `expiresAt` is at/near expiry before the first connect.
- Consider re-syncing tools into active sessions after a late auth success.
## Workaround
- Manually perform the `refresh_token` grant against the `token_endpoint`, write the rotated tokens back into the cache; or re-run the CLI's interactive auth for the server before starting the session.
---
cc @connor4312 (VS Code team)
Filed with assistance from GitHub Copilot CLI.
Guía de contribución
Línea de trabajo
Comienza con la ruta OAuthChallenge del rmcp streamable HTTP transport y el controlador CLI que registra "OAuth authentication required." Comprueba los tokens almacenados en caché en ~/.copilot/mcp-oauth-config/.tokens.json y el token_endpoint descubierto; después verifica que un refresh grant persista los tokens rotados y vuelva a conectar con el servidor. Se considera completado cuando los access tokens expirados se actualizan silenciosamente y las herramientas del servidor se cargan sin un inicio de sesión interactivo.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- rust
- Área
- authentication, cli
- Tipo de issue
- Error
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Estado de actividad
- Activo
- Claridad
- Bastante claro
- Aptitud para principiantes
- 48/100