docs
This commit is contained in:
Vendored
+17
@@ -9,6 +9,7 @@ from pydantic import Field
|
||||
|
||||
|
||||
server = FastMCP("echo-fixture")
|
||||
_remembered_value: str | None = None
|
||||
|
||||
|
||||
class EchoToolResult(TypedDict):
|
||||
@@ -22,6 +23,22 @@ async def echo_tool(
|
||||
return {"echoed": text}
|
||||
|
||||
|
||||
@server.tool(title="Remember value tool")
|
||||
async def remember_value_tool(
|
||||
value: Annotated[str, Field(description="Value held in this server process.")],
|
||||
) -> dict[str, str]:
|
||||
"""Store state so proxy tests can distinguish reused and fresh sessions."""
|
||||
global _remembered_value
|
||||
_remembered_value = value
|
||||
return {"remembered": value}
|
||||
|
||||
|
||||
@server.tool(title="Recall value tool")
|
||||
async def recall_value_tool() -> dict[str, str | None]:
|
||||
"""Return process-local state written by `remember_value_tool`."""
|
||||
return {"remembered": _remembered_value}
|
||||
|
||||
|
||||
@server.tool(title="Resource link tool")
|
||||
async def resource_link_tool() -> list[mcp_types.ResourceLink]:
|
||||
"""Return a link to a fixture resource so proxy URI rewriting is testable."""
|
||||
|
||||
@@ -70,7 +70,7 @@ def test_fixture_server_emits_observable_protocol_notifications_directly() -> No
|
||||
assert "notifications/message" in methods
|
||||
|
||||
|
||||
def test_proxy_does_not_relay_upstream_protocol_notifications_yet() -> None:
|
||||
def _fixture_proxy_notification_methods() -> list[str]:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "protocol_relay_store",
|
||||
connections=[
|
||||
@@ -102,5 +102,29 @@ def test_proxy_does_not_relay_upstream_protocol_notifications_yet() -> None:
|
||||
except PermissionError as exc:
|
||||
pytest.skip(f"stdio MCP transport is not permitted in this environment: {exc}")
|
||||
|
||||
methods = _notification_methods(notifications)
|
||||
assert methods == []
|
||||
return _notification_methods(notifications)
|
||||
|
||||
|
||||
def test_proxy_does_not_relay_generic_upstream_notifications_yet() -> None:
|
||||
methods = _fixture_proxy_notification_methods()
|
||||
|
||||
# Stateful proxy sessions preserve FastMCP's supported request callbacks,
|
||||
# but generic upstream change/update notification rebroadcast is separate
|
||||
# protocol relay work.
|
||||
assert "notifications/tools/list_changed" not in methods
|
||||
assert "notifications/resources/list_changed" not in methods
|
||||
assert "notifications/prompts/list_changed" not in methods
|
||||
assert "notifications/resources/updated" not in methods
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
reason=(
|
||||
"FastMCP StatefulProxyClient log forwarding assumes mapping-valued log "
|
||||
"data; valid string-valued MCP logging data is rejected upstream."
|
||||
),
|
||||
)
|
||||
def test_proxy_relays_string_valued_upstream_log_when_fastmcp_supports_it() -> None:
|
||||
methods = _fixture_proxy_notification_methods()
|
||||
|
||||
assert "notifications/message" in methods
|
||||
|
||||
@@ -83,14 +83,16 @@ def test_proxy_lists_and_calls_upstream_tools() -> None:
|
||||
proxy_tools_payload = _structured(proxy_tools_result)
|
||||
proxy_tools = proxy_tools_payload["tools"]
|
||||
assert proxy_tools_payload["nextCursor"] is None
|
||||
assert proxy_tools_payload["total"] == 3
|
||||
assert len(proxy_tools) == 3
|
||||
assert proxy_tools_payload["total"] == 5
|
||||
assert len(proxy_tools) == 5
|
||||
assert proxy_tools[0]["proxy_name"] == "fixture.personal.echo_tool"
|
||||
assert proxy_tools[0]["connection_id"] == "fixture.personal"
|
||||
assert proxy_tools[0]["local_name"] == "echo_tool"
|
||||
assert proxy_tools[0]["enabled"] is True
|
||||
proxy_names = [tool["proxy_name"] for tool in proxy_tools]
|
||||
assert "fixture.personal.emit_notifications_tool" in proxy_names
|
||||
assert "fixture.personal.remember_value_tool" in proxy_names
|
||||
assert "fixture.personal.recall_value_tool" in proxy_names
|
||||
assert "fixture.personal.resource_link_tool" in proxy_names
|
||||
|
||||
proxy_tool_result = await client.call_tool(
|
||||
@@ -153,6 +155,39 @@ def test_proxy_rewrites_resource_links_returned_by_tools() -> None:
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_proxy_reuses_one_upstream_session_for_stateful_tools() -> None:
|
||||
"""Visible proxy tools must share server-local state for one MCP client."""
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "proxy_stateful_session_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
server="fixture",
|
||||
account="personal",
|
||||
metadata={
|
||||
"transport": "stdio",
|
||||
"command": sys.executable,
|
||||
"args": [fixture_server_path()],
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_proxy_client(config)
|
||||
async with client:
|
||||
written = await client.call_tool(
|
||||
"fixture.personal.remember_value_tool",
|
||||
{"value": "held"},
|
||||
)
|
||||
recalled = await client.call_tool("fixture.personal.recall_value_tool")
|
||||
|
||||
assert _structured(written)["remembered"] == "held"
|
||||
assert _structured(recalled)["remembered"] == "held"
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_proxy_rejects_invalid_connection_config() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "proxy_invalid_store",
|
||||
@@ -355,7 +390,7 @@ def test_proxy_proxy_tool_listing_supports_filters_and_cursor() -> None:
|
||||
first_page = _structured(first_page_result)
|
||||
assert len(first_page["tools"]) == 1
|
||||
assert first_page["nextCursor"] is not None
|
||||
assert first_page["total"] == 6
|
||||
assert first_page["total"] == 10
|
||||
|
||||
second_page_result = await client.call_tool(
|
||||
"wf.admin.list_proxy_tools",
|
||||
|
||||
Reference in New Issue
Block a user