mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-28 10:18:08 +02:00
Publish FastMCP 4.0.0b1 docs to gofastmcp.com (#4695)
This commit is contained in:
parent
8cf4506aa9
commit
0747ce0bc1
220 changed files with 12093 additions and 8314 deletions
|
|
@ -35,6 +35,7 @@ from fastmcp.tools.tool_transform import (
|
|||
)
|
||||
from fastmcp.utilities.http import find_available_port
|
||||
from fastmcp.utilities.tests import run_server_async
|
||||
from tests.conftest import user_meta
|
||||
|
||||
USERS = [
|
||||
{"id": "1", "name": "Alice", "active": True},
|
||||
|
|
@ -888,7 +889,12 @@ class TestPrompts:
|
|||
result = await client.get_prompt("welcome", {"name": "Alice"})
|
||||
async with Client(proxy_server) as client:
|
||||
proxy_result = await client.get_prompt("welcome", {"name": "Alice"})
|
||||
assert proxy_result == result
|
||||
# Each server stamps its own `serverInfo` into `_meta` (spec #3002), so
|
||||
# the proxy's stamp naturally differs from the origin's. Compare the
|
||||
# relayed payload.
|
||||
assert proxy_result.model_copy(
|
||||
update={"meta": user_meta(proxy_result.meta)}
|
||||
) == result.model_copy(update={"meta": user_meta(result.meta)})
|
||||
|
||||
async def test_render_prompt_calls_prompt(self, proxy_server):
|
||||
async with Client(proxy_server) as client:
|
||||
|
|
@ -942,8 +948,11 @@ class TestPrompts:
|
|||
async with Client(proxy_server) as client:
|
||||
proxy_result = await client.get_prompt("image_prompt")
|
||||
|
||||
# The proxy result should match the original exactly
|
||||
assert proxy_result == result
|
||||
# The proxy relays the original payload; only the per-server
|
||||
# `serverInfo` `_meta` stamp differs.
|
||||
assert proxy_result.model_copy(
|
||||
update={"meta": user_meta(proxy_result.meta)}
|
||||
) == result.model_copy(update={"meta": user_meta(result.meta)})
|
||||
# Verify the image content is preserved as ImageContent, not JSON text
|
||||
assert isinstance(proxy_result.messages[1].content, mcp_types.ImageContent)
|
||||
assert proxy_result.messages[1].content.data == "iVBORw0KGgoAAAANSUhEUg=="
|
||||
|
|
@ -1554,3 +1563,64 @@ class TestProxyProviderTransportErrors:
|
|||
with pytest.raises(MCPError, match="Client failed to connect"):
|
||||
async with Client(proxy, mode=mode) as client:
|
||||
await client.list_tools()
|
||||
|
||||
|
||||
async def test_proxy_preserves_x_mcp_header_annotation():
|
||||
"""A proxy re-advertises a backend tool's `x-mcp-header` annotation (SEP-2243).
|
||||
|
||||
The routing headers are per-hop: the SDK client regenerates them on each
|
||||
HTTP request. For `Mcp-Param-*` to be emitted on the proxy->backend hop (and
|
||||
on the caller->proxy hop), the proxy must carry the backend's `x-mcp-header`
|
||||
schema annotation through to its own advertised tool schema.
|
||||
"""
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
backend = FastMCP("Backend")
|
||||
|
||||
@backend.tool
|
||||
def route(
|
||||
tenant: Annotated[str, Field(json_schema_extra={"x-mcp-header": "Tenant"})],
|
||||
) -> str:
|
||||
return tenant
|
||||
|
||||
proxy = create_proxy(backend)
|
||||
async with Client(proxy) as client:
|
||||
tools = await client.list_tools()
|
||||
|
||||
(tool,) = [t for t in tools if t.name == "route"]
|
||||
assert tool.input_schema["properties"]["tenant"]["x-mcp-header"] == "Tenant"
|
||||
|
||||
|
||||
async def test_proxy_forwards_mcp_param_header_to_modern_http_backend():
|
||||
"""A proxy in front of a modern Streamable-HTTP backend routes an annotated call (SEP-2243).
|
||||
|
||||
A modern backend validates that an `x-mcp-header` argument is mirrored into an
|
||||
`Mcp-Param-*` header and rejects the call with `HEADER_MISMATCH` when it is
|
||||
missing. The SDK client caches the annotation map on `list_tools`, but a
|
||||
proxied `tools/call` goes straight to `call_tool` on a fresh backend session,
|
||||
so the proxy must seed the map itself. This exercises the real validating HTTP
|
||||
hop end to end.
|
||||
"""
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
backend = FastMCP("Backend")
|
||||
|
||||
@backend.tool
|
||||
def route(
|
||||
tenant: Annotated[str, Field(json_schema_extra={"x-mcp-header": "Tenant"})],
|
||||
) -> str:
|
||||
return f"routed:{tenant}"
|
||||
|
||||
async with run_server_async(backend, transport="http") as url:
|
||||
# mode="auto" negotiates the modern protocol with the HTTP backend, so
|
||||
# the proxy->backend hop is the validating one. (ProxyClient defaults to
|
||||
# legacy, which neither emits nor validates these headers.)
|
||||
proxy = create_proxy(ProxyClient(StreamableHttpTransport(url), mode="auto"))
|
||||
async with Client(proxy) as client:
|
||||
result = await client.call_tool("route", {"tenant": "acme"})
|
||||
|
||||
assert result.data == "routed:acme"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue