github / github/copilot-cli

Remote MCP (OAuth): expired access token forces interactive re-auth instead of silent refresh_token grant

Abierto
#4,203 1 comentario 3 reacciones 0 asignados Ver en GitHub
area:authentication area:mcp
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

Abrir la 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

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.