Python: Fix header_provider headers not reaching streamable HTTP transport requests#7218
Merged
eavanvalkenburg merged 2 commits intoJul 21, 2026
Conversation
…ests MCPStreamableHTTPTool.call_tool stores header_provider output in a ContextVar, but the streamable HTTP transport sends requests from tasks spawned at connect time, whose contexts never observe values set later. The request hook therefore always read an empty dict on real connections and the per-call headers (e.g. Authorization) were silently dropped. Keep the ContextVar for in-context reads and add an instance-level snapshot of the active call's headers that the request hook falls back to across tasks.
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a Python MCP Streamable HTTP integration bug where MCPStreamableHTTPTool.header_provider headers set during call_tool() were not applied to HTTP requests sent from transport tasks spawned during connect(), causing per-call headers (e.g., Authorization) to be dropped in real connections.
Changes:
- Add an instance-level “active call headers” snapshot and have the httpx request hook fall back to it when the ContextVar is empty.
- Update
call_tool()to manage the snapshot lifecycle for the duration of a tool call. - Add regression tests using a real
streamable_http_client+httpx.MockTransportto ensureAuthorizationreachestools/call, plus a test asserting the snapshot is restored after the call.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| python/packages/core/agent_framework/_mcp.py | Adds instance-level header snapshot fallback for streamable HTTP request hooks during call_tool(). |
| python/packages/core/tests/core/test_mcp.py | Adds regression coverage validating headers reach tools/call over the real streamable HTTP transport. |
Contributor
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
…ader mixing Parallel tool invocations run concurrently per function-invocation batch, so two call_tool invocations on the same MCPStreamableHTTPTool could overwrite each other's active-header snapshot while requests were still in flight, attaching the wrong per-call credentials. Hold a per-instance lock for the duration of a header-bearing call, add a regression test that fails without the lock, and normalize captured header casing in the transport-task test.
Contributor
Author
|
@microsoft-github-policy-service agree |
eavanvalkenburg
approved these changes
Jul 21, 2026
eavanvalkenburg
enabled auto-merge
July 21, 2026 12:08
chetantoshniwal
approved these changes
Jul 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation & Context
MCPStreamableHTTPTool.header_providerheaders never reach the MCP server on realstreamable HTTP connections.
call_tool()stores the provider's output in acontextvars.ContextVar, but the transport sends the actual HTTP requests from tasksthe MCP SDK spawns at connect time (
post_writerhanding requests totg.start_soon).Those tasks copied their context before
call_tool()ran, so the httpx request hookalways reads an empty dict and the per-call headers (e.g.
Authorization) are silentlydropped. The existing unit tests read the ContextVar from the caller's task, which is
why they pass while real connections fail.
Description & Review Guide
call_tool(), in addition to the ContextVar. The request hook falls back to thesnapshot when the ContextVar is empty, so headers reach requests sent from
transport tasks. The previous snapshot value is restored when the call completes.
streamable_http_clienttransportagainst an in-process
httpx.MockTransportserver (initialize, tools/list,tools/call — no network) and asserts the
Authorizationheader arrives on thetools/callrequest. It fails without the fix. Also added a test that thesnapshot is set during a call and cleared afterwards.
header_providernow works on realstreamable HTTP connections. No public API changes; existing tests are unchanged
and passing.
call_tool()invocations onthe same tool instance follow last-writer-wins for the snapshot while their requests
are in flight. Sequential calls are always attributed correctly, and this is strictly
better than current behavior where the headers are never applied — flagging it in
case a stricter scoping is preferred.
Related Issue
Fixes #7161
Open PR #7080 touches the same header machinery but addresses a different gap
(headers during the
initializehandshake, #7079). This PR fixes per-call headersfor
tools/call; the two are complementary.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.