Skip to content

Commit 1d58321

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 f2b89ec commit 1d58321

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
@@ -2267,6 +2267,109 @@ if __name__ == "__main__":
22672267
_Full example: [examples/snippets/clients/streamable_basic.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/streamable_basic.py)_
22682268
<!-- /snippet-source -->
22692269

2270+
### Per-Request HTTP Headers
2271+
2272+
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:
2273+
2274+
<!-- snippet-source examples/snippets/clients/per_request_headers_example.py -->
2275+
```python
2276+
"""
2277+
Example demonstrating per-request headers functionality for MCP client.
2278+
2279+
Shows how to use the extra_headers parameter to send different HTTP headers
2280+
with each request, enabling use cases like per-user authentication, request
2281+
tracing, A/B testing, and multi-tenant applications.
2282+
"""
2283+
2284+
import asyncio
2285+
2286+
from mcp import ClientSession
2287+
from mcp.client.streamable_http import streamablehttp_client
2288+
2289+
2290+
async def main():
2291+
"""Demonstrate per-request headers functionality."""
2292+
2293+
# Connection-level headers (static for the entire session)
2294+
connection_headers = {"Authorization": "Bearer org-level-token", "X-Org-ID": "org-123"}
2295+
2296+
async with streamablehttp_client("https://mcp.example.com/mcp", headers=connection_headers) as (
2297+
read_stream,
2298+
write_stream,
2299+
_,
2300+
):
2301+
async with ClientSession(read_stream, write_stream) as session:
2302+
await session.initialize()
2303+
2304+
# Example 1: Request tracing
2305+
tracing_headers = {
2306+
"X-Request-ID": "req-12345",
2307+
"X-Trace-ID": "trace-abc-456",
2308+
}
2309+
result = await session.call_tool("process_data", {"type": "analytics"}, extra_headers=tracing_headers)
2310+
print(f"Traced request result: {result}")
2311+
2312+
# Example 2: User-specific authentication
2313+
user_headers = {
2314+
"X-User-ID": "alice",
2315+
"X-Auth-Token": "user-token-12345",
2316+
}
2317+
result = await session.call_tool("get_user_data", {"fields": ["profile"]}, extra_headers=user_headers)
2318+
print(f"User-specific result: {result}")
2319+
2320+
# Example 3: A/B testing
2321+
experiment_headers = {
2322+
"X-Experiment-ID": "new-ui-test",
2323+
"X-Variant": "variant-b",
2324+
}
2325+
result = await session.call_tool(
2326+
"get_recommendations", {"user_id": "user123"}, extra_headers=experiment_headers
2327+
)
2328+
print(f"A/B test result: {result}")
2329+
2330+
# Example 4: Override connection-level headers
2331+
override_headers = {
2332+
"Authorization": "Bearer user-specific-token", # Overrides connection-level
2333+
"X-Special-Permission": "admin",
2334+
}
2335+
result = await session.call_tool("admin_operation", {"operation": "reset"}, extra_headers=override_headers)
2336+
print(f"Admin operation result: {result}")
2337+
2338+
# Example 5: Works with all ClientSession methods
2339+
await session.list_resources(extra_headers={"X-Resource-Filter": "public"})
2340+
await session.get_prompt("template", extra_headers={"X-Context": "help"})
2341+
await session.set_logging_level("debug", extra_headers={"X-Debug-Session": "true"})
2342+
2343+
2344+
if __name__ == "__main__":
2345+
print("MCP Client Per-Request Headers Example")
2346+
print("=" * 50)
2347+
2348+
try:
2349+
asyncio.run(main())
2350+
except Exception as e:
2351+
print(f"Example requires a running MCP server. Error: {e}")
2352+
print("\nThis example demonstrates the API usage patterns.")
2353+
```
2354+
2355+
_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)_
2356+
<!-- /snippet-source -->
2357+
2358+
The `extra_headers` parameter is available for all `ClientSession` methods that make server requests:
2359+
2360+
- `call_tool()`
2361+
- `get_prompt()`
2362+
- `read_resource()`
2363+
- `list_tools()`
2364+
- `list_prompts()`
2365+
- `list_resources()`
2366+
- `list_resource_templates()`
2367+
- `subscribe()`
2368+
- `unsubscribe()`
2369+
- `set_logging_level()`
2370+
2371+
Per-request headers are merged with the transport's default headers, with per-request headers taking precedence for duplicate keys.
2372+
22702373
### Client Display Utilities
22712374

22722375
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)