github / github/copilot-sdk

Python: no way to answer mcp.headers_refresh_required, so MCP auth headers are fixed for the life of a session

オープン
#2,492 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
主要言語
Java
スター
10.5k
フォーク
1.5k
平均マージ
1日 11時間
マージ済み PR(30日)
128

説明

**Package:** `github-copilot-sdk` (Python). Checked against **1.0.9** and **1.0.11**.

### Summary

For MCP servers behind Entra (workload identity), the `Authorization` header is a
bearer token with a finite lifetime — ours lasts ~24h. `create_session(mcp_servers=...)`
resolves each server's `headers` once, when the session is created, and the Python
SDK exposes no way to replace them afterwards. Any host process outliving its token
permanently loses those servers.

The wire protocol already models exactly this. What is missing is the Python binding.

### The protocol has it

`copilot/generated/session_events.py`:

```python
MCP_HEADERS_REFRESH_REQUIRED = "mcp.headers_refresh_required"
MCP_HEADERS_REFRESH_COMPLETED = "mcp.headers_refresh_completed"
```

`copilot/generated/rpc.py`:

```python
class MCPHeadersHandlePendingHeadersRefreshRequest:
"""Host response: supply dynamic headers or decline this refresh."""
kind: MCPHeadersHandlePendingHeadersRefreshRequestKind # "headers" | "none"
headers: dict[str, str] | None = None
"""Headers to overlay onto the MCP request. Dynamic headers override static
config headers but do not replace SDK-managed request headers."""
```

…plus the `mcp_headers_handle_pending_headers_refresh_request` method.

### The bindings don't

Nothing outside `generated/` references it, in either version:

```console
$ grep -rn "headers_refresh" --include=*.py . | grep -v generated/rpc.py
./generated/session_events.py:203: MCP_HEADERS_REFRESH_REQUIRED = "mcp.headers_refresh_required"
./generated/session_events.py:204: MCP_HEADERS_REFRESH_COMPLETED = "mcp.headers_refresh_completed"
```

Contrast `on_mcp_auth_request`, which *is* wired (`client.py:2155`, and
`_register_mcp_auth_handler` at the session). So a host can answer an OAuth request
but cannot answer a headers refresh.

### Ask

Expose a handler for the pending headers refresh, e.g.
`create_session(..., on_mcp_headers_refresh_request=...)` returning
`{"kind": "headers", "headers": {...}}`, registered the same way
`on_mcp_auth_request` already is.

---

### Why `on_mcp_auth_request` is not a workaround for us

#1669 added the OAuth host-token handlers and covers "refresh/replacement tokens,
upscope, reauth", so this looks like the intended path. We cannot reach it, because
the handler is never called for our servers.

Our servers answer an unauthenticated request with a **relative** resource_metadata
reference:

```console
POST /mcp -> 401
WWW-Authenticate: Bearer resource_metadata="/.well-known/oauth-protected-resource"
```

The CLI appears to fetch that verbatim rather than resolving it against the MCP
request URL, and then abandons the transport (`~/.copilot/logs/process-*.log`):

```
[ERROR] Failed to connect with OAuth in background: Error: Failed to fetch MCP
OAuth protected-resource metadata from /.well-known/oauth-protected-resource
[ERROR] worker quit with fatal: Transport channel closed, when Client(OAuthChallenge { ...401... })
```

Resolved against the request URL (`http://:8000/mcp`, per RFC 3986) the document
is served and valid:

```console
GET http://:8000/.well-known/oauth-protected-resource -> 200
{"resource":"api://","authorization_servers":["https://login.microsoftonline.com//v2.0"],"scopes_supported":[...]}
```

Observed with a deliberately invalid token in `headers` **and** `auth: True` with a
handler registered:

```
EVENT session.mcp_server_status_changed gis pending
EVENT session.mcp_servers_loaded
EVENT session.mcp_server_status_changed gis needs-auth
EVENT session.mcp_server_status_changed gis failed
REPLY:
STATUS: {'gis': 'failed'} | auth callback fired: 0 time(s)
```

Two things would each independently help, and may deserve their own issue — happy to
split this out if you'd prefer:

1. Resolve a relative `resource_metadata` URI reference against the MCP request URL.
2. Fall back to the registered host auth handler when protected-resource discovery
fails, instead of tearing down the transport.

### Why this is easy to miss

The failure is entirely silent. No exception, no callback, no failed turn — the
server just goes `failed` and the model answers without those tools, reporting them
as not existing rather than as having gone away. Nothing in the symptom leads back
to the cause.

### Workaround, for anyone else hitting this

Send the token in the connection `headers` so no challenge is ever issued, and check
server status every turn so expiry is at least visible. That trades a silent failure
for a bounded one, but the session still cannot outlive its token.

コントリビューションガイド

コントリビューションガイドを開く

調査の方向性

Start in copilot/client.py around line 2155 and _register_mcp_auth_handler, then inspect generated/rpc.py for mcp_headers_handle_pending_headers_refresh_request and generated/session_events.py for the refresh event names. Trace how on_mcp_auth_request is exposed and registered, then confirm the new handler accepts headers or none and is invoked for pending refreshes.

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python
領域
api, authentication
issue の種類
機能追加
難易度
3/5
見積もり時間
1〜2日
活発さ
活発
明瞭さ
明確に書かれている
初心者へのやさしさ
72/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。