Python: Add header_provider to Streamable HTTP MCP servers (#4849)

* Python: Add header_provider to MCPStreamableHTTPTool (#4808)

Add a header_provider callback parameter to MCPStreamableHTTPTool that
enables injecting dynamic per-request HTTP headers from runtime kwargs
(originating from FunctionInvocationContext.kwargs set in agent middleware).

The implementation uses contextvars and httpx event hooks to ensure headers
are task-local and safe for concurrent tool calls:

- header_provider receives the runtime kwargs dict and returns headers
- call_tool sets a ContextVar before delegating to MCPTool.call_tool
- An httpx request event hook reads from the ContextVar and injects headers

Example usage:
    mcp_tool = MCPStreamableHTTPTool(
        name="web-api",
        url="https://api.example.com/mcp",
        header_provider=lambda kwargs: {
            "X-Auth-Token": kwargs.get("auth_token", ""),
        },
    )

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address review feedback for #4808: Python: [Bug]: Unable to pass AgentContext to MCPStreamableHTTPTool

* Add test for header_provider via FunctionTool.invoke with FunctionInvocationContext

Addresses PR review comment: exercises the full pipeline from
FunctionInvocationContext.kwargs through FunctionTool.invoke to
MCPStreamableHTTPTool.call_tool and header_provider, rather than
testing call_tool in isolation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address review feedback for #4808: review comment fixes

* Fix streamable MCP transport defaults

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix Azure AI test client mocks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix MCP runtime kwarg regressions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Stabilize MCP tool runtime kwargs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use context kwargs in MCP wrappers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* updated mcp samples

* fix link

---------

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Eduard van Valkenburg
2026-03-31 19:23:49 +02:00
committed by GitHub
Unverified
parent 7c2dae8855
commit 9c57680f00
8 changed files with 503 additions and 53 deletions
+3 -1
View File
@@ -11,7 +11,7 @@ The Model Context Protocol (MCP) is an open standard for connecting AI agents to
| Sample | File | Description |
|--------|------|-------------|
| **Agent as MCP Server** | [`agent_as_mcp_server.py`](agent_as_mcp_server.py) | Shows how to expose an Agent Framework agent as an MCP server that other AI applications can connect to |
| **API Key Authentication** | [`mcp_api_key_auth.py`](mcp_api_key_auth.py) | Demonstrates API key authentication with MCP servers |
| **API Key Authentication** | [`mcp_api_key_auth.py`](mcp_api_key_auth.py) | Demonstrates API key authentication with MCP servers using `header_provider`, runtime invocation kwargs, and a command-line API key argument |
| **GitHub Integration with PAT** | [`mcp_github_pat.py`](mcp_github_pat.py) | Demonstrates connecting to GitHub's MCP server using Personal Access Token (PAT) authentication |
## Prerequisites
@@ -19,5 +19,7 @@ The Model Context Protocol (MCP) is an open standard for connecting AI agents to
- `OPENAI_API_KEY` environment variable
- `OPENAI_RESPONSES_MODEL` environment variable
Run `mcp_api_key_auth.py` with the MCP API key as the first command-line argument.
For `mcp_github_pat.py`:
- `GITHUB_PAT` - Your GitHub Personal Access Token (create at https://github.com/settings/tokens)
@@ -4,7 +4,7 @@ from typing import Annotated, Any
import anyio
from agent_framework import Agent, tool
from agent_framework.openai import OpenAIResponsesClient
from agent_framework.openai import OpenAIChatClient
from dotenv import load_dotenv
# Load environment variables from .env file
@@ -57,7 +57,7 @@ async def run() -> None:
# Define an agent
# Agent's name and description provide better context for AI model
agent = Agent(
client=OpenAIResponsesClient(),
client=OpenAIChatClient(),
name="RestaurantAgent",
description="Answer questions about the menu.",
tools=[get_specials, get_item_price],
@@ -1,20 +1,31 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
import sys
from agent_framework import Agent, MCPStreamableHTTPTool
from agent_framework.openai import OpenAIResponsesClient
from agent_framework.openai import OpenAIChatClient
from dotenv import load_dotenv
from httpx import AsyncClient
# Load environment variables from .env file
load_dotenv()
"""
MCP Authentication Example
MCP API Key Authentication Example
This example demonstrates how to authenticate with MCP servers using API key headers.
This sample demonstrates the runtime ``header_provider`` pattern for
``MCPStreamableHTTPTool``. The MCP tool derives authentication headers from
``function_invocation_kwargs`` passed to ``Agent.run(...)`` so the API key stays
in runtime context instead of being baked into a shared ``httpx.AsyncClient``.
Replace the ``url`` parameter in the ``MCPStreamableHTTPTool`` with your authenticated server URL and
run the sample with your API key as a command-line argument:
python mcp_api_key_auth.py <your_api_key>
The ``header_provider`` here is just a simple lambda, but it can be a more complex function that retrieves and
formats headers as needed, allowing for flexible authentication schemes.
For more complex scenarios, you could implement token refresh logic or support multiple authentication methods
within the header provider function.
For more authentication examples including OAuth 2.0 flows, see:
- https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/clients/simple-auth-client
@@ -22,44 +33,28 @@ For more authentication examples including OAuth 2.0 flows, see:
"""
async def api_key_auth_example() -> None:
"""Example of using API key authentication with MCP server."""
# Configuration
mcp_server_url = os.getenv("MCP_SERVER_URL", "your-mcp-server-url")
api_key = os.getenv("MCP_API_KEY")
async def api_key_auth_example(api_key: str) -> None:
"""Run an agent against an MCP server using runtime-provided API key headers."""
# Create authentication headers
# Common patterns:
# - Bearer token: "Authorization": f"Bearer {api_key}"
# - API key header: "X-API-Key": api_key
# - Custom header: "Authorization": f"ApiKey {api_key}"
auth_headers = {
"Authorization": f"Bearer {api_key}",
}
# Create HTTP client with authentication headers
http_client = AsyncClient(headers=auth_headers)
# Create MCP tool with the configured HTTP client
async with (
MCPStreamableHTTPTool(
async with Agent(
client=OpenAIChatClient(),
name="Agent",
instructions="You are a helpful assistant. Use your MCP tool when answering the user's question.",
tools=MCPStreamableHTTPTool(
name="MCP tool",
description="MCP tool description",
url=mcp_server_url,
http_client=http_client, # Pass HTTP client with authentication headers
) as mcp_tool,
Agent(
client=OpenAIResponsesClient(),
name="Agent",
instructions="You are a helpful assistant.",
tools=mcp_tool,
) as agent,
):
query = "What tools are available to you?"
description="MCP tool description.",
url="<your authenticated server url>",
header_provider=lambda kwargs: {"Authorization": f"Bearer {kwargs['mcp_api_key']}"},
),
) as agent:
query = "Use your MCP tool to tell me what tools are available to you."
print(f"User: {query}")
result = await agent.run(query)
result = await agent.run(
query,
function_invocation_kwargs={"mcp_api_key": api_key},
)
print(f"Agent: {result.text}")
if __name__ == "__main__":
asyncio.run(api_key_auth_example())
asyncio.run(api_key_auth_example(sys.argv[1]))
@@ -4,7 +4,7 @@ import asyncio
import os
from agent_framework import Agent
from agent_framework.openai import OpenAIResponsesClient
from agent_framework.openai import OpenAIChatClient
from dotenv import load_dotenv
"""
@@ -45,7 +45,7 @@ async def github_mcp_example() -> None:
# 4. Create agent with the GitHub MCP tool using instance method
# The MCP tool manages the connection to the MCP server and makes its tools available
# Set approval_mode="never_require" to allow the MCP tool to execute without approval
client = OpenAIResponsesClient()
client = OpenAIChatClient()
github_mcp_tool = client.get_mcp_tool(
name="GitHub",
url="https://api.githubcopilot.com/mcp/",