Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
c457a0d
1
Parent(s): eea96ed
Ensure behavior-affecting headers are excluded
Browse files
docs/patterns/http-requests.mdx
CHANGED
|
@@ -77,7 +77,7 @@ async def safe_header_info() -> dict:
|
|
| 77 |
}
|
| 78 |
```
|
| 79 |
|
| 80 |
-
By default, `get_http_headers()` excludes problematic headers like `content-length`. To include all headers, use `get_http_headers(include_all=True)`.
|
| 81 |
|
| 82 |
## Important Notes
|
| 83 |
|
|
|
|
| 77 |
}
|
| 78 |
```
|
| 79 |
|
| 80 |
+
By default, `get_http_headers()` excludes problematic headers like `host` and `content-length`. To include all headers, use `get_http_headers(include_all=True)`.
|
| 81 |
|
| 82 |
## Important Notes
|
| 83 |
|
src/fastmcp/server/dependencies.py
CHANGED
|
@@ -48,11 +48,24 @@ def get_http_headers(include_all: bool = False) -> dict[str, str]:
|
|
| 48 |
if include_all:
|
| 49 |
exclude_headers = set()
|
| 50 |
else:
|
| 51 |
-
exclude_headers = {
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
headers = {}
|
| 58 |
|
|
|
|
| 48 |
if include_all:
|
| 49 |
exclude_headers = set()
|
| 50 |
else:
|
| 51 |
+
exclude_headers = {
|
| 52 |
+
"host",
|
| 53 |
+
"content-length",
|
| 54 |
+
"connection",
|
| 55 |
+
"transfer-encoding",
|
| 56 |
+
"upgrade",
|
| 57 |
+
"te",
|
| 58 |
+
"keep-alive",
|
| 59 |
+
"expect",
|
| 60 |
+
# Proxy-related headers
|
| 61 |
+
"proxy-authenticate",
|
| 62 |
+
"proxy-authorization",
|
| 63 |
+
"proxy-connection",
|
| 64 |
+
}
|
| 65 |
+
# (just in case)
|
| 66 |
+
assert all(h.lower() == h for h in exclude_headers), (
|
| 67 |
+
"Excluded headers must be lowercase"
|
| 68 |
+
)
|
| 69 |
|
| 70 |
headers = {}
|
| 71 |
|
tests/client/test_openapi.py
CHANGED
|
@@ -28,7 +28,8 @@ def fastmcp_server_for_headers() -> FastMCP:
|
|
| 28 |
return request.headers
|
| 29 |
|
| 30 |
mcp = FastMCP.from_fastapi(
|
| 31 |
-
app,
|
|
|
|
| 32 |
)
|
| 33 |
|
| 34 |
return mcp
|
|
@@ -169,16 +170,42 @@ class TestClientHeaders:
|
|
| 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={"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
)
|
| 177 |
) as 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["
|
|
|
|
| 182 |
|
| 183 |
async def test_client_headers_proxy(self, proxy_server: str):
|
| 184 |
"""
|
|
@@ -188,4 +215,4 @@ class TestClientHeaders:
|
|
| 188 |
result = await client.read_resource("resource://get_headers_headers_get")
|
| 189 |
assert isinstance(result[0], TextResourceContents)
|
| 190 |
headers = json.loads(result[0].text)
|
| 191 |
-
assert headers["x-server"] == "test-abc"
|
|
|
|
| 28 |
return request.headers
|
| 29 |
|
| 30 |
mcp = FastMCP.from_fastapi(
|
| 31 |
+
app,
|
| 32 |
+
httpx_client_kwargs={"headers": {"x-server-header": "test-abc"}},
|
| 33 |
)
|
| 34 |
|
| 35 |
return mcp
|
|
|
|
| 170 |
headers = json.loads(result[0].text)
|
| 171 |
assert headers["x-test"] == "test-123"
|
| 172 |
|
| 173 |
+
# async def test_certain_client_headers_are_stripped(self, shttp_server: str):
|
| 174 |
+
# async with Client(
|
| 175 |
+
# transport=StreamableHttpTransport(shttp_server, headers={"host": "1.2.3.4"})
|
| 176 |
+
# ) as client:
|
| 177 |
+
# result = await client.read_resource("resource://get_headers_headers_get")
|
| 178 |
+
# assert isinstance(result[0], TextResourceContents)
|
| 179 |
+
# headers = json.loads(result[0].text)
|
| 180 |
+
# assert "host" not in headers
|
| 181 |
+
|
| 182 |
async def test_client_overrides_server_headers(self, shttp_server: str):
|
| 183 |
async with Client(
|
| 184 |
transport=StreamableHttpTransport(
|
| 185 |
+
shttp_server, headers={"x-server-header": "test-client"}
|
| 186 |
+
)
|
| 187 |
+
) as client:
|
| 188 |
+
result = await client.read_resource("resource://get_headers_headers_get")
|
| 189 |
+
assert isinstance(result[0], TextResourceContents)
|
| 190 |
+
headers = json.loads(result[0].text)
|
| 191 |
+
assert headers["x-server-header"] == "test-client"
|
| 192 |
+
|
| 193 |
+
async def test_client_with_excluded_header_is_ignored(self, sse_server: str):
|
| 194 |
+
async with Client(
|
| 195 |
+
transport=SSETransport(
|
| 196 |
+
sse_server,
|
| 197 |
+
headers={
|
| 198 |
+
"x-server-header": "test-client",
|
| 199 |
+
"host": "1.2.3.4",
|
| 200 |
+
"not-host": "1.2.3.4",
|
| 201 |
+
},
|
| 202 |
)
|
| 203 |
) as client:
|
| 204 |
result = await client.read_resource("resource://get_headers_headers_get")
|
| 205 |
assert isinstance(result[0], TextResourceContents)
|
| 206 |
headers = json.loads(result[0].text)
|
| 207 |
+
assert headers["not-host"] == "1.2.3.4"
|
| 208 |
+
assert headers["host"] == "fastapi"
|
| 209 |
|
| 210 |
async def test_client_headers_proxy(self, proxy_server: str):
|
| 211 |
"""
|
|
|
|
| 215 |
result = await client.read_resource("resource://get_headers_headers_get")
|
| 216 |
assert isinstance(result[0], TextResourceContents)
|
| 217 |
headers = json.loads(result[0].text)
|
| 218 |
+
assert headers["x-server-header"] == "test-abc"
|