This commit is contained in:
lda
2026-05-25 23:27:11 +07:00 Verified
parent dc0799b2ff
commit 334d69a98a
9 changed files with 170 additions and 15 deletions
+17
View File
@@ -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."""
+27 -3
View File
@@ -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
+38 -3
View File
@@ -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",