Skip to content

Commit 876e13a

Browse files
committed
feat: add per-request HTTP headers support to ClientSession
Implements per-request headers functionality across all ClientSession methods to enable multi-tenant authentication, request tracing, A/B testing, and debugging while maintaining a single persistent connection. Technical Implementation: - Headers are passed through ClientMessageMetadata to transport layer - Per-request headers take precedence over connection-level headers - All methods follow consistent extra_headers: dict[str, str] | None = None pattern Addresses: #1509
1 parent c92bb2f commit 876e13a

File tree

6 files changed

+956
-14
lines changed

6 files changed

+956
-14
lines changed

README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2233,6 +2233,109 @@ if __name__ == "__main__":
22332233
_Full example: [examples/snippets/clients/streamable_basic.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/streamable_basic.py)_
22342234
<!-- /snippet-source -->
22352235

2236+
### Per-Request HTTP Headers
2237+
2238+
When using HTTP transports, you can pass custom headers on a per-request basis. This enables various use cases such as request tracing, authentication context, A/B testing, debugging flags, and more while maintaining a single persistent connection:
2239+
2240+
<!-- snippet-source examples/snippets/clients/per_request_headers_example.py -->
2241+
```python
2242+
"""
2243+
Example demonstrating per-request headers functionality for MCP client.
2244+
2245+
Shows how to use the extra_headers parameter to send different HTTP headers
2246+
with each request, enabling use cases like per-user authentication, request
2247+
tracing, A/B testing, and multi-tenant applications.
2248+
"""
2249+
2250+
import asyncio
2251+
2252+
from mcp import ClientSession
2253+
from mcp.client.streamable_http import streamablehttp_client
2254+
2255+
2256+
async def main():
2257+
"""Demonstrate per-request headers functionality."""
2258+
2259+
# Connection-level headers (static for the entire session)
2260+
connection_headers = {"Authorization": "Bearer org-level-token", "X-Org-ID": "org-123"}
2261+
2262+
async with streamablehttp_client("https://mcp.example.com/mcp", headers=connection_headers) as (
2263+
read_stream,
2264+
write_stream,
2265+
_,
2266+
):
2267+
async with ClientSession(read_stream, write_stream) as session:
2268+
await session.initialize()
2269+
2270+
# Example 1: Request tracing
2271+
tracing_headers = {
2272+
"X-Request-ID": "req-12345",
2273+
"X-Trace-ID": "trace-abc-456",
2274+
}
2275+
result = await session.call_tool("process_data", {"type": "analytics"}, extra_headers=tracing_headers)
2276+
print(f"Traced request result: {result}")
2277+
2278+
# Example 2: User-specific authentication
2279+
user_headers = {
2280+
"X-User-ID": "alice",
2281+
"X-Auth-Token": "user-token-12345",
2282+
}
2283+
result = await session.call_tool("get_user_data", {"fields": ["profile"]}, extra_headers=user_headers)
2284+
print(f"User-specific result: {result}")
2285+
2286+
# Example 3: A/B testing
2287+
experiment_headers = {
2288+
"X-Experiment-ID": "new-ui-test",
2289+
"X-Variant": "variant-b",
2290+
}
2291+
result = await session.call_tool(
2292+
"get_recommendations", {"user_id": "user123"}, extra_headers=experiment_headers
2293+
)
2294+
print(f"A/B test result: {result}")
2295+
2296+
# Example 4: Override connection-level headers
2297+
override_headers = {
2298+
"Authorization": "Bearer user-specific-token", # Overrides connection-level
2299+
"X-Special-Permission": "admin",
2300+
}
2301+
result = await session.call_tool("admin_operation", {"operation": "reset"}, extra_headers=override_headers)
2302+
print(f"Admin operation result: {result}")
2303+
2304+
# Example 5: Works with all ClientSession methods
2305+
await session.list_resources(extra_headers={"X-Resource-Filter": "public"})
2306+
await session.get_prompt("template", extra_headers={"X-Context": "help"})
2307+
await session.set_logging_level("debug", extra_headers={"X-Debug-Session": "true"})
2308+
2309+
2310+
if __name__ == "__main__":
2311+
print("MCP Client Per-Request Headers Example")
2312+
print("=" * 50)
2313+
2314+
try:
2315+
asyncio.run(main())
2316+
except Exception as e:
2317+
print(f"Example requires a running MCP server. Error: {e}")
2318+
print("\nThis example demonstrates the API usage patterns.")
2319+
```
2320+
2321+
_Full example: [examples/snippets/clients/per_request_headers_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/per_request_headers_example.py)_
2322+
<!-- /snippet-source -->
2323+
2324+
The `extra_headers` parameter is available for all `ClientSession` methods that make server requests:
2325+
2326+
- `call_tool()`
2327+
- `get_prompt()`
2328+
- `read_resource()`
2329+
- `list_tools()`
2330+
- `list_prompts()`
2331+
- `list_resources()`
2332+
- `list_resource_templates()`
2333+
- `subscribe()`
2334+
- `unsubscribe()`
2335+
- `set_logging_level()`
2336+
2337+
Per-request headers are merged with the transport's default headers, with per-request headers taking precedence for duplicate keys.
2338+
22362339
### Client Display Utilities
22372340

