Skip to content

Commit b4b95dd

Browse files
committed
feat: add configurable HTTP header forwarding
1 parent 0cff354 commit b4b95dd

File tree

2 files changed

+73
-6
lines changed

2 files changed

+73
-6
lines changed

fastapi_mcp/server.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ def __init__(
113113
Optional[AuthConfig],
114114
Doc("Configuration for MCP authentication"),
115115
] = None,
116+
headers: Annotated[
117+
Optional[List[str]],
118+
Doc(
119+
"""
120+
List of HTTP header names to forward from the incoming MCP request into each tool invocation.
121+
Only headers in this allowlist will be forwarded. Defaults to ['authorization'].
122+
"""
123+
),
124+
] = None,
116125
):
117126
# Validate operation and tag filtering options
118127
if include_operations is not None and exclude_operations is not None:
@@ -147,6 +156,8 @@ def __init__(
147156
timeout=10.0,
148157
)
149158

159+
self._forward_headers = {h.lower() for h in (headers or ["Authorization"])}
160+
150161
self.setup_server()
151162

152163
def setup_server(self) -> None:
@@ -407,11 +418,12 @@ async def _execute_api_tool(
407418
raise ValueError(f"Parameter name is None for parameter: {param}")
408419
headers[param_name] = arguments.pop(param_name)
409420

421+
# Forward headers that are in the allowlist
410422
if http_request_info and http_request_info.headers:
411-
if "Authorization" in http_request_info.headers:
412-
headers["Authorization"] = http_request_info.headers["Authorization"]
413-
elif "authorization" in http_request_info.headers:
414-
headers["Authorization"] = http_request_info.headers["authorization"]
423+
for name, value in http_request_info.headers.items():
424+
# case-insensitive check for allowed headers
425+
if name.lower() in self._forward_headers:
426+
headers[name] = value
415427

416428
body = arguments if arguments else None
417429

tests/test_mcp_simple_app.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ def fastapi_mcp(simple_fastapi_app: FastAPI) -> FastApiMCP:
2121
mcp.mount()
2222
return mcp
2323

24+
@pytest.fixture
25+
def fastapi_mcp_with_custom_header(simple_fastapi_app: FastAPI) -> FastApiMCP:
26+
mcp = FastApiMCP(
27+
simple_fastapi_app,
28+
name="Test MCP Server with custom header",
29+
description="Test description",
30+
headers=["X-Custom-Header"],
31+
)
32+
mcp.mount()
33+
return mcp
2434

2535
@pytest.fixture
2636
def lowlevel_server_simple_app(fastapi_mcp: FastApiMCP) -> Server:
@@ -311,5 +321,50 @@ async def test_headers_passthrough_to_tool_handler(fastapi_mcp: FastApiMCP):
311321

312322
if mock_request.called:
313323
headers_arg = mock_request.call_args[0][4] # headers are the 5th argument
314-
assert "Authorization" in headers_arg
315-
assert headers_arg["Authorization"] == "Bearer token456"
324+
assert "authorization" in headers_arg
325+
assert headers_arg["authorization"] == "Bearer token456"
326+
327+
328+
@pytest.mark.asyncio
329+
async def test_custom_header_passthrough_to_tool_handler(
330+
fastapi_mcp_with_custom_header: FastApiMCP
331+
):
332+
from unittest.mock import patch, MagicMock
333+
from fastapi_mcp.types import HTTPRequestInfo
334+
335+
# Test with custom header "X-Custom-Header"
336+
with patch.object(fastapi_mcp_with_custom_header, "_request") as mock_request:
337+
mock_response = MagicMock()
338+
mock_response.status_code = 200
339+
mock_response.text = '{"result": "success"}'
340+
mock_response.json.return_value = {"result": "success"}
341+
mock_request.return_value = mock_response
342+
343+
http_request_info = HTTPRequestInfo(
344+
method="POST",
345+
path="/test",
346+
headers={"X-Custom-Header": "MyValue123"},
347+
cookies={},
348+
query_params={},
349+
body=None,
350+
)
351+
352+
try:
353+
# Call the _execute_api_tool method directly
354+
# We don't care if it succeeds, just that _request gets the right headers
355+
await fastapi_mcp_with_custom_header._execute_api_tool(
356+
client=fastapi_mcp_with_custom_header._http_client,
357+
tool_name="get_item",
358+
arguments={"item_id": 1},
359+
operation_map=fastapi_mcp_with_custom_header.operation_map,
360+
http_request_info=http_request_info,
361+
)
362+
except Exception:
363+
pass
364+
365+
assert mock_request.called, "The _request method was not called"
366+
367+
if mock_request.called:
368+
headers_arg = mock_request.call_args[0][4] # headers are the 5th argument
369+
assert "X-Custom-Header" in headers_arg
370+
assert headers_arg["X-Custom-Header"] == "MyValue123"

0 commit comments

Comments
 (0)