Python: Add origin-scoped headers for MCP connect authentication - #7892
Python: Add origin-scoped headers for MCP connect authentication#7892Shivani . (Shivani767) wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds origin-scoped static headers to authenticate MCP initialization while retaining per-call header overlays.
Changes:
- Adds
headers=support and header resolution. - Adds ambient, overlay, redirect, and initialize tests.
- Documents connect-time versus per-call authentication.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
python/packages/core/agent_framework/_mcp.py |
Implements static header injection. |
python/packages/core/tests/core/test_mcp.py |
Adds authentication regression tests. |
python/packages/core/agent_framework/security.py |
Updates proxy guidance. |
python/packages/core/AGENTS.md |
Documents header behavior. |
python/samples/02-agents/mcp/mcp_api_key_auth.py |
Explains handshake authentication. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
f2482d9 to
cafe2da
Compare
Allow MCPStreamableHTTPTool to authenticate initialize/handshake with static headers while keeping header_provider for per-call overlays, so kwargs-only providers no longer leave connect unauthenticated.
Track headers injected by the MCP request hook and remove them on redirected cross-origin requests, route SecureMCPToolProxy through headers=, and harden the redirect regression tests with X-API-Key.
cafe2da to
7d6952d
Compare
| if _url_origin(request.url) != target_origin: | ||
| # Strip secrets this hook previously injected. HTTPX removes | ||
| # Authorization on cross-origin redirects, but other credentials | ||
| # (e.g. X-API-Key) can remain on the redirected request. | ||
| for key in self._injected_header_keys: | ||
| request.headers.pop(key, None) |
There was a problem hiding this comment.
Following up on the redirect thread: could we track these keys on the request rather than on the tool? _injected_header_keys is the last same-origin request's global set, so an independent request through a shared http_client to another origin loses its own same-named header; concurrent ambient and call traffic can also replace the set before a redirect, leaving the earlier secret attached. A marker in request.extensions would follow the HTTPX redirect chain without affecting unrelated requests.
There was a problem hiding this comment.
Should the client created here be owned and closed by the tool? Passing this AsyncClient into streamable_http_client makes the SDK treat it as caller-owned, so _exit_stack never closes it; every MCPStreamableHTTPTool(headers=...) or URL-mode SecureMCPToolProxy lifecycle leaves its connection pool open. Could the tool register http_client.aclose on _exit_stack only when it creates the client?
|
Shivani . (@Shivani767) please fix the file conflicts as well, thanks. |
|
Re-open when wanting to take this forward. |
Motivation & Context
MCPStreamableHTTPTool.connect()can still reach servers that authenticate the initialize handshake without credentials when auth comes only from a kwargs-dependentheader_provider. Per-call kwargs are not available untilcall_tool(), so those servers get a 401 (or hang) before any tool call runs.#7305 already delivers
header_providerheaders across transport tasks and authenticates ambient requests for static providers. This PR covers the remaining connect-time gap for servers that require handshake auth.Description & Review Guide
What are the major changes?
headers=constructor argument onMCPStreamableHTTPTool.header_provideras a per-call overlay on top ofheaders=.X-API-Keythat HTTPX would otherwise copy).SecureMCPToolProxyURL mode throughheaders=instead of baking tokens intoAsyncClient.What is the impact of these changes?
connect()without baking tokens into a customhttpx.AsyncClient.header_providersetups can still authenticate the handshake viaheaders=while using dynamic tokens for tool calls.header_provider/ custom-client behavior is unchanged for callers that already worked.What do you want reviewers to focus on?
headers=is the right connect-time contract vs changing ambientheader_provider({})behavior.Related Issue
Fixes #7841
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.