|
| 1 | +""" |
| 2 | +Example demonstrating per-request headers functionality for MCP client. |
| 3 | +
|
| 4 | +This example shows how to use the new extra_headers parameter in call_tool() |
| 5 | +to send different HTTP headers with each tool call, enabling various use cases |
| 6 | +such as per-user authentication, request tracing, A/B testing, debugging flags, |
| 7 | +and multi-tenant applications. |
| 8 | +""" |
| 9 | + |
| 10 | +import asyncio |
| 11 | + |
| 12 | +from mcp import ClientSession |
| 13 | +from mcp.client.streamable_http import streamablehttp_client |
| 14 | + |
| 15 | + |
| 16 | +async def main(): |
| 17 | + """Demonstrate per-request headers functionality.""" |
| 18 | + |
| 19 | + # Connection-level headers (static for the entire session) |
| 20 | + connection_headers = {"Authorization": "Bearer org-level-token", "X-Org-ID": "org-123"} |
| 21 | + |
| 22 | + # Connect to MCP server with connection-level headers |
| 23 | + async with streamablehttp_client("https://mcp.example.com/mcp", headers=connection_headers) as ( |
| 24 | + read_stream, |
| 25 | + write_stream, |
| 26 | + _, |
| 27 | + ): |
| 28 | + async with ClientSession(read_stream, write_stream) as session: |
| 29 | + await session.initialize() |
| 30 | + |
| 31 | + # Example 1: Call tool without per-request headers |
| 32 | + # Uses only connection-level headers |
| 33 | + print("=== Example 1: Default headers ===") |
| 34 | + result = await session.call_tool("get_data", {}) |
| 35 | + print(f"Result: {result}") |
| 36 | + |
| 37 | + # Example 2: Request tracing and correlation |
| 38 | + print("\n=== Example 2: Request tracing ===") |
| 39 | + tracing_headers = { |
| 40 | + "X-Request-ID": "req-12345", |
| 41 | + "X-Trace-ID": "trace-abc-456", |
| 42 | + "X-Correlation-ID": "corr-789", |
| 43 | + } |
| 44 | + |
| 45 | + result = await session.call_tool("process_data", {"type": "analytics"}, extra_headers=tracing_headers) |
| 46 | + print(f"Result with tracing: {result}") |
| 47 | + |
| 48 | + # Example 3: A/B testing and feature flags |
| 49 | + print("\n=== Example 3: A/B testing ===") |
| 50 | + experiment_headers = { |
| 51 | + "X-Experiment-ID": "new-ui-test", |
| 52 | + "X-Variant": "variant-b", |
| 53 | + "X-Feature-Flags": "enable-beta-features,new-algorithm", |
| 54 | + } |
| 55 | + |
| 56 | + result = await session.call_tool( |
| 57 | + "get_recommendations", {"user_id": "user123"}, extra_headers=experiment_headers |
| 58 | + ) |
| 59 | + print(f"Result with A/B testing: {result}") |
| 60 | + |
| 61 | + # Example 4: Debug and profiling |
| 62 | + print("\n=== Example 4: Debug mode ===") |
| 63 | + debug_headers = {"X-Debug-Mode": "true", "X-Profile": "performance", "X-Log-Level": "verbose"} |
| 64 | + |
| 65 | + result = await session.call_tool("complex_calculation", {"data": [1, 2, 3]}, extra_headers=debug_headers) |
| 66 | + print(f"Result with debugging: {result}") |
| 67 | + |
| 68 | + # Example 5: Authentication context (user-specific) |
| 69 | + print("\n=== Example 5: User authentication ===") |
| 70 | + user_headers = {"X-Auth-Token": "user-token-12345", "X-User-ID": "alice", "X-Session-ID": "sess-789"} |
| 71 | + |
| 72 | + result = await session.call_tool( |
| 73 | + "get_user_data", {"fields": ["profile", "preferences"]}, extra_headers=user_headers |
| 74 | + ) |
| 75 | + print(f"Result for user: {result}") |
| 76 | + |
| 77 | + # Example 6: Override connection-level headers |
| 78 | + print("\n=== Example 6: Override connection-level authorization ===") |
| 79 | + override_headers = { |
| 80 | + "Authorization": "Bearer user-specific-token", # Overrides connection-level |
| 81 | + "X-Special-Permission": "admin", |
| 82 | + } |
| 83 | + |
| 84 | + result = await session.call_tool("admin_operation", {"operation": "reset"}, extra_headers=override_headers) |
| 85 | + print(f"Result with overridden auth: {result}") |
| 86 | + |
| 87 | + # Example 7: Combine with other call_tool parameters |
| 88 | + print("\n=== Example 7: Combined with meta and other parameters ===") |
| 89 | + combined_headers = {"X-Request-Source": "api", "X-Priority": "high", "X-Client-Version": "1.2.3"} |
| 90 | + |
| 91 | + meta_data = {"correlation_id": "req-123", "client_version": "1.0.0"} |
| 92 | + |
| 93 | + result = await session.call_tool( |
| 94 | + "complex_operation", |
| 95 | + {"param1": "value1", "param2": "value2"}, |
| 96 | + meta=meta_data, |
| 97 | + extra_headers=combined_headers, |
| 98 | + ) |
| 99 | + print(f"Result with combined parameters: {result}") |
| 100 | + |
| 101 | + |
| 102 | +# Multi-tenant example showing how different users can use the same connection |
| 103 | +async def multi_tenant_example(): |
| 104 | + """Example of multi-tenant usage with per-request headers.""" |
| 105 | + |
| 106 | + print("\n" + "=" * 60) |
| 107 | + print("MULTI-TENANT EXAMPLE") |
| 108 | + print("=" * 60) |
| 109 | + |
| 110 | + # Organization-level connection |
| 111 | + org_headers = {"Authorization": "Bearer org-api-key-xyz789", "X-Org-ID": "org-acme-corp"} |
| 112 | + |
| 113 | + async with streamablehttp_client("https://mcp.example.com/mcp", headers=org_headers) as ( |
| 114 | + read_stream, |
| 115 | + write_stream, |
| 116 | + _, |
| 117 | + ): |
| 118 | + async with ClientSession(read_stream, write_stream) as session: |
| 119 | + await session.initialize() |
| 120 | + |
| 121 | + # Simulate handling requests from different tenants/users |
| 122 | + tenants = [ |
| 123 | + {"tenant_id": "tenant-001", "user_id": "alice", "auth_token": "alice-jwt-token-abc123"}, |
| 124 | + {"tenant_id": "tenant-002", "user_id": "bob", "auth_token": "bob-jwt-token-def456"}, |
| 125 | + {"tenant_id": "tenant-001", "user_id": "charlie", "auth_token": "charlie-jwt-token-ghi789"}, |
| 126 | + ] |
| 127 | + |
| 128 | + for i, tenant in enumerate(tenants, 1): |
| 129 | + print(f"\n--- Request {i}: {tenant['user_id']} from {tenant['tenant_id']} ---") |
| 130 | + |
| 131 | + # Each request gets tenant-specific headers |
| 132 | + tenant_headers = { |
| 133 | + "X-Tenant-ID": tenant["tenant_id"], |
| 134 | + "X-User-ID": tenant["user_id"], |
| 135 | + "X-Auth-Token": tenant["auth_token"], |
| 136 | + "X-Request-ID": f"req-{i}-{tenant['user_id']}", |
| 137 | + } |
| 138 | + |
| 139 | + try: |
| 140 | + result = await session.call_tool( |
| 141 | + "get_tenant_data", {"data_type": "dashboard"}, extra_headers=tenant_headers |
| 142 | + ) |
| 143 | + print(f"Success for {tenant['user_id']}: {len(result.content)} items") |
| 144 | + |
| 145 | + except Exception as e: |
| 146 | + print(f"Error for {tenant['user_id']}: {e}") |
| 147 | + |
| 148 | + |
| 149 | +if __name__ == "__main__": |
| 150 | + print("MCP Client Per-Request Headers Example") |
| 151 | + print("=" * 50) |
| 152 | + |
| 153 | + # Note: This example assumes a running MCP server at the specified URL |
| 154 | + # In practice, you would replace with your actual MCP server endpoint |
| 155 | + |
| 156 | + try: |
| 157 | + asyncio.run(main()) |
| 158 | + asyncio.run(multi_tenant_example()) |
| 159 | + except Exception as e: |
| 160 | + print(f"Example requires a running MCP server. Error: {e}") |
| 161 | + print("\nThis example demonstrates the API usage patterns.") |
| 162 | + print("Replace 'https://mcp.example.com/mcp' with your actual MCP server URL.") |
0 commit comments