Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

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

Open
#2,492 0 comments 0 reactions 0 assignees View on GitHub

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

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:

  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.

Dominant language
Java
Stars
10.5k
Forks
1.5k
Avg merge
1d 9h
Merged PRs (30d)
130

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from github/copilot-sdk

All issues in github/copilot-sdk

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.