Spaces:
Running
Running
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 |
-
#
|
| 371 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 540 |
-
|
| 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
|
| 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-
|
| 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 |
"""
|