Skip to content

Python: Fix header_provider headers not reaching streamable HTTP transport requests#7218

Merged
eavanvalkenburg merged 2 commits into
microsoft:mainfrom
robbiebusinessacc:contrib/mcp-header-provider-transport-tasks
Jul 21, 2026
Merged

Python: Fix header_provider headers not reaching streamable HTTP transport requests#7218
eavanvalkenburg merged 2 commits into
microsoft:mainfrom
robbiebusinessacc:contrib/mcp-header-provider-transport-tasks

Conversation

@robbiebusinessacc

Copy link
Copy Markdown
Contributor

Motivation & Context

MCPStreamableHTTPTool.header_provider headers never reach the MCP server on real
streamable HTTP connections. call_tool() stores the provider's output in a
contextvars.ContextVar, but the transport sends the actual HTTP requests from tasks
the MCP SDK spawns at connect time (post_writer handing requests to tg.start_soon).
Those tasks copied their context before call_tool() ran, so the httpx request hook
always reads an empty dict and the per-call headers (e.g. Authorization) are silently
dropped. The existing unit tests read the ContextVar from the caller's task, which is
why they pass while real connections fail.

Description & Review Guide

  • What are the major changes?
    • Keep the per-call headers in an instance-level snapshot for the duration of
      call_tool(), in addition to the ContextVar. The request hook falls back to the
      snapshot when the ContextVar is empty, so headers reach requests sent from
      transport tasks. The previous snapshot value is restored when the call completes.
    • Added a regression test that drives the real streamable_http_client transport
      against an in-process httpx.MockTransport server (initialize, tools/list,
      tools/call — no network) and asserts the Authorization header arrives on the
      tools/call request. It fails without the fix. Also added a test that the
      snapshot is set during a call and cleared afterwards.
  • What is the impact of these changes? header_provider now works on real
    streamable HTTP connections. No public API changes; existing tests are unchanged
    and passing.
  • What do you want reviewers to focus on? Concurrent call_tool() invocations on
    the 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 initialize handshake, #7079). This PR fixes per-call headers
for tools/call; the two are complementary.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

…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.
Copilot AI review requested due to automatic review settings July 20, 2026 15:53
@giles17 giles17 added the python Usage: [Issues, PRs], Target: Python label Jul 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.MockTransport to ensure Authorization reaches tools/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.

Comment thread python/packages/core/agent_framework/_mcp.py
Comment thread python/packages/core/agent_framework/_mcp.py
Comment thread python/packages/core/tests/core/test_mcp.py
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework
   _mcp.py136911391%241, 247, 356, 375, 596, 675–676, 808, 863, 978, 981, 991, 995, 1042–1043, 1048, 1055–1056, 1063, 1068–1069, 1076–1077, 1081, 1086–1087, 1096, 1103–1104, 1122, 1135, 1159–1160, 1179–1182, 1184–1185, 1189, 1215, 1249–1251, 1253, 1306–1308, 1367–1368, 1637, 1678–1679, 1692, 1695, 1704–1705, 1710–1711, 1717, 1771–1772, 1792–1793, 1802–1803, 1808–1809, 1815, 1908, 1911, 1938, 1961–1965, 1988–1990, 1995, 1999–2000, 2102, 2109, 2111, 2182, 2197–2198, 2205–2206, 2211–2212, 2217, 2221, 2236, 2298, 2481, 2483, 2505, 2507–2510, 2523–2524, 2568, 2630, 3055–3056, 3293–3294, 3312
TOTAL44839510388% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
9140 33 💤 0 ❌ 0 🔥 2m 20s ⏱️

…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.
@robbiebusinessacc

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

@eavanvalkenburg
eavanvalkenburg added this pull request to the merge queue Jul 21, 2026
Merged via the queue into microsoft:main with commit a4f02aa Jul 21, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: mcp server can not receive self-define header

5 participants