test orgs 2

This commit is contained in:
lda
2026-05-07 06:16:28 +07:00 Verified
parent 06d25582c9
commit 54f3e9a739
10 changed files with 11 additions and 10 deletions
+1
View File
@@ -0,0 +1 @@
"""MCP integration and proxy tests."""
+140
View File
@@ -0,0 +1,140 @@
from __future__ import annotations
import asyncio
import json
from wf_mcp import (
BrokerConfig,
ConnectionConfig,
FileStore,
WfMcpService,
build_service_from_config,
create_broker_server,
load_broker_config,
)
from .test_support import (
FailingDiscoveryAdapter,
FakeAdapter,
local_temp_root,
)
def test_load_broker_config_resolves_relative_store_root() -> None:
tmp_path = local_temp_root() / "broker_config_test"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
"store_root": ".broker-store",
"connections": [
{
"id": "demo.personal",
"server": "demo",
"account": "personal",
}
],
}
),
encoding="utf-8",
)
config = load_broker_config(config_path)
assert config.store_root == (tmp_path / ".broker-store").resolve()
assert [connection.id for connection in config.connections] == ["demo.personal"]
def test_create_broker_server_exposes_tools_resources_and_prompts() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "broker_server_store"))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_adapter("demo", FakeAdapter())
asyncio.run(service.refresh_connection_catalog("demo.personal"))
server = create_broker_server(service)
tools = asyncio.run(server.list_tools())
resources = asyncio.run(server.list_resources())
prompts = asyncio.run(server.list_prompts())
tool_names = {tool.name for tool in tools}
resource_names = {resource.name for resource in resources}
prompt_names = {prompt.name for prompt in prompts}
assert "get_connection_statuses" in tool_names
assert "refresh_connection_catalog" in tool_names
assert "invoke_broker_method" in tool_names
assert "call_broker_tool" in tool_names
assert "catalog.all" in resource_names
assert "events.all" in resource_names
assert "status.all" in resource_names
assert "plan_with_catalog" in prompt_names
def test_build_service_from_config_registers_connections() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "broker_config_store",
connections=[
ConnectionConfig(id="demo.personal", server="demo", account="personal"),
ConnectionConfig(id="demo.work", server="demo", account="work"),
],
)
service = build_service_from_config(config)
ids = [connection.id for connection in service.connections.list_all()]
assert ids == ["demo.personal", "demo.work"]
def test_broker_refresh_tool_returns_structured_error() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "broker_fail_store"))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_adapter("demo", FailingDiscoveryAdapter())
server = create_broker_server(service)
_content, structured = asyncio.run(
server.call_tool(
"refresh_connection_catalog", {"connection_id": "demo.personal"}
)
)
assert structured == {
"connection_id": "demo.personal",
"refreshed": False,
"error_type": "PermissionError",
"error": "Access is denied",
}
def test_broker_call_tool_returns_structured_result() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "broker_tool_store"))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_adapter("demo", FakeAdapter())
server = create_broker_server(service)
_content, structured = asyncio.run(
server.call_tool(
"call_broker_tool",
{
"connection_id": "demo.personal",
"tool_name": "echo_tool",
"arguments": {"text": "hello"},
},
)
)
assert structured == {
"connection_id": "demo.personal",
"tool_name": "echo_tool",
"ok": True,
"outcome": "ok",
"output": {"echoed": "hello"},
"meta": {},
}
+183
View File
@@ -0,0 +1,183 @@
from __future__ import annotations
import json
from pathlib import Path
import pytest
from pydantic import ValidationError
from wf_mcp.cli import build_parser, main
from wf_mcp.broker_server import load_broker_config
from .test_support import local_temp_root
def _write_config(path: Path) -> None:
path.write_text(
json.dumps(
{
"store_root": ".wf_mcp_store",
"connections": [
{
"id": "demo.personal",
"server": "demo",
"account": "personal",
}
],
}
),
encoding="utf-8",
)
def test_build_parser_accepts_serve_transport() -> None:
parser = build_parser()
args = parser.parse_args(
["--config", "wf_mcp.config.json", "serve", "--transport", "streamable_http"]
)
assert args.command == "serve"
assert args.transport == "streamable_http"
assert args.mode == "proxy"
assert args.resources_as_tools is False
assert args.prompts_as_tools is False
assert args.search_tools is False
def test_build_parser_accepts_proxy_compatibility_flags() -> None:
parser = build_parser()
args = parser.parse_args(
[
"--config",
"wf_mcp.config.json",
"serve",
"--resources-as-tools",
"--prompts-as-tools",
"--search-tools",
]
)
assert args.command == "serve"
assert args.mode == "proxy"
assert args.resources_as_tools is True
assert args.prompts_as_tools is True
assert args.search_tools is True
def test_cli_connections_prints_configured_connections(capsys) -> None:
tmp_path = local_temp_root() / "cli_connections_test"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
_write_config(config_path)
exit_code = main(["--config", str(config_path), "connections"])
captured = capsys.readouterr()
assert exit_code == 0
payload = json.loads(captured.out)
assert payload[0]["id"] == "demo.personal"
def test_cli_catalog_prints_empty_catalog_when_not_refreshed(
capsys,
) -> None:
tmp_path = local_temp_root() / "cli_catalog_test"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
_write_config(config_path)
exit_code = main(["--config", str(config_path), "catalog"])
captured = capsys.readouterr()
assert exit_code == 0
payload = json.loads(captured.out)
assert payload["nodes"] == []
assert payload["resources"] == []
assert payload["prompts"] == []
def test_cli_status_prints_connection_statuses(capsys) -> None:
tmp_path = local_temp_root() / "cli_status_test"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
_write_config(config_path)
exit_code = main(["--config", str(config_path), "status"])
captured = capsys.readouterr()
assert exit_code == 0
payload = json.loads(captured.out)
assert payload == [
{
"connection_id": "demo.personal",
"server": "demo",
"account": "personal",
"enabled": True,
"has_snapshot": False,
"fetched_at_epoch_ms": None,
"max_age_seconds": None,
"node_count": 0,
"resource_count": 0,
"prompt_count": 0,
}
]
def test_load_broker_config_normalizes_typed_stdio_metadata() -> None:
tmp_path = local_temp_root() / "cli_typed_stdio_config_test"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
"store_root": ".wf_mcp_store",
"connections": [
{
"id": "demo.personal",
"server": "demo",
"account": "personal",
"metadata": {
"command": "python",
"args": ["server.py"],
"env": {"TOKEN": "secret"},
},
}
],
}
),
encoding="utf-8",
)
config = load_broker_config(config_path)
assert config.store_root == (tmp_path / ".wf_mcp_store").resolve()
assert config.connections[0].metadata == {
"transport": "stdio",
"command": "python",
"args": ["server.py"],
"env": {"TOKEN": "secret"},
}
def test_load_broker_config_rejects_bad_metadata_shape() -> None:
tmp_path = local_temp_root() / "cli_bad_config_test"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
"connections": [
{
"id": "demo.personal",
"server": "demo",
"account": "personal",
"metadata": {"transport": "stdio", "args": "server.py"},
}
],
}
),
encoding="utf-8",
)
with pytest.raises(ValidationError):
load_broker_config(config_path)
+27
View File
@@ -0,0 +1,27 @@
from __future__ import annotations
from wf_mcp.names import (
is_admin_tool_name,
namespaced_tool_name,
parse_namespaced_tool_name,
)
def test_namespaced_tool_names_are_reversible_with_known_connections() -> None:
proxy_name = namespaced_tool_name("everything.default", "get-sum")
parsed = parse_namespaced_tool_name(
proxy_name,
{"everything.default", "everything"},
)
assert parsed is not None
assert parsed.proxy_name == "everything.default_get-sum"
assert parsed.connection_id == "everything.default"
assert parsed.local_name == "get-sum"
def test_namespaced_tool_parser_rejects_unknown_and_admin_names() -> None:
assert parse_namespaced_tool_name("missing_echo", {"everything.default"}) is None
assert is_admin_tool_name("wf.mcp_list_connections") is True
assert is_admin_tool_name("everything.default_echo") is False
+124
View File
@@ -0,0 +1,124 @@
from __future__ import annotations
import asyncio
import pytest
from wf_mcp import ConnectionConfig, FileStore, McpSdkAdapter, WfMcpService
from .test_support import (
everything_server_connection,
fixture_server_path,
local_temp_root,
sys,
)
def test_mcp_sdk_adapter_lists_and_calls_stdio_tool() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "sdk_adapter_store"))
service.register_connection(
ConnectionConfig(
id="fixture.personal",
server="fixture",
account="personal",
metadata={
"transport": "stdio",
"command": sys.executable,
"args": [fixture_server_path()],
},
)
)
service.register_adapter("fixture", McpSdkAdapter())
try:
asyncio.run(service.refresh_connection_catalog("fixture.personal"))
except PermissionError as exc:
pytest.skip(f"stdio MCP transport is not permitted in this environment: {exc}")
payload = service.get_catalog().as_payload()
assert payload["nodes"][0]["qualified_name"] == "fixture.personal.echo_tool"
assert payload["resources"] == [
{
"qualified_name": "fixture.personal.resource.welcome",
"connection_id": "fixture.personal",
"local_name": "resource.welcome",
"uri": "fixture://docs/welcome",
"title": "Resource Welcome",
"description": "Welcome text resource for fixture tests.",
"mime_type": "text/plain",
"metadata": payload["resources"][0]["metadata"],
}
]
assert payload["prompts"] == [
{
"qualified_name": "fixture.personal.prompt.summarize",
"connection_id": "fixture.personal",
"local_name": "prompt.summarize",
"title": "Prompt Summarize",
"description": "Summarize an input text for fixture tests.",
"arguments": payload["prompts"][0]["arguments"],
"metadata": payload["prompts"][0]["metadata"],
}
]
resource_result = asyncio.run(
service.read_resource("fixture.personal.resource.welcome")
)
prompt_result = asyncio.run(
service.render_prompt(
"fixture.personal.prompt.summarize",
arguments={"text": "hello"},
)
)
assert (
resource_result["contents"][0]["text"] == "Welcome from the fixture MCP server."
)
assert (
prompt_result["messages"][0]["content"]["text"]
== "Summarize this text:\n\nhello"
)
ping_result = asyncio.run(service.invoke_method("fixture.personal", "ping"))
assert ping_result == {}
adapter = McpSdkAdapter()
try:
result = asyncio.run(
adapter.call_tool(
connection=service.connections.get("fixture.personal"),
auth=None,
tool_name="echo_tool",
payload={"text": "hello"},
)
)
except PermissionError as exc:
pytest.skip(f"stdio MCP transport is not permitted in this environment: {exc}")
assert result.outcome == "ok"
assert result.output == {"echoed": "hello"}
def test_mcp_sdk_adapter_can_probe_everything_server() -> None:
connection = everything_server_connection()
if connection is None:
pytest.skip(
"set MCP_EVERYTHING_COMMAND to enable the live everything-server integration test"
)
service = WfMcpService(
store=FileStore(local_temp_root() / "everything_server_store")
)
service.register_connection(connection)
service.register_adapter("everything", McpSdkAdapter())
try:
asyncio.run(service.refresh_connection_catalog("everything.default"))
except PermissionError as exc:
pytest.skip(f"live MCP transport is not permitted in this environment: {exc}")
payload = service.get_catalog().as_payload()
assert payload["nodes"], "everything-server should expose at least one tool"
assert all(
node["qualified_name"].startswith("everything.default.")
for node in payload["nodes"]
)
assert "resources" in payload
assert "prompts" in payload
+420
View File
@@ -0,0 +1,420 @@
from __future__ import annotations
import asyncio
import shutil
from wf_core import END, RunStatus
from wf_mcp import (
AuthRecord,
ConnectionConfig,
FileStore,
RawWorkflowPlan,
WfMcpService,
)
from wf_mcp.error_info import error_payload
from .test_support import (
FailingDiscoveryAdapter,
FakeAdapter,
echo_tool,
finalize_tool,
local_temp_root,
)
def test_service_builds_namespaced_catalog() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "catalog_store"))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_specs("demo.personal", echo_tool, finalize_tool)
payload = service.get_catalog().as_payload()
names = [node["qualified_name"] for node in payload["nodes"]]
assert names == [
"demo.personal.echo_tool",
"demo.personal.finalize_tool",
]
def test_service_compiles_and_runs_raw_plan() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "run_store"))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_specs("demo.personal", echo_tool, finalize_tool)
plan = RawWorkflowPlan(
name="demo_plan",
input_schema={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
},
state_schema={
"fields": {
"echoed": {"type": "string"},
"result": {"type": "string"},
}
},
output_schema={
"type": "object",
"properties": {"result": {"type": "string"}},
"required": ["result"],
},
start="echo",
nodes=[
{
"id": "echo",
"type": "node",
"node": "demo.personal.echo_tool",
"in_map": {"input.text": "text"},
"out_map": {"echoed": "state.echoed"},
},
{
"id": "finalize",
"type": "node",
"node": "demo.personal.finalize_tool",
"in_map": {"state.echoed": "echoed"},
"out_map": {"result": "state.result"},
},
],
edges=[
{"from": "echo", "outcome": "ok", "to": "finalize"},
{"from": "finalize", "outcome": "done", "to": END},
],
)
run = asyncio.run(service.run_workflow_from_plan(plan, {"text": "hello"}))
assert run.status == RunStatus.COMPLETED
assert run.output == {"result": "final:hello"}
event_kinds = [event.kind for event in service.list_events()]
assert "workflow_run_started" in event_kinds
assert "workflow_run_completed" in event_kinds
def test_service_refreshes_catalog_from_adapter() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "adapter_store"))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.save_auth(
AuthRecord(
connection_id="demo.personal",
scheme="token",
payload={"token": "abc"},
)
)
service.register_adapter("demo", FakeAdapter())
asyncio.run(service.refresh_connection_catalog("demo.personal"))
payload = service.get_catalog().as_payload()
assert payload["nodes"] == [
{
"qualified_name": "demo.personal.echo_tool",
"connection_id": "demo.personal",
"local_name": "echo_tool",
"title": "Echo Tool",
"description": "Echo text back",
"outcomes": ["ok"],
"input_schema": {
"additionalProperties": True,
"properties": {"text": {"title": "Text"}},
"required": ["text"],
"title": "demo.personal_echo_tool_Input",
"type": "object",
},
"output_schema": {
"additionalProperties": True,
"properties": {"echoed": {"title": "Echoed"}},
"required": ["echoed"],
"title": "demo.personal_echo_tool_Output",
"type": "object",
},
}
]
assert payload["resources"] == [
{
"qualified_name": "demo.personal.resource.welcome",
"connection_id": "demo.personal",
"local_name": "resource.welcome",
"title": "Welcome Resource",
"uri": "demo://docs/welcome",
"description": "Welcome resource",
"mime_type": "text/plain",
"metadata": {"kind": "static"},
}
]
assert payload["prompts"] == [
{
"qualified_name": "demo.personal.prompt.summarize",
"connection_id": "demo.personal",
"local_name": "prompt.summarize",
"title": "Summarize Prompt",
"description": "Summarize text",
"arguments": [
{
"name": "text",
"required": True,
"description": "Text to summarize",
}
],
"metadata": {"kind": "template"},
}
]
assert payload["connections"] == [
{
"connection_id": "demo.personal",
"fetched_at_epoch_ms": payload["connections"][0]["fetched_at_epoch_ms"],
"max_age_seconds": 300,
"metadata": {
"server": "demo",
"account": "personal",
"auth_scheme": "token",
},
}
]
event_kinds = [event.kind for event in service.list_events()]
assert "catalog_refresh_started" in event_kinds
assert "catalog_refresh_completed" in event_kinds
def test_service_records_tool_call_events() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "event_store"))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_adapter("demo", FakeAdapter())
asyncio.run(service.refresh_connection_catalog("demo.personal"))
plan = RawWorkflowPlan(
name="tool_only_plan",
input_schema={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
},
state_schema={
"fields": {
"echoed": {"type": "string"},
}
},
output_schema={
"type": "object",
"properties": {"echoed": {"type": "string"}},
"required": ["echoed"],
},
start="echo",
nodes=[
{
"id": "echo",
"type": "node",
"node": "demo.personal.echo_tool",
"in_map": {"input.text": "text"},
"out_map": {"echoed": "state.echoed"},
}
],
edges=[
{"from": "echo", "outcome": "ok", "to": END},
],
)
run = asyncio.run(service.run_workflow_from_plan(plan, {"text": "hello"}))
assert run.status == RunStatus.COMPLETED
tool_events = [
event for event in service.list_events() if "tool_call" in event.kind
]
assert [event.kind for event in tool_events] == [
"tool_call_started",
"tool_call_completed",
]
assert tool_events[0].capability_id == "demo.personal.echo_tool"
assert tool_events[1].payload["outcome"] == "ok"
def test_service_can_inspect_resources_and_prompts() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "inspect_store"))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_adapter("demo", FakeAdapter())
asyncio.run(service.refresh_connection_catalog("demo.personal"))
resources = service.list_resources(connection_id="demo.personal")
prompts = service.list_prompts(connection_id="demo.personal")
assert [resource.qualified_name for resource in resources] == [
"demo.personal.resource.welcome"
]
assert [prompt.qualified_name for prompt in prompts] == [
"demo.personal.prompt.summarize"
]
resource = service.get_resource("demo.personal.resource.welcome")
prompt = service.get_prompt("demo.personal.prompt.summarize")
assert resource.uri == "demo://docs/welcome"
assert prompt.arguments[0]["name"] == "text"
def test_service_reports_connection_statuses() -> None:
store = local_temp_root() / "status_store"
# clear store before test
shutil.rmtree(store)
service = WfMcpService(store=FileStore(store))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_adapter("demo", FakeAdapter())
before = service.connection_statuses()
assert before == [
{
"connection_id": "demo.personal",
"server": "demo",
"account": "personal",
"enabled": True,
"has_snapshot": False,
"fetched_at_epoch_ms": None,
"max_age_seconds": None,
"node_count": 0,
"resource_count": 0,
"prompt_count": 0,
}
]
asyncio.run(service.refresh_connection_catalog("demo.personal"))
after = service.connection_statuses()
assert after[0]["has_snapshot"] is True
assert after[0]["node_count"] == 1
assert after[0]["resource_count"] == 1
assert after[0]["prompt_count"] == 1
def test_service_can_proxy_resource_reads_and_prompt_gets() -> None:
store = local_temp_root() / "proxy_store"
# new store
shutil.rmtree(store)
service = WfMcpService(store=FileStore(store))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_adapter("demo", FakeAdapter())
asyncio.run(service.refresh_connection_catalog("demo.personal"))
resource_result = asyncio.run(
service.read_resource("demo.personal.resource.welcome")
)
prompt_result = asyncio.run(
service.render_prompt(
"demo.personal.prompt.summarize",
arguments={"text": "hello world"},
)
)
assert (
resource_result["contents"][0]["text"]
== "Welcome from the fake adapter resource."
)
assert (
prompt_result["messages"][0]["content"]["text"]
== "Summarize this text:\n\nhello world"
)
event_kinds = [event.kind for event in service.list_events()]
assert "resource_read_started" in event_kinds
assert "resource_read_completed" in event_kinds
assert "prompt_get_started" in event_kinds
assert "prompt_get_completed" in event_kinds
def test_service_can_invoke_raw_method_and_notification() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "raw_store"))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_adapter("demo", FakeAdapter())
result = asyncio.run(
service.invoke_method("demo.personal", "demo.echo", params={"text": "hello"})
)
asyncio.run(
service.send_notification(
"demo.personal",
"notifications/progress",
params={"progress": 1},
)
)
assert result == {"echoed": "hello"}
event_kinds = [event.kind for event in service.list_events()]
assert "raw_method_started" in event_kinds
assert "raw_method_completed" in event_kinds
assert "raw_notification_started" in event_kinds
assert "raw_notification_completed" in event_kinds
def test_service_can_call_upstream_tool_directly() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "direct_tool_store"))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_adapter("demo", FakeAdapter())
result = asyncio.run(
service.call_tool(
"demo.personal",
"echo_tool",
arguments={"text": "hello"},
)
)
assert result == {
"outcome": "ok",
"output": {"echoed": "hello"},
"meta": {},
}
event_kinds = [event.kind for event in service.list_events()]
assert "tool_call_started" in event_kinds
assert "tool_call_completed" in event_kinds
def test_service_records_catalog_refresh_failures() -> None:
service = WfMcpService(store=FileStore(local_temp_root() / "refresh_fail_store"))
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_adapter("demo", FailingDiscoveryAdapter())
try:
asyncio.run(service.refresh_connection_catalog("demo.personal"))
except PermissionError as exc:
assert str(exc) == "Access is denied"
else:
raise AssertionError("expected refresh to fail")
failure_events = [
event
for event in service.list_events()
if event.kind == "catalog_refresh_failed"
]
assert len(failure_events) == 1
assert failure_events[0].payload == {
"error_type": "PermissionError",
"error": "Access is denied",
}
def test_error_payload_unwraps_exception_group() -> None:
exc = ExceptionGroup("outer", [PermissionError("Access is denied")])
assert error_payload(exc) == {
"error_type": "PermissionError",
"error": "Access is denied",
}
+19
View File
@@ -0,0 +1,19 @@
from __future__ import annotations
from wf_mcp import AuthRecord, FileStore
from .test_support import local_temp_root
def test_file_store_round_trips_auth() -> None:
store = FileStore(local_temp_root() / "auth_store")
record = AuthRecord(
connection_id="demo.personal",
scheme="oauth",
payload={"token": "secret"},
)
store.save_auth(record)
loaded = store.load_auth("demo.personal")
assert loaded == record
+267
View File
@@ -0,0 +1,267 @@
from __future__ import annotations
import os
import sys
from pathlib import Path
from typing import Any
from pydantic import BaseModel
from wf_authoring import NodeReturn, node
from wf_core import RuntimeContext
from wf_mcp import (
AuthRecord,
ConnectionConfig,
DiscoveredPrompt,
DiscoveredResource,
DiscoveredTool,
ToolCallResult,
)
class EchoInput(BaseModel):
text: str
class EchoOutput(BaseModel):
echoed: str
class FinalizeInput(BaseModel):
echoed: str
class FinalizeOutput(BaseModel):
result: str
@node()
async def echo_tool(payload: EchoInput, ctx: RuntimeContext) -> EchoOutput:
return EchoOutput(echoed=payload.text)
@node(outcomes=("done",))
def finalize_tool(
payload: FinalizeInput, ctx: RuntimeContext
) -> NodeReturn[FinalizeOutput]:
return NodeReturn(
outcome="done",
output=FinalizeOutput(result=f"final:{payload.echoed}"),
)
def local_temp_root() -> Path:
root = Path("test-artifacts") / "wf_mcp_store"
root.mkdir(parents=True, exist_ok=True)
return root
def fixture_server_path() -> str:
return str(Path(__file__).resolve().parents[1] / "fixtures" / "mcp_echo_server.py")
def everything_server_connection() -> ConnectionConfig | None:
transport = os.environ.get("MCP_EVERYTHING_TRANSPORT", "stdio")
if transport == "stdio":
command = os.environ.get("MCP_EVERYTHING_COMMAND")
if not command:
return None
raw_args = os.environ.get("MCP_EVERYTHING_ARGS", "")
args = [arg for arg in raw_args.split(" ") if arg]
metadata: dict[str, Any] = {
"transport": transport,
"command": command,
"args": args,
}
elif transport == "streamable_http":
url = os.environ.get("MCP_EVERYTHING_URL")
if not url:
raise AssertionError(
"MCP_EVERYTHING_URL must be set when MCP_EVERYTHING_TRANSPORT=streamable_http"
)
metadata = {
"transport": "streamable_http",
"url": url,
}
else:
return None
return ConnectionConfig(
id="everything.default",
server="everything",
account="default",
metadata=metadata,
)
class FakeAdapter:
async def list_tools(
self,
connection: ConnectionConfig,
auth: AuthRecord | None,
) -> list[DiscoveredTool]:
return [
DiscoveredTool(
name="echo_tool",
title="Echo Tool",
description="Echo text back",
input_schema={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
},
output_schema={
"type": "object",
"properties": {"echoed": {"type": "string"}},
"required": ["echoed"],
},
)
]
async def list_resources(
self,
connection: ConnectionConfig,
auth: AuthRecord | None,
) -> list[DiscoveredResource]:
return [
DiscoveredResource(
uri="demo://docs/welcome",
name="resource.welcome",
title="Welcome Resource",
description="Welcome resource",
mime_type="text/plain",
metadata={"kind": "static"},
)
]
async def list_prompts(
self,
connection: ConnectionConfig,
auth: AuthRecord | None,
) -> list[DiscoveredPrompt]:
return [
DiscoveredPrompt(
name="prompt.summarize",
title="Summarize Prompt",
description="Summarize text",
arguments=[
{
"name": "text",
"required": True,
"description": "Text to summarize",
}
],
metadata={"kind": "template"},
)
]
async def get_connection_metadata(
self,
connection: ConnectionConfig,
auth: AuthRecord | None,
) -> dict[str, Any]:
return {
"server": connection.server,
"account": connection.account,
"auth_scheme": auth.scheme if auth is not None else None,
}
async def read_resource(
self,
connection: ConnectionConfig,
auth: AuthRecord | None,
uri: str,
) -> dict[str, Any]:
if uri != "demo://docs/welcome":
raise KeyError(uri)
return {
"contents": [
{
"uri": uri,
"mimeType": "text/plain",
"text": "Welcome from the fake adapter resource.",
}
]
}
async def get_prompt(
self,
connection: ConnectionConfig,
auth: AuthRecord | None,
prompt_name: str,
arguments: dict[str, str] | None = None,
) -> dict[str, Any]:
if prompt_name != "prompt.summarize":
raise KeyError(prompt_name)
text = (arguments or {}).get("text", "")
return {
"description": "Summarize text",
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": f"Summarize this text:\n\n{text}",
},
}
],
}
async def invoke_method(
self,
connection: ConnectionConfig,
auth: AuthRecord | None,
method: str,
params: dict[str, Any] | None = None,
) -> dict[str, Any]:
if method == "ping":
return {}
if method == "demo.echo":
return {"echoed": (params or {}).get("text", "")}
raise KeyError(method)
async def send_notification(
self,
connection: ConnectionConfig,
auth: AuthRecord | None,
method: str,
params: dict[str, Any] | None = None,
) -> None:
return None
async def call_tool(
self,
connection: ConnectionConfig,
auth: AuthRecord | None,
tool_name: str,
payload: dict[str, Any],
) -> ToolCallResult:
if tool_name != "echo_tool":
raise KeyError(tool_name)
return ToolCallResult(
outcome="ok",
output={"echoed": str(payload["text"])},
)
class FailingDiscoveryAdapter(FakeAdapter):
async def list_tools(
self,
connection: ConnectionConfig,
auth: AuthRecord | None,
) -> list[DiscoveredTool]:
raise PermissionError("Access is denied")
__all__ = [
"FailingDiscoveryAdapter",
"FakeAdapter",
"echo_tool",
"everything_server_connection",
"finalize_tool",
"fixture_server_path",
"local_temp_root",
"sys",
]
+445
View File
@@ -0,0 +1,445 @@
from __future__ import annotations
import asyncio
import json
import sys
from typing import Any
import pytest
from wf_mcp import (
BrokerConfig,
ConnectionConfig,
ProxyConfigError,
create_transparent_proxy_client,
validate_transparent_proxy_config,
)
from wf_mcp.broker_server import load_broker_config
from .test_support import fixture_server_path, local_temp_root
def _structured(result: Any) -> dict[str, Any]:
content = result.structured_content
assert isinstance(content, dict)
return content
def test_transparent_proxy_lists_and_calls_upstream_tools() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_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_transparent_proxy_client(config)
async with client:
tools = await client.list_tools()
names = [tool.name for tool in tools]
assert "wf.mcp_list_connections" in names
assert "wf.mcp_get_connection_statuses" in names
assert "wf.mcp_list_proxy_tools" in names
assert "wf.mcp_get_proxy_tool" in names
assert "fixture.personal_echo_tool" in names
connections_result = await client.call_tool("wf.mcp_list_connections")
assert _structured(connections_result) == {
"result": [
{
"id": "fixture.personal",
"server": "fixture",
"account": "personal",
"enabled": True,
"metadata": {
"transport": "stdio",
"command": sys.executable,
"args": [fixture_server_path()],
},
}
]
}
result = await client.call_tool(
"fixture.personal_echo_tool",
{"text": "hello"},
)
assert _structured(result) == {"echoed": "hello"}
proxy_tools_result = await client.call_tool("wf.mcp_list_proxy_tools")
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"] == 1
assert len(proxy_tools) == 1
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_tool_result = await client.call_tool(
"wf.mcp_get_proxy_tool",
{"proxy_name": "fixture.personal_echo_tool"},
)
proxy_tool = _structured(proxy_tool_result)
assert proxy_tool["proxy_name"] == "fixture.personal_echo_tool"
assert proxy_tool["connection_id"] == "fixture.personal"
assert proxy_tool["local_name"] == "echo_tool"
assert proxy_tool["input_schema"]["properties"]["text"]["type"] == "string"
asyncio.run(run_proxy())
def test_transparent_proxy_rejects_invalid_connection_config() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_invalid_store",
connections=[
ConnectionConfig(
id="fixture.personal",
server="fixture",
account="personal",
metadata={"transport": "stdio"},
),
ConnectionConfig(
id="fixture.personal",
server="fixture",
account="work",
metadata={"transport": "websocket"},
),
ConnectionConfig(
id="bad_scope.personal",
server="bad_scope",
account="personal",
metadata={"transport": "stdio", "command": sys.executable},
),
ConnectionConfig(
id="fixture.http",
server="fixture",
account="http",
metadata={"transport": "http"},
),
ConnectionConfig(
id="wf.mcp",
server="wf",
account="mcp",
metadata={"transport": "stdio", "command": sys.executable},
),
],
)
with pytest.raises(ProxyConfigError) as exc_info:
validate_transparent_proxy_config(config)
message = str(exc_info.value)
assert "duplicate connection id 'fixture.personal'" in message
assert "fixture.personal: stdio transport requires metadata.command" in message
assert "fixture.personal: unsupported MCP transport 'websocket'" in message
assert "connection id 'bad_scope.personal' must not contain '_'" in message
assert "fixture.http: http transport requires metadata.url" in message
assert "connection id 'wf.mcp' is reserved by wf-mcp" in message
def test_transparent_proxy_can_expose_resources_and_prompts_as_tools() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_helper_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_transparent_proxy_client(
config,
resources_as_tools=True,
prompts_as_tools=True,
)
async with client:
tools = await client.list_tools()
names = [tool.name for tool in tools]
assert "list_resources" in names
assert "read_resource" in names
assert "list_prompts" in names
assert "get_prompt" in names
asyncio.run(run_proxy())
def test_transparent_proxy_can_collapse_upstream_tools_behind_search() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_search_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_transparent_proxy_client(config, search_tools=True)
async with client:
tools = await client.list_tools()
names = [tool.name for tool in tools]
assert "search_tools" in names
assert "wf.mcp_list_connections" in names
assert "wf.mcp_get_connection_statuses" in names
assert "wf.mcp_list_proxy_tools" in names
assert "fixture.personal_echo_tool" not in names
search_result = await client.call_tool(
"search_tools",
{"query": "echo text back"},
)
assert "fixture.personal_echo_tool" in str(search_result)
asyncio.run(run_proxy())
def test_transparent_proxy_proxy_tool_listing_supports_filters_and_cursor() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_paged_tools_store",
connections=[
ConnectionConfig(
id="fixture.personal",
server="fixture",
account="personal",
metadata={
"transport": "stdio",
"command": sys.executable,
"args": [fixture_server_path()],
},
),
ConnectionConfig(
id="fixture.work",
server="fixture",
account="work",
metadata={
"transport": "stdio",
"command": sys.executable,
"args": [fixture_server_path()],
},
),
],
)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config)
async with client:
first_page_result = await client.call_tool(
"wf.mcp_list_proxy_tools",
{"limit": 1},
)
first_page = _structured(first_page_result)
assert len(first_page["tools"]) == 1
assert first_page["nextCursor"] is not None
assert first_page["total"] == 2
second_page_result = await client.call_tool(
"wf.mcp_list_proxy_tools",
{"limit": 1, "cursor": first_page["nextCursor"]},
)
second_page = _structured(second_page_result)
assert len(second_page["tools"]) == 1
assert (
second_page["tools"][0]["proxy_name"]
!= first_page["tools"][0]["proxy_name"]
)
filtered_result = await client.call_tool(
"wf.mcp_list_proxy_tools",
{
"connection_id": "fixture.personal",
"query": "echo",
"limit": 10,
},
)
filtered = _structured(filtered_result)
assert filtered["nextCursor"] is None
assert filtered["total"] == 1
assert filtered["tools"][0]["proxy_name"] == "fixture.personal_echo_tool"
asyncio.run(run_proxy())
def test_transparent_proxy_admin_tools_mutate_config_file() -> None:
tmp_path = local_temp_root() / "transparent_proxy_admin_store"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
"store_root": ".wf_mcp_store",
"connections": [
{
"id": "fixture.personal",
"server": "fixture",
"account": "personal",
"enabled": False,
}
],
}
),
encoding="utf-8",
)
config = load_broker_config(config_path)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config, config_path=config_path)
async with client:
add_result = await client.call_tool(
"wf.mcp_add_connection",
{
"connection_id": "fixture.work",
"server": "fixture",
"account": "work",
"enabled": False,
"metadata": {
"transport": "stdio",
"command": sys.executable,
"args": [fixture_server_path()],
},
},
)
assert _structured(add_result) == {
"action": "add_connection",
"connection_id": "fixture.work",
"ok": True,
"requires_reload": True,
}
disable_result = await client.call_tool(
"wf.mcp_disable_connection",
{"connection_id": "fixture.work"},
)
assert _structured(disable_result) == {
"action": "update_connection",
"connection_id": "fixture.work",
"ok": True,
"requires_reload": True,
}
update_result = await client.call_tool(
"wf.mcp_update_connection",
{
"connection_id": "fixture.work",
"metadata": {
"transport": "stdio",
"command": sys.executable,
"args": ["updated.py"],
},
},
)
assert _structured(update_result) == {
"action": "update_connection",
"connection_id": "fixture.work",
"ok": True,
"requires_reload": True,
}
config_result = await client.call_tool("wf.mcp_get_config")
assert "fixture.work" in str(_structured(config_result))
remove_result = await client.call_tool(
"wf.mcp_remove_connection",
{"connection_id": "fixture.work"},
)
assert _structured(remove_result) == {
"action": "remove_connection",
"connection_id": "fixture.work",
"ok": True,
"requires_reload": True,
}
asyncio.run(run_proxy())
config_after = load_broker_config(config_path)
assert [connection.id for connection in config_after.connections] == [
"fixture.personal"
]
def test_transparent_proxy_admin_reload_remounts_connections() -> None:
tmp_path = local_temp_root() / "transparent_proxy_reload_store"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
"store_root": ".wf_mcp_store",
"connections": [],
}
),
encoding="utf-8",
)
config = load_broker_config(config_path)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config, config_path=config_path)
async with client:
initial_tools = await client.list_tools()
initial_names = [tool.name for tool in initial_tools]
assert "fixture.personal_echo_tool" not in initial_names
await client.call_tool(
"wf.mcp_add_connection",
{
"connection_id": "fixture.personal",
"server": "fixture",
"account": "personal",
"metadata": {
"transport": "stdio",
"command": sys.executable,
"args": [fixture_server_path()],
},
},
)
before_reload_tools = await client.list_tools()
before_reload_names = [tool.name for tool in before_reload_tools]
assert "fixture.personal_echo_tool" not in before_reload_names
reload_result = await client.call_tool("wf.mcp_reload_config")
assert _structured(reload_result) == {
"ok": True,
"reloaded": True,
"mounted_connections": ["fixture.personal"],
"connection_count": 1,
"enabled_connection_count": 1,
}
after_reload_tools = await client.list_tools()
after_reload_names = [tool.name for tool in after_reload_tools]
assert "fixture.personal_echo_tool" in after_reload_names
result = await client.call_tool(
"fixture.personal_echo_tool",
{"text": "reloaded"},
)
assert _structured(result) == {"echoed": "reloaded"}
asyncio.run(run_proxy())