Files
wehub-resource-sync 60e0ffc959
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Waiting to run
Run static analysis / static_analysis (push) Waiting to run
Tests / Tests: Python 3.10 on ubuntu-latest (push) Waiting to run
Tests / Tests: Python 3.13 on ubuntu-latest (push) Waiting to run
Tests / Tests: Python 3.10 on windows-latest (push) Waiting to run
Tests / Tests with lowest-direct dependencies (push) Waiting to run
Tests / Package install smoke (push) Waiting to run
Upgrade checks / Static analysis (push) Waiting to run
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Waiting to run
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Waiting to run
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Waiting to run
Upgrade checks / Integration tests (push) Waiting to run
Upgrade checks / Notify on failure (push) Blocked by required conditions
Upgrade checks / Close issue on success (push) Blocked by required conditions
Update MCPServerConfig Schema / update-config-schema (push) Waiting to run
Update SDK Documentation / update-sdk-docs (push) Waiting to run
Tests / MCP conformance tests (push) Waiting to run
Tests / Integration tests (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:39:59 +08:00

204 lines
7.4 KiB
Python

import json
import pytest
from fastapi import FastAPI, Request
from mcp_types import TextResourceContents
from fastmcp import Client, FastMCP
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
from fastmcp.server import create_proxy
from fastmcp.server.providers.openapi import MCPType, RouteMap
from fastmcp.utilities.tests import run_server_async
def create_fastmcp_server_for_headers() -> FastMCP:
"""Create a FastMCP server from FastAPI app with experimental parser."""
app = FastAPI()
@app.get("/headers")
def get_headers(request: Request):
return request.headers
@app.get("/headers/{header_name}")
def get_header_by_name(header_name: str, request: Request):
return request.headers[header_name]
@app.post("/headers")
def post_headers(request: Request):
return request.headers
mcp = FastMCP.from_fastapi(
app,
httpx_client_kwargs={"headers": {"x-server-header": "test-abc"}},
route_maps=[
# GET requests with path parameters go to ResourceTemplate
RouteMap(
methods=["GET"],
pattern=r".*\{.*\}.*",
mcp_type=MCPType.RESOURCE_TEMPLATE,
),
# GET requests without path parameters go to Resource
RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE),
],
)
return mcp
@pytest.fixture
async def shttp_server():
"""Start a test server with StreamableHttp transport."""
server = create_fastmcp_server_for_headers()
async with run_server_async(server, transport="http") as url:
yield url
@pytest.fixture
async def sse_server():
"""Start a test server with SSE transport."""
server = create_fastmcp_server_for_headers()
async with run_server_async(server, transport="sse") as url:
yield url
@pytest.fixture
async def proxy_server(shttp_server: str):
"""Start a proxy server."""
proxy = create_proxy(StreamableHttpTransport(shttp_server))
async with run_server_async(proxy, transport="http") as url:
yield url
async def test_fastapi_client_headers_streamable_http_resource(shttp_server: str):
async with Client(transport=StreamableHttpTransport(shttp_server)) as client:
result = await client.read_resource("resource://get_headers_headers_get")
assert isinstance(result[0], TextResourceContents)
headers = json.loads(result[0].text)
assert headers["x-server-header"] == "test-abc"
async def test_fastapi_client_headers_sse_resource(sse_server: str):
async with Client(transport=SSETransport(sse_server)) as client:
result = await client.read_resource("resource://get_headers_headers_get")
assert isinstance(result[0], TextResourceContents)
headers = json.loads(result[0].text)
assert headers["x-server-header"] == "test-abc"
async def test_fastapi_client_headers_streamable_http_tool(shttp_server: str):
async with Client(transport=StreamableHttpTransport(shttp_server)) as client:
result = await client.call_tool("post_headers_headers_post")
headers: dict[str, str] = result.data
assert headers["x-server-header"] == "test-abc"
async def test_fastapi_client_headers_sse_tool(sse_server: str):
async with Client(transport=SSETransport(sse_server)) as client:
result = await client.call_tool("post_headers_headers_post")
headers: dict[str, str] = result.data
assert headers["x-server-header"] == "test-abc"
async def test_client_headers_sse_resource(sse_server: str):
async with Client(
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
) as client:
result = await client.read_resource("resource://get_headers_headers_get")
assert isinstance(result[0], TextResourceContents)
headers = json.loads(result[0].text)
assert headers["x-test"] == "test-123"
async def test_client_headers_shttp_resource(shttp_server: str):
async with Client(
transport=StreamableHttpTransport(shttp_server, headers={"X-TEST": "test-123"})
) as client:
result = await client.read_resource("resource://get_headers_headers_get")
assert isinstance(result[0], TextResourceContents)
headers = json.loads(result[0].text)
assert headers["x-test"] == "test-123"
async def test_client_headers_sse_resource_template(sse_server: str):
async with Client(
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
) as client:
result = await client.read_resource(
"resource://get_header_by_name_headers/x-test"
)
assert isinstance(result[0], TextResourceContents)
header = json.loads(result[0].text)
assert header == "test-123"
async def test_client_headers_shttp_resource_template(shttp_server: str):
async with Client(
transport=StreamableHttpTransport(shttp_server, headers={"X-TEST": "test-123"})
) as client:
result = await client.read_resource(
"resource://get_header_by_name_headers/x-test"
)
assert isinstance(result[0], TextResourceContents)
header = json.loads(result[0].text)
assert header == "test-123"
async def test_client_headers_sse_tool(sse_server: str):
async with Client(
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
) as client:
result = await client.call_tool("post_headers_headers_post")
headers: dict[str, str] = result.data
assert headers["x-test"] == "test-123"
async def test_client_headers_shttp_tool(shttp_server: str):
async with Client(
transport=StreamableHttpTransport(shttp_server, headers={"X-TEST": "test-123"})
) as client:
result = await client.call_tool("post_headers_headers_post")
headers: dict[str, str] = result.data
assert headers["x-test"] == "test-123"
async def test_client_overrides_server_headers(shttp_server: str):
async with Client(
transport=StreamableHttpTransport(
shttp_server, headers={"x-server-header": "test-client"}
)
) as client:
result = await client.read_resource("resource://get_headers_headers_get")
assert isinstance(result[0], TextResourceContents)
headers = json.loads(result[0].text)
assert headers["x-server-header"] == "test-client"
async def test_client_with_excluded_header_is_ignored(sse_server: str):
async with Client(
transport=SSETransport(
sse_server,
headers={
"x-server-header": "test-client",
"host": "1.2.3.4",
"not-host": "1.2.3.4",
},
)
) as client:
result = await client.read_resource("resource://get_headers_headers_get")
assert isinstance(result[0], TextResourceContents)
headers = json.loads(result[0].text)
assert headers["not-host"] == "1.2.3.4"
assert headers["host"] == "fastapi"
@pytest.mark.flaky(retries=2, delay=1)
async def test_client_headers_proxy(proxy_server: str):
"""
Test that client headers are passed through the proxy to the remove server.
"""
async with Client(transport=StreamableHttpTransport(proxy_server)) as client:
result = await client.read_resource("resource://get_headers_headers_get")
assert isinstance(result[0], TextResourceContents)
headers = json.loads(result[0].text)
assert headers["x-server-header"] == "test-abc"