Python: no way to answer mcp.headers_refresh_required, so MCP auth headers are fixed for the life of a session
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 72/100
- Issue type
- Feature
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- python
- Domain
- api, authentication
Research direction
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.
Written by the indexing model from the issue text.
Description
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:
MCP_HEADERS_REFRESH_REQUIRED = "mcp.headers_refresh_required"
MCP_HEADERS_REFRESH_COMPLETED = "mcp.headers_refresh_completed"
copilot/generated/rpc.py:
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:
$ 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:
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://<host>:8000/mcp, per RFC 3986) the document
is served and valid:
GET http://<host>:8000/.well-known/oauth-protected-resource -> 200
{"resource":"api://<guid>","authorization_servers":["https://login.microsoftonline.com/<tenant>/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: <model answers using only built-in tools>
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:
- Resolve a relative
resource_metadataURI reference against the MCP request URL. - 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.
- Dominant language
- Java
- Stars
- 10.5k
- Forks
- 1.5k
- Avg merge
- 1d 9h
- Merged PRs (30d)
- 130
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from github/copilot-sdk
-
agentic-workflows
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
github/copilot-sdk#2760 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
github/copilot-sdk#2759 ·
-
documentation
Difficulty 1/5 Under an hour Newbie friendliness 85/100
github/copilot-sdk#2758 ·
-
agentic-workflows
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
github/copilot-sdk#2709 · 1 comment ·
-
Difficulty 1/5 Under an hour Newbie friendliness 78/100
github/copilot-sdk#2673 ·
All issues in github/copilot-sdk
Similar issues
-
awaiting triage bug Causes friction Hop Gui P1 P2 Transforms
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
apache/flink-agents#1152 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
jenkinsci/blueocean-plugin#5417 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
objectionary/eo-graphs#75 ·