github / github/copilot-cli

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

未关闭
#4,203 1 条评论 3 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

area:authentication area:mcp
主要语言
Shell
星标
11.2k
派生
1.9k
平均合并
14 小时 16 分钟
30 天内合并 PR
6

描述

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 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:
    "notion": { "type": "http", "url": "https://mcp.notion.com/mcp" }
    
  • Cached grant in Keychain (service copilot-mcp-oauth, account <hash>) and ~/.copilot/mcp-oauth-config/<hash>.tokens.json — contains accessToken, expiresAt, refreshToken.
  • Server authorization-server metadata (RFC 8414) 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.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

从 rmcp streamable HTTP transport 的 OAuthChallenge 路径以及记录 "OAuth authentication required." 的 CLI 处理程序开始。检查 ~/.copilot/mcp-oauth-config/.tokens.json 中缓存的令牌和发现的 token_endpoint,然后验证 refresh grant 是否会持久化轮换后的令牌并重新连接服务器。过期的 access tokens 能够静默刷新,并且服务器的工具无需交互式登录即可加载,即表示完成。

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

评估

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

把新 issue 发到你的邮箱

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