You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add support for passing custom HTTP headers on a per-request basis when calling MCP tools via ClientSession.call_tool(). This is needed for multi-tenant applications where different requests require different authentication tokens or request-specific metadata headers while maintaining a single persistent MCP connection.
Use Case: Multi-Tenant SaaS Applications
In multi-tenant deployments, a single application instance serves multiple users/tenants concurrently. Each user request needs to include tenant-specific authentication headers when calling MCP tools, but creating a new MCP connection per request introduces unacceptable latency:
Connection establishment: ~500ms overhead per request
At 1000 concurrent users: 8.3 minutes of cumulative overhead per minute
Connection pooling doesn't help because each user needs different headers
Current State: Headers can only be set at connection initialization in streamablehttp_client(), making them static for the connection lifetime.
Desired State: Ability to pass custom headers per call_tool() invocation while maintaining a single persistent connection.
Request tracing - Correlation IDs, trace IDs for distributed systems
Rate limiting - User-specific rate limit tokens
A/B testing - Feature flags or experiment IDs
Tenant isolation - Tenant identifiers for data partitioning
Current Behavior
frommcp.client.streamable_httpimportstreamablehttp_client# Headers set at connection time - static for connection lifetimeasyncwithstreamablehttp_client(
url="https://mcp.example.com",
headers={"Authorization": "Bearer static-token"}, # Cannot change per-request
) as (read_stream, write_stream, get_session_id):
asyncwithClientSession(read_stream, write_stream) assession:
# User A's requestresult=awaitsession.call_tool("list_sites", {}) # Uses static token# User B's request (needs different token)result=awaitsession.call_tool("list_sites", {}) # Still uses static token
Proposed Solution
Option 1: Add extra_headers Parameter to call_tool()
Similar to how read_timeout_seconds was added for per-request timeout configuration (Issue #600), add an optional parameter for per-request headers:
asyncdefcall_tool(
self,
name: str,
arguments: dict[str, Any] |None=None,
read_timeout_seconds: timedelta|None=None,
progress_callback: ProgressFnT|None=None,
*,
meta: dict[str, Any] |None=None,
extra_headers: dict[str, str] |None=None, # NEW
) ->types.CallToolResult:
""" Send a tools/call request. Args: extra_headers: Additional HTTP headers to include in this specific request. These are merged with connection-level headers, with extra_headers taking precedence for duplicate keys. """
I'm willing to contribute a pull request implementing this feature if the approach is acceptable to maintainers. Our production use case requires this functionality, and we believe it would benefit the broader MCP community.
Description
Related Issues: #638, #600, #1305
Summary
Add support for passing custom HTTP headers on a per-request basis when calling MCP tools via
ClientSession.call_tool(). This is needed for multi-tenant applications where different requests require different authentication tokens or request-specific metadata headers while maintaining a single persistent MCP connection.Use Case: Multi-Tenant SaaS Applications
In multi-tenant deployments, a single application instance serves multiple users/tenants concurrently. Each user request needs to include tenant-specific authentication headers when calling MCP tools, but creating a new MCP connection per request introduces unacceptable latency:
Current State: Headers can only be set at connection initialization in
streamablehttp_client(), making them static for the connection lifetime.Desired State: Ability to pass custom headers per
call_tool()invocation while maintaining a single persistent connection.Related Use Cases
This pattern appears in several scenarios:
Current Behavior
Proposed Solution
Option 1: Add
extra_headersParameter tocall_tool()Similar to how
read_timeout_secondswas added for per-request timeout configuration (Issue #600), add an optional parameter for per-request headers:Usage Example:
Implementation Notes:
ClientSession.call_tool()signature to acceptextra_headersStreamableHTTPTransport._handle_post_request(), merge extra_headers with base headersOption 2: Extend Transport Context
Add headers to the existing
RequestContextmechanism:Backward Compatibility
All proposed solutions are backward compatible:
extra_headersparameter is optional (defaults toNone)Precedent
The SDK already supports per-request configuration for timeouts:
This establishes a pattern that certain aspects of tool invocation may need per-request customization beyond the JSON-RPC protocol itself.
Non-Solution: Per-Request Connections
Creating a new connection per request defeats the purpose of persistent connections and introduces significant latency overhead.
Implementation Considerations
Header Merging Strategy
Connection-level headers should be merged with per-request headers:
Transport-Specific
This feature should only affect HTTP-based transports (Streamable HTTP, SSE). Stdio transport would ignore
extra_headersas it has no HTTP layer.Security Considerations
Per-request headers enable proper security patterns:
Related Issues
I'm willing to contribute a pull request implementing this feature if the approach is acceptable to maintainers. Our production use case requires this functionality, and we believe it would benefit the broader MCP community.
Thanks,
Damian.
References
No response