Skip to content

Commit abce375

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 c44e68f commit abce375

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

2156+
### Per-Request HTTP Headers
2157+
2158+
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:
2159+
2160+
<!-- snippet-source examples/snippets/clients/per_request_headers_example.py -->
2161+
```python
2162+
"""
2163+
Example demonstrating per-request headers functionality for MCP client.
2164+
2165+
Shows how to use the extra_headers parameter to send different HTTP headers
2166+
with each request, enabling use cases like per-user authentication, request
2167+
tracing, A/B testing, and multi-tenant applications.
2168+
"""
2169+
2170+
import asyncio
2171+
2172+
from mcp import ClientSession
2173+
from mcp.client.streamable_http import streamablehttp_client
2174+
2175+
2176+
async def main():
2177+
"""Demonstrate per-request headers functionality."""
2178+
2179+
# Connection-level headers (static for the entire session)
2180+
connection_headers = {"Authorization": "Bearer org-level-token", "X-Org-ID": "org-123"}
2181+
2182+
async with streamablehttp_client("https://mcp.example.com/mcp", headers=connection_headers) as (
2183+
read_stream,
2184+
write_stream,
2185+
_,
2186+
):
2187+
async with ClientSession(read_stream, write_stream) as session:
2188+
await session.initialize()
2189+
2190+
# Example 1: Request tracing
2191+
tracing_headers = {
2192+
"X-Request-ID": "req-12345",
2193+
"X-Trace-ID": "trace-abc-456",
2194+
}
2195+
result = await session.call_tool("process_data", {"type": "analytics"}, extra_headers=tracing_headers)
2196+
print(f"Traced request result: {result}")
2197+
2198+
# Example 2: User-specific authentication
2199+
user_headers = {
2200+
"X-User-ID": "alice",
2201+
"X-Auth-Token": "user-token-12345",
2202+
}
2203+
result = await session.call_tool("get_user_data", {"fields": ["profile"]}, extra_headers=user_headers)
2204+
print(f"User-specific result: {result}")
2205+
2206+
# Example 3: A/B testing
2207+
experiment_headers = {
2208+
"X-Experiment-ID": "new-ui-test",
2209+
"X-Variant": "variant-b",
2210+
}
2211+
result = await session.call_tool(
2212+
"get_recommendations", {"user_id": "user123"}, extra_headers=experiment_headers
2213+
)
2214+
print(f"A/B test result: {result}")
2215+
2216+
# Example 4: Override connection-level headers
2217+
override_headers = {
2218+
"Authorization": "Bearer user-specific-token", # Overrides connection-level
2219+
"X-Special-Permission": "admin",
2220+
}
2221+
result = await session.call_tool("admin_operation", {"operation": "reset"}, extra_headers=override_headers)
2222+
print(f"Admin operation result: {result}")
2223+
2224+
# Example 5: Works with all ClientSession methods
2225+
await session.list_resources(extra_headers={"X-Resource-Filter": "public"})
2226+
await session.get_prompt("template", extra_headers={"X-Context": "help"})
2227+
await session.set_logging_level("debug", extra_headers={"X-Debug-Session": "true"})
2228+
2229+
2230+
if __name__ == "__main__":
2231+
print("MCP Client Per-Request Headers Example")
2232+
print("=" * 50)
2233+
2234+
try:
2235+
asyncio.run(main())
2236+
except Exception as e:
2237+
print(f"Example requires a running MCP server. Error: {e}")
2238+
print("\nThis example demonstrates the API usage patterns.")
2239+
```
2240+
2241+
_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)_
2242+
<!-- /snippet-source -->
2243+
2244+
The `extra_headers` parameter is available for all `ClientSession` methods that make server requests:
2245+
2246+
- `call_tool()`
2247+
- `get_prompt()`
2248+
- `read_resource()`
2249+
- `list_tools()`
2250+
- `list_prompts()`
2251+
- `list_resources()`
2252+
- `list_resource_templates()`
2253+
- `subscribe()`
2254+
- `unsubscribe()`
2255+
- `set_logging_level()`
2256+
2257+
Per-request headers are merged with the transport's default headers, with per-request headers taking precedence for duplicate keys.
2258+
21562259
### Client Display Utilities
21572260

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