22382341
When building MCP clients, the SDK provides utilities to help display human-readable names for tools, resources, and prompts:
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""
2+
Example demonstrating per-request headers functionality for MCP client.
3+
4+
Shows how to use the extra_headers parameter to send different HTTP headers
5+
with each request, enabling use cases like per-user authentication, request
6+
tracing, A/B testing, and multi-tenant applications.
7+
"""
8+
9+
import asyncio
10+
11+
from mcp import ClientSession
12+
from mcp.client.streamable_http import streamablehttp_client
13+
14+
15+
async def main():
16+
"""Demonstrate per-request headers functionality."""
17+
18+
# Connection-level headers (static for the entire session)
19+
connection_headers = {"Authorization": "Bearer org-level-token", "X-Org-ID": "org-123"}
20+
21+
async with streamablehttp_client("https://mcp.example.com/mcp", headers=connection_headers) as (
22+
read_stream,
23+
write_stream,
24+
_,
25+
):
26+
async with ClientSession(read_stream, write_stream) as session:
27+
await session.initialize()
28+
29+
# Example 1: Request tracing
30+
tracing_headers = {
31+
"X-Request-ID": "req-12345",
32+
"X-Trace-ID": "trace-abc-456",
33+
}
34+
result = await session.call_tool("process_data", {"type": "analytics"}, extra_headers=tracing_headers)
35+
print(f"Traced request result: {result}")
36+
37+
# Example 2: User-specific authentication
38+
user_headers = {
39+
"X-User-ID": "alice",
40+
"X-Auth-Token": "user-token-12345",
41+
}
42+
result = await session.call_tool("get_user_data", {"fields": ["profile"]}, extra_headers=user_headers)
43+
print(f"User-specific result: {result}")
44+
45+
# Example 3: A/B testing
46+
experiment_headers = {
47+
"X-Experiment-ID": "new-ui-test",
48+
"X-Variant": "variant-b",
49+
}
50+
result = await session.call_tool(
51+
"get_recommendations", {"user_id": "user123"}, extra_headers=experiment_headers
52+
)
53+
print(f"A/B test result: {result}")
54+
55+
# Example 4: Override connection-level headers
56+
override_headers = {
57+
"Authorization": "Bearer user-specific-token", # Overrides connection-level
58+
"X-Special-Permission": "admin",
59+
}
60+
result = await session.call_tool("admin_operation", {"operation": "reset"}, extra_headers=override_headers)
61+
print(f"Admin operation result: {result}")
62+
63+
# Example 5: Works with all ClientSession methods
64+
await session.list_resources(extra_headers={"X-Resource-Filter": "public"})
65+
await session.get_prompt("template", extra_headers={"X-Context": "help"})
66+
await session.set_logging_level("debug", extra_headers={"X-Debug-Session": "true"})
67+
68+
69+
if __name__ == "__main__":
70+
print("MCP Client Per-Request Headers Example")
71+
print("=" * 50)
72+
73+
try:
74+
asyncio.run(main())
75+
except Exception as e:
76+
print(f"Example requires a running MCP server. Error: {e}")
77+
print("\nThis example demonstrates the API usage patterns.")

0 commit comments

Comments
 (0)