Jeremiah Lowin commited on
Commit
2f2fec7
·
1 Parent(s): e99d5dd

Use lowercase name for headers

Browse files
src/fastmcp/client/transports.py CHANGED
@@ -148,6 +148,7 @@ class SSETransport(ClientTransport):
148
  try:
149
  active_request = get_http_request()
150
  for name, value in active_request.headers.items():
 
151
  if name not in self.headers and name not in EXCLUDE_HEADERS:
152
  client_kwargs["headers"][name] = str(value)
153
  except RuntimeError:
@@ -208,6 +209,7 @@ class StreamableHttpTransport(ClientTransport):
208
  try:
209
  active_request = get_http_request()
210
  for name, value in active_request.headers.items():
 
211
  if name not in self.headers and name not in EXCLUDE_HEADERS:
212
  client_kwargs["headers"][name] = str(value)
213
 
 
148
  try:
149
  active_request = get_http_request()
150
  for name, value in active_request.headers.items():
151
+ name = name.lower()
152
  if name not in self.headers and name not in EXCLUDE_HEADERS:
153
  client_kwargs["headers"][name] = str(value)
154
  except RuntimeError:
 
209
  try:
210
  active_request = get_http_request()
211
  for name, value in active_request.headers.items():
212
+ name = name.lower()
213
  if name not in self.headers and name not in EXCLUDE_HEADERS:
214
  client_kwargs["headers"][name] = str(value)
215
 
src/fastmcp/server/openapi.py CHANGED
@@ -35,6 +35,26 @@ logger = get_logger(__name__)
35
 
36
  HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  # Type definitions for the mapping functions
39
  RouteMapFn = Callable[[HTTPRoute, "MCPType"], "MCPType | None"]
40
  ComponentFn = Callable[
@@ -367,26 +387,20 @@ class OpenAPITool(Tool):
367
  # Prepare headers - fix typing by ensuring all values are strings
368
  headers = {}
369
 
370
- # Try to get headers from the current MCP client HTTP request
371
- try:
372
- http_request = get_http_request()
373
- # Add headers from the MCP client request
374
- for name, value in http_request.headers.items():
375
- # Don't override headers that are already set on the client
376
- if name not in self._client.headers:
377
- headers[name] = str(value)
378
- except RuntimeError:
379
- # No active HTTP request (e.g., STDIO transport), continue without client headers
380
- pass
381
-
382
- # Add any OpenAPI-defined header parameters (these take precedence over client headers)
383
  for p in self._route.parameters:
384
  if (
385
  p.location == "header"
386
  and p.name in kwargs
387
  and kwargs[p.name] is not None
388
  ):
389
- headers[p.name] = str(kwargs[p.name])
 
 
 
 
 
390
 
391
  # Prepare request body
392
  json_data = None
@@ -536,16 +550,8 @@ class OpenAPIResource(Resource):
536
 
537
  # Prepare headers from MCP client request if available
538
  headers = {}
539
- try:
540
- http_request = get_http_request()
541
- # Add headers from the MCP client request
542
- for name, value in http_request.headers.items():
543
- # Don't override headers that are already set on the client
544
- if name not in self._client.headers:
545
- headers[name] = str(value)
546
- except RuntimeError:
547
- # No active HTTP request (e.g., STDIO transport), continue without client headers
548
- pass
549
 
550
  response = await self._client.request(
551
  method=self._route.method,
@@ -991,8 +997,3 @@ class FastMCPOpenAPI(FastMCP):
991
  logger.debug(
992
  f"Registered TEMPLATE: {uri_template_str} ({route.method} {route.path}) with tags: {route.tags}"
993
  )
994
-
995
- async def _mcp_call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
996
- """Override the call_tool method to return the raw result without converting to content."""
997
- result = await self._tool_manager.call_tool(name, arguments)
998
- return result
 
35
 
36
  HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]
37
 
38
+
39
+ def _get_mcp_client_headers() -> dict[str, str]:
40
+ """
41
+ Extract headers from the current MCP client HTTP request if available.
42
+
43
+ These headers will take precedence over OpenAPI-defined headers when both are present.
44
+
45
+ Returns:
46
+ Dictionary of header name-value pairs (lowercased names), or empty dict if no HTTP request is active.
47
+ """
48
+ try:
49
+ http_request = get_http_request()
50
+ return {
51
+ name.lower(): str(value) for name, value in http_request.headers.items()
52
+ }
53
+ except RuntimeError:
54
+ # No active HTTP request (e.g., STDIO transport), return empty dict
55
+ return {}
56
+
57
+
58
  # Type definitions for the mapping functions
59
  RouteMapFn = Callable[[HTTPRoute, "MCPType"], "MCPType | None"]
60
  ComponentFn = Callable[
 
387
  # Prepare headers - fix typing by ensuring all values are strings
388
  headers = {}
389
 
390
+ # Start with OpenAPI-defined header parameters
391
+ openapi_headers = {}
 
 
 
 
 
 
 
 
 
 
 
392
  for p in self._route.parameters:
393
  if (
394
  p.location == "header"
395
  and p.name in kwargs
396
  and kwargs[p.name] is not None
397
  ):
398
+ openapi_headers[p.name.lower()] = str(kwargs[p.name])
399
+ headers.update(openapi_headers)
400
+
401
+ # Add headers from the current MCP client HTTP request (these take precedence)
402
+ mcp_headers = _get_mcp_client_headers()
403
+ headers.update(mcp_headers)
404
 
405
  # Prepare request body
406
  json_data = None
 
550
 
551
  # Prepare headers from MCP client request if available
552
  headers = {}
553
+ mcp_headers = _get_mcp_client_headers()
554
+ headers.update(mcp_headers)
 
 
 
 
 
 
 
 
555
 
556
  response = await self._client.request(
557
  method=self._route.method,
 
997
  logger.debug(
998
  f"Registered TEMPLATE: {uri_template_str} ({route.method} {route.path}) with tags: {route.tags}"
999
  )
 
 
 
 
 
tests/client/test_openapi.py CHANGED
@@ -169,7 +169,7 @@ class TestClientHeaders:
169
  headers = json.loads(result[0].text)
170
  assert headers["x-test"] == "test-123"
171
 
172
- async def test_client_doesnt_override_server_headers(self, shttp_server: str):
173
  async with Client(
174
  transport=StreamableHttpTransport(
175
  shttp_server, headers={"X-SERVER": "test-client"}
@@ -178,7 +178,7 @@ class TestClientHeaders:
178
  result = await client.read_resource("resource://get_headers_headers_get")
179
  assert isinstance(result[0], TextResourceContents)
180
  headers = json.loads(result[0].text)
181
- assert headers["x-server"] == "test-abc"
182
 
183
  async def test_client_headers_proxy(self, proxy_server: str):
184
  """
 
169
  headers = json.loads(result[0].text)
170
  assert headers["x-test"] == "test-123"
171
 
172
+ async def test_client_overrides_server_headers(self, shttp_server: str):
173
  async with Client(
174
  transport=StreamableHttpTransport(
175
  shttp_server, headers={"X-SERVER": "test-client"}
 
178
  result = await client.read_resource("resource://get_headers_headers_get")
179
  assert isinstance(result[0], TextResourceContents)
180
  headers = json.loads(result[0].text)
181
+ assert headers["x-server"] == "test-client"
182
 
183
  async def test_client_headers_proxy(self, proxy_server: str):
184
  """