test org 2
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
||||
from wf_mcp.server import create_server_client
|
||||
|
||||
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 server_config() -> BrokerConfig:
|
||||
return BrokerConfig(
|
||||
store_root=local_temp_root() / "server_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
server="fixture",
|
||||
account="personal",
|
||||
metadata={
|
||||
"transport": "stdio",
|
||||
"command": sys.executable,
|
||||
"args": [fixture_server_path()],
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
async def assert_safe_tool_maps(
|
||||
client: Any,
|
||||
*,
|
||||
original_name: str,
|
||||
safe_name: str,
|
||||
arguments: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Assert one safe public tool name maps back to its original tool."""
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
assert safe_name in names
|
||||
assert original_name not in names
|
||||
assert all(re.fullmatch(r"^[a-zA-Z0-9_-]{1,64}$", name) for name in names)
|
||||
assert len(names) == len(set(names))
|
||||
return structured(await client.call_tool(safe_name, arguments or {}))
|
||||
@@ -0,0 +1,199 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
|
||||
from wf_mcp.broker.config import build_service_from_config, load_broker_config
|
||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
||||
from wf_mcp.server import create_server_client
|
||||
|
||||
from ..test_support import fixture_server_path, local_temp_root
|
||||
from .conftest import server_config, structured
|
||||
|
||||
|
||||
def test_config_built_service_uses_persistent_tool_executor() -> None:
|
||||
config = server_config()
|
||||
|
||||
service = build_service_from_config(config)
|
||||
|
||||
assert service.adapters["fixture"].__class__.__name__ == "McpSdkAdapter"
|
||||
assert service.tool_executor is not None
|
||||
|
||||
|
||||
def test_server_exposes_upstream_admin_and_workflow_tools() -> None:
|
||||
config = server_config()
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
tools_by_name = {tool.name: tool for tool in tools}
|
||||
assert "fixture.personal.echo_tool" in names
|
||||
assert "wf.admin.list_connections" in names
|
||||
assert "wf.admin.get_connection_statuses" in names
|
||||
assert "wf.admin.refresh_connection_catalog" in names
|
||||
assert "wf.admin.get_catalog" in names
|
||||
assert "wf.admin.get_planner_catalog" in names
|
||||
assert "wf.admin.list_sources" in names
|
||||
assert "wf.admin.inspect_source" in names
|
||||
assert "wf.admin.read_resource" in names
|
||||
assert "wf.admin.render_prompt" in names
|
||||
assert "wf.admin.invoke_method" in names
|
||||
assert "wf.admin.get_events" in names
|
||||
assert "wf.workflow.list_artifacts" in names
|
||||
assert "wf.workflow.list_capabilities" in names
|
||||
assert "wf.workflow.inspect_capability" in names
|
||||
assert "wf.workflow.call_capability" in names
|
||||
assert "wf.workflow.validate_draft" in names
|
||||
assert "wf.workflow.compile_draft" in names
|
||||
assert "wf.workflow.create_artifact_from_draft" in names
|
||||
assert "wf.workflow.patch_draft" in names
|
||||
assert "wf.workflow.list_draft_workspaces" in names
|
||||
assert "wf.workflow.create_draft_workspace" in names
|
||||
assert "wf.workflow.get_draft_workspace" in names
|
||||
assert "wf.workflow.delete_draft_workspace" in names
|
||||
assert "wf.workflow.patch_draft_workspace" in names
|
||||
assert "wf.workflow.validate_draft_workspace" in names
|
||||
assert "wf.workflow.set_draft_name" in names
|
||||
assert "wf.workflow.set_draft_route" in names
|
||||
assert "wf.workflow.set_step_input_map" in names
|
||||
assert "wf.workflow.set_step_output_map" in names
|
||||
assert "wf.workflow.create_minimal_draft_workspace" in names
|
||||
assert "wf.workflow.create_draft_workspace_from_capability" in names
|
||||
assert "wf.workflow.create_artifact_from_workspace" in names
|
||||
assert "wf.workflow.create_wrapper_from_workspace" in names
|
||||
assert "wf.workflow.run_deployment" in names
|
||||
call_capability_schema = tools_by_name[
|
||||
"wf.workflow.call_capability"
|
||||
].outputSchema
|
||||
assert call_capability_schema is not None
|
||||
assert "source_id" in call_capability_schema["properties"]
|
||||
assert "kind" in call_capability_schema["properties"]
|
||||
assert "diagnostics" in call_capability_schema["properties"]
|
||||
create_workspace_schema = tools_by_name[
|
||||
"wf.workflow.create_draft_workspace"
|
||||
].outputSchema
|
||||
assert create_workspace_schema is not None
|
||||
assert "workspace_id" in create_workspace_schema["properties"]
|
||||
assert "revision" in create_workspace_schema["properties"]
|
||||
list_sources_schema = tools_by_name["wf.admin.list_sources"].inputSchema
|
||||
assert (
|
||||
"inspect_source"
|
||||
in list_sources_schema["properties"]["limit"]["description"]
|
||||
)
|
||||
inspect_source_schema = tools_by_name["wf.admin.inspect_source"].inputSchema
|
||||
assert (
|
||||
"Exact source id"
|
||||
in inspect_source_schema["properties"]["source_id"]["description"]
|
||||
)
|
||||
minimal_workspace_input = tools_by_name[
|
||||
"wf.workflow.create_minimal_draft_workspace"
|
||||
].inputSchema
|
||||
minimal_request = minimal_workspace_input["properties"]["request"]
|
||||
assert minimal_request["properties"]["workspace_id"]["pattern"]
|
||||
assert "error_message_source" in minimal_request["properties"]
|
||||
assert "input" in minimal_request["properties"]
|
||||
assert "output" in minimal_request["properties"]
|
||||
assert "input_map" in minimal_request["properties"]
|
||||
assert (
|
||||
minimal_request["properties"]["input_schema"]["description"]
|
||||
== "Public input JSON Schema for the workflow or wrapper being "
|
||||
"drafted."
|
||||
)
|
||||
from_capability_input = tools_by_name[
|
||||
"wf.workflow.create_draft_workspace_from_capability"
|
||||
].inputSchema
|
||||
from_capability_request = from_capability_input["properties"]["request"]
|
||||
assert "capability_name" in from_capability_request["properties"]
|
||||
assert "input_schema" in from_capability_request["properties"]
|
||||
assert "input" in from_capability_request["properties"]
|
||||
assert "output" in from_capability_request["properties"]
|
||||
assert "output_map" in from_capability_request["properties"]
|
||||
from_capability_output = tools_by_name[
|
||||
"wf.workflow.create_draft_workspace_from_capability"
|
||||
].outputSchema
|
||||
assert from_capability_output is not None
|
||||
assert "wrapper_hints" in from_capability_output["properties"]
|
||||
wrapper_workspace_input = tools_by_name[
|
||||
"wf.workflow.create_wrapper_from_workspace"
|
||||
].inputSchema
|
||||
wrapper_request = wrapper_workspace_input["properties"]["request"]
|
||||
assert "kind" not in wrapper_request["properties"]
|
||||
assert "artifact_id" in wrapper_request["properties"]
|
||||
|
||||
echo_result = await client.call_tool(
|
||||
"fixture.personal.echo_tool",
|
||||
{"text": "hello"},
|
||||
)
|
||||
artifacts_result = await client.call_tool("wf.workflow.list_artifacts")
|
||||
capability_result = await client.call_tool(
|
||||
"wf.workflow.call_capability",
|
||||
{
|
||||
"qualified_name": "wf.std.constant",
|
||||
"payload": {"value": "hello"},
|
||||
},
|
||||
)
|
||||
sources_result = await client.call_tool("wf.admin.list_sources")
|
||||
|
||||
assert structured(echo_result)["echoed"] == "hello"
|
||||
assert structured(artifacts_result)["nodes"] == []
|
||||
assert structured(artifacts_result)["total"] == 0
|
||||
assert structured(capability_result)["outcome"] == "ok"
|
||||
assert structured(capability_result)["output"] == {"value": "hello"}
|
||||
source_ids = {
|
||||
source["id"] for source in structured(sources_result)["sources"]
|
||||
}
|
||||
assert "wf.admin" in source_ids
|
||||
assert "wf.docs" in source_ids
|
||||
assert "wf.std" in source_ids
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_reuses_real_upstream_session_across_workflow_requests() -> None:
|
||||
"""Workflow node calls may share one stateful MCP session across requests."""
|
||||
config = server_config()
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config)
|
||||
async with client:
|
||||
await client.call_tool(
|
||||
"wf.admin.refresh_connection_catalog",
|
||||
{"connection_id": "fixture.personal"},
|
||||
)
|
||||
first = await client.call_tool(
|
||||
"wf.workflow.call_capability",
|
||||
{
|
||||
"qualified_name": "fixture.personal.echo_tool",
|
||||
"payload": {"text": "one"},
|
||||
},
|
||||
)
|
||||
second = await client.call_tool(
|
||||
"wf.workflow.call_capability",
|
||||
{
|
||||
"qualified_name": "fixture.personal.echo_tool",
|
||||
"payload": {"text": "two"},
|
||||
},
|
||||
)
|
||||
|
||||
assert structured(first)["output"]["echoed"] == "one"
|
||||
assert structured(second)["output"]["echoed"] == "two"
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_can_hide_admin_tools() -> None:
|
||||
config = server_config()
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
assert "fixture.personal.echo_tool" in names
|
||||
assert "wf.workflow.list_artifacts" in names
|
||||
assert "wf.admin.list_connections" not in names
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
@@ -0,0 +1,160 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
|
||||
from mcp import types as mcp_types
|
||||
|
||||
from wf_mcp.broker.config import load_broker_config
|
||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
||||
from wf_mcp.server import create_server_client
|
||||
|
||||
from ..test_support import fixture_server_path, local_temp_root
|
||||
from .conftest import server_config, structured
|
||||
|
||||
|
||||
def test_server_exposes_platform_documentation_resources() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_docs_resource_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
resources = await client.list_resources()
|
||||
uris = [str(resource.uri) for resource in resources]
|
||||
assert "wf://docs/operator-manual" in uris
|
||||
assert "wf://docs/workflow-capabilities" in uris
|
||||
assert "wf://docs/workflow-drafts" in uris
|
||||
|
||||
result = await client.read_resource("wf://docs/operator-manual")
|
||||
assert isinstance(result[0], mcp_types.TextResourceContents)
|
||||
assert "wf_mcp Operator Manual" in result[0].text
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_exposes_platform_documentation_prompts() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_docs_prompt_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
prompts = await client.list_prompts()
|
||||
names = [prompt.name for prompt in prompts]
|
||||
assert "wf.docs.operator_guide" in names
|
||||
|
||||
result = await client.get_prompt("wf.docs.operator_guide")
|
||||
content = result.messages[0].content
|
||||
assert isinstance(content, mcp_types.TextContent)
|
||||
assert "wf://docs/operator-manual" in content.text
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_admin_tools_can_read_local_documentation_source() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_admin_docs_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(
|
||||
config,
|
||||
resources_as_tools=True,
|
||||
prompts_as_tools=True,
|
||||
safe_tool_names=True,
|
||||
)
|
||||
async with client:
|
||||
resource = await client.call_tool(
|
||||
"wf_admin_read_resource",
|
||||
{"qualified_name": "wf.docs.workflow_capabilities"},
|
||||
)
|
||||
prompt = await client.call_tool(
|
||||
"wf_admin_render_prompt",
|
||||
{"qualified_name": "wf.docs.workflow_authoring_guide"},
|
||||
)
|
||||
|
||||
resource_payload = structured(resource)
|
||||
prompt_payload = structured(prompt)
|
||||
assert resource_payload["contents"][0]["uri"] == (
|
||||
"wf://docs/workflow-capabilities"
|
||||
)
|
||||
assert "# Workflow Capabilities" in resource_payload["contents"][0]["text"]
|
||||
assert (
|
||||
"wf://docs/workflow-capabilities"
|
||||
in (prompt_payload["messages"][0]["content"]["text"])
|
||||
)
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_reload_syncs_service_connection_source_enabled_state() -> None:
|
||||
tmp_path = local_temp_root() / "unified_reload_service_source_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,
|
||||
"metadata": {
|
||||
"transport": "stdio",
|
||||
"command": sys.executable,
|
||||
"args": [fixture_server_path()],
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
config = load_broker_config(config_path)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, config_path=config_path)
|
||||
async with client:
|
||||
await client.call_tool(
|
||||
"wf.admin.refresh_connection_catalog",
|
||||
{"connection_id": "fixture.personal"},
|
||||
)
|
||||
before = await client.call_tool(
|
||||
"wf.workflow.list_capabilities",
|
||||
{"source_id": "fixture.personal"},
|
||||
)
|
||||
assert structured(before)["capabilities"] == []
|
||||
|
||||
await client.call_tool(
|
||||
"wf.admin.enable_connection",
|
||||
{"connection_id": "fixture.personal"},
|
||||
)
|
||||
await client.call_tool("wf.admin.reload_config")
|
||||
|
||||
sources = await client.call_tool("wf.admin.list_sources", {"limit": 100})
|
||||
fixture_source = next(
|
||||
source
|
||||
for source in structured(sources)["sources"]
|
||||
if source["id"] == "fixture.personal"
|
||||
)
|
||||
assert fixture_source["enabled"] is True
|
||||
|
||||
after = await client.call_tool(
|
||||
"wf.workflow.list_capabilities",
|
||||
{"source_id": "fixture.personal"},
|
||||
)
|
||||
names = [
|
||||
capability["name"] for capability in structured(after)["capabilities"]
|
||||
]
|
||||
assert "fixture.personal.echo_tool" in names
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
@@ -0,0 +1,232 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
||||
from wf_mcp.server import create_server_client
|
||||
|
||||
from ..test_support import fixture_server_path, local_temp_root
|
||||
from .conftest import assert_safe_tool_maps, server_config, structured
|
||||
|
||||
|
||||
def test_server_search_mode_pins_stable_control_and_workflow_tools() -> None:
|
||||
config = server_config()
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_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 "call_tool" in names
|
||||
assert "wf.admin.list_sources" in names
|
||||
assert "wf.admin.list_connections" in names
|
||||
assert "wf.admin.get_connection_statuses" in names
|
||||
assert "wf.admin.reload_config" in names
|
||||
assert "wf.admin.list_proxy_tools" in names
|
||||
assert "wf.admin.get_proxy_tool" in names
|
||||
assert "wf.workflow.list_artifacts" in names
|
||||
assert "wf.workflow.list_capabilities" in names
|
||||
assert "wf.workflow.inspect_capability" in names
|
||||
assert "wf.workflow.call_capability" in names
|
||||
assert "wf.workflow.list_draft_workspaces" in names
|
||||
assert "wf.workflow.create_draft_workspace" in names
|
||||
assert "wf.workflow.get_draft_workspace" in names
|
||||
assert "wf.workflow.delete_draft_workspace" in names
|
||||
assert "wf.workflow.patch_draft_workspace" in names
|
||||
assert "wf.workflow.validate_draft_workspace" in names
|
||||
assert "wf.workflow.set_draft_name" in names
|
||||
assert "wf.workflow.set_draft_route" in names
|
||||
assert "wf.workflow.set_step_input_map" in names
|
||||
assert "wf.workflow.set_step_output_map" in names
|
||||
assert "wf.workflow.create_minimal_draft_workspace" in names
|
||||
assert "wf.workflow.create_artifact_from_workspace" in names
|
||||
assert "wf.workflow.create_wrapper_from_workspace" in names
|
||||
assert "wf.workflow.inspect_artifact" in names
|
||||
assert "wf.workflow.list_deployments" in names
|
||||
assert "wf.workflow.inspect_deployment" in names
|
||||
assert "wf.workflow.save_deployment" in names
|
||||
assert "wf.workflow.validate_deployment" in names
|
||||
assert "wf.workflow.run_deployment" in names
|
||||
|
||||
assert "wf.workflow.validate_draft" not in names
|
||||
assert "wf.workflow.compile_draft" not in names
|
||||
assert "wf.workflow.create_artifact_from_plan" not in names
|
||||
assert "wf.workflow.create_artifact_from_draft" not in names
|
||||
assert "wf.workflow.patch_draft" not in names
|
||||
assert "wf.admin.call_tool" not in names
|
||||
assert "fixture.personal.echo_tool" not in names
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_search_mode_can_use_safe_tool_names() -> None:
|
||||
config = server_config()
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(
|
||||
config,
|
||||
search_tools=True,
|
||||
safe_tool_names=True,
|
||||
)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
|
||||
assert "search_tools" in names
|
||||
assert "call_tool" in names
|
||||
assert "wf_admin_list_sources" in names
|
||||
assert "wf_workflow_call_capability" in names
|
||||
assert "wf.admin.list_sources" not in names
|
||||
|
||||
result = await assert_safe_tool_maps(
|
||||
client,
|
||||
original_name="wf.admin.list_sources",
|
||||
safe_name="wf_admin_list_sources",
|
||||
)
|
||||
source_ids = {source["id"] for source in result["sources"]}
|
||||
assert "wf.std" in source_ids
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_safe_tool_names_adapts_dotted_runtime_names() -> None:
|
||||
config = server_config()
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, safe_tool_names=True)
|
||||
async with client:
|
||||
artifacts = await assert_safe_tool_maps(
|
||||
client,
|
||||
original_name="wf.workflow.list_artifacts",
|
||||
safe_name="wf_workflow_list_artifacts",
|
||||
)
|
||||
echo = await assert_safe_tool_maps(
|
||||
client,
|
||||
original_name="fixture.personal.echo_tool",
|
||||
safe_name="fixture_personal_echo_tool",
|
||||
arguments={"text": "hello"},
|
||||
)
|
||||
|
||||
assert artifacts["nodes"] == []
|
||||
assert artifacts["total"] == 0
|
||||
assert echo["echoed"] == "hello"
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_workflow_tools_have_human_metadata() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_metadata_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
by_name = {tool.name: tool for tool in tools}
|
||||
list_artifacts = by_name["wf.workflow.list_artifacts"]
|
||||
run_deployment = by_name["wf.workflow.run_deployment"]
|
||||
inspect_run = by_name["wf.workflow.inspect_run"]
|
||||
read_run_trace = by_name["wf.workflow.read_run_trace"]
|
||||
|
||||
assert list_artifacts.title == "List Workflow Artifacts"
|
||||
assert "saved workflow artifacts" in (list_artifacts.description or "")
|
||||
assert "query" in list_artifacts.inputSchema["properties"]
|
||||
assert "kind" in list_artifacts.inputSchema["properties"]
|
||||
assert "cursor" in list_artifacts.inputSchema["properties"]
|
||||
assert "limit" in list_artifacts.inputSchema["properties"]
|
||||
assert run_deployment.title == "Run Workflow Deployment"
|
||||
assert "deployment_id" in (run_deployment.description or "")
|
||||
assert "trace_range" in run_deployment.inputSchema["properties"]
|
||||
trace_range_schema = run_deployment.inputSchema["properties"]["trace_range"]
|
||||
assert "Debug traces" in trace_range_schema.get("description", "")
|
||||
assert "null" in [
|
||||
option.get("type") for option in trace_range_schema["anyOf"]
|
||||
]
|
||||
assert inspect_run.title == "Inspect Workflow Run"
|
||||
assert "trace" in (inspect_run.description or "").lower()
|
||||
read_trace_schema = read_run_trace.inputSchema["properties"]["trace_range"]
|
||||
assert "Debug traces" in read_trace_schema.get("description", "")
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_create_artifact_from_plan_exposes_plan_as_plain_object() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_create_artifact_schema_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
by_name = {tool.name: tool for tool in tools}
|
||||
schema = by_name["wf.workflow.create_artifact_from_plan"].inputSchema
|
||||
plan_schema = schema["properties"]["plan"]
|
||||
|
||||
assert plan_schema["type"] == "object"
|
||||
assert plan_schema.get("additionalProperties") is True
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_draft_tools_expose_plain_object_and_patch_array_schemas() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_draft_schema_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
by_name = {tool.name: tool for tool in tools}
|
||||
|
||||
validate_schema = by_name["wf.workflow.validate_draft"].inputSchema
|
||||
validate_draft_schema = validate_schema["properties"]["draft"]
|
||||
create_schema = by_name[
|
||||
"wf.workflow.create_artifact_from_draft"
|
||||
].inputSchema
|
||||
create_draft_schema = create_schema["properties"]["draft"]
|
||||
patch_schema = by_name["wf.workflow.patch_draft"].inputSchema
|
||||
patch_draft_schema = patch_schema["properties"]["draft"]
|
||||
patch_patch_schema = patch_schema["properties"]["patch"]
|
||||
|
||||
assert validate_draft_schema["type"] == "object"
|
||||
assert validate_draft_schema.get("additionalProperties") is True
|
||||
assert create_draft_schema["type"] == "object"
|
||||
assert create_draft_schema.get("additionalProperties") is True
|
||||
assert patch_draft_schema["type"] == "object"
|
||||
assert patch_patch_schema["type"] == "array"
|
||||
assert "$defs" not in patch_patch_schema
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_admin_tools_have_human_metadata() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_admin_metadata_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
by_name = {tool.name: tool for tool in tools}
|
||||
list_connections = by_name["wf.admin.list_connections"]
|
||||
reload_config = by_name["wf.admin.reload_config"]
|
||||
|
||||
assert list_connections.title == "List Connections"
|
||||
assert "configured MCP connections" in (list_connections.description or "")
|
||||
assert reload_config.title == "Reload Config"
|
||||
assert "remount" in (reload_config.description or "")
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
@@ -1,689 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from mcp import types as mcp_types
|
||||
|
||||
from wf_mcp.broker.config import build_service_from_config, load_broker_config
|
||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
||||
from wf_mcp.server import create_server_client
|
||||
|
||||
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_config_built_service_uses_persistent_tool_executor() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "runtime_config_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
server="fixture",
|
||||
account="personal",
|
||||
metadata={
|
||||
"transport": "stdio",
|
||||
"command": sys.executable,
|
||||
"args": [fixture_server_path()],
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
service = build_service_from_config(config)
|
||||
|
||||
assert service.adapters["fixture"].__class__.__name__ == "McpSdkAdapter"
|
||||
assert service.tool_executor is not None
|
||||
|
||||
|
||||
async def _assert_safe_tool_maps(
|
||||
client: Any,
|
||||
*,
|
||||
original_name: str,
|
||||
safe_name: str,
|
||||
arguments: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Assert one safe public tool name maps back to its original tool."""
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
assert safe_name in names
|
||||
assert original_name not in names
|
||||
assert all(re.fullmatch(r"^[a-zA-Z0-9_-]{1,64}$", name) for name in names)
|
||||
assert len(names) == len(set(names))
|
||||
return _structured(await client.call_tool(safe_name, arguments or {}))
|
||||
|
||||
|
||||
def test_server_exposes_upstream_admin_and_workflow_tools() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_server_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_server_client(config)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
tools_by_name = {tool.name: tool for tool in tools}
|
||||
assert "fixture.personal.echo_tool" in names
|
||||
assert "wf.admin.list_connections" in names
|
||||
assert "wf.admin.get_connection_statuses" in names
|
||||
assert "wf.admin.refresh_connection_catalog" in names
|
||||
assert "wf.admin.get_catalog" in names
|
||||
assert "wf.admin.get_planner_catalog" in names
|
||||
assert "wf.admin.list_sources" in names
|
||||
assert "wf.admin.inspect_source" in names
|
||||
assert "wf.admin.read_resource" in names
|
||||
assert "wf.admin.render_prompt" in names
|
||||
assert "wf.admin.invoke_method" in names
|
||||
assert "wf.admin.get_events" in names
|
||||
assert "wf.workflow.list_artifacts" in names
|
||||
assert "wf.workflow.list_capabilities" in names
|
||||
assert "wf.workflow.inspect_capability" in names
|
||||
assert "wf.workflow.call_capability" in names
|
||||
assert "wf.workflow.validate_draft" in names
|
||||
assert "wf.workflow.compile_draft" in names
|
||||
assert "wf.workflow.create_artifact_from_draft" in names
|
||||
assert "wf.workflow.patch_draft" in names
|
||||
assert "wf.workflow.list_draft_workspaces" in names
|
||||
assert "wf.workflow.create_draft_workspace" in names
|
||||
assert "wf.workflow.get_draft_workspace" in names
|
||||
assert "wf.workflow.delete_draft_workspace" in names
|
||||
assert "wf.workflow.patch_draft_workspace" in names
|
||||
assert "wf.workflow.validate_draft_workspace" in names
|
||||
assert "wf.workflow.set_draft_name" in names
|
||||
assert "wf.workflow.set_draft_route" in names
|
||||
assert "wf.workflow.set_step_input_map" in names
|
||||
assert "wf.workflow.set_step_output_map" in names
|
||||
assert "wf.workflow.create_minimal_draft_workspace" in names
|
||||
assert "wf.workflow.create_draft_workspace_from_capability" in names
|
||||
assert "wf.workflow.create_artifact_from_workspace" in names
|
||||
assert "wf.workflow.create_wrapper_from_workspace" in names
|
||||
assert "wf.workflow.run_deployment" in names
|
||||
call_capability_schema = tools_by_name[
|
||||
"wf.workflow.call_capability"
|
||||
].outputSchema
|
||||
assert call_capability_schema is not None
|
||||
assert "source_id" in call_capability_schema["properties"]
|
||||
assert "kind" in call_capability_schema["properties"]
|
||||
assert "diagnostics" in call_capability_schema["properties"]
|
||||
create_workspace_schema = tools_by_name[
|
||||
"wf.workflow.create_draft_workspace"
|
||||
].outputSchema
|
||||
assert create_workspace_schema is not None
|
||||
assert "workspace_id" in create_workspace_schema["properties"]
|
||||
assert "revision" in create_workspace_schema["properties"]
|
||||
list_sources_schema = tools_by_name["wf.admin.list_sources"].inputSchema
|
||||
assert (
|
||||
"inspect_source"
|
||||
in list_sources_schema["properties"]["limit"]["description"]
|
||||
)
|
||||
inspect_source_schema = tools_by_name["wf.admin.inspect_source"].inputSchema
|
||||
assert (
|
||||
"Exact source id"
|
||||
in inspect_source_schema["properties"]["source_id"]["description"]
|
||||
)
|
||||
minimal_workspace_input = tools_by_name[
|
||||
"wf.workflow.create_minimal_draft_workspace"
|
||||
].inputSchema
|
||||
minimal_request = minimal_workspace_input["properties"]["request"]
|
||||
assert minimal_request["properties"]["workspace_id"]["pattern"]
|
||||
assert "error_message_source" in minimal_request["properties"]
|
||||
assert "input" in minimal_request["properties"]
|
||||
assert "output" in minimal_request["properties"]
|
||||
assert "input_map" in minimal_request["properties"]
|
||||
assert (
|
||||
minimal_request["properties"]["input_schema"]["description"]
|
||||
== "Public input JSON Schema for the workflow or wrapper being "
|
||||
"drafted."
|
||||
)
|
||||
from_capability_input = tools_by_name[
|
||||
"wf.workflow.create_draft_workspace_from_capability"
|
||||
].inputSchema
|
||||
from_capability_request = from_capability_input["properties"]["request"]
|
||||
assert "capability_name" in from_capability_request["properties"]
|
||||
assert "input_schema" in from_capability_request["properties"]
|
||||
assert "input" in from_capability_request["properties"]
|
||||
assert "output" in from_capability_request["properties"]
|
||||
assert "output_map" in from_capability_request["properties"]
|
||||
from_capability_output = tools_by_name[
|
||||
"wf.workflow.create_draft_workspace_from_capability"
|
||||
].outputSchema
|
||||
assert from_capability_output is not None
|
||||
assert "wrapper_hints" in from_capability_output["properties"]
|
||||
wrapper_workspace_input = tools_by_name[
|
||||
"wf.workflow.create_wrapper_from_workspace"
|
||||
].inputSchema
|
||||
wrapper_request = wrapper_workspace_input["properties"]["request"]
|
||||
assert "kind" not in wrapper_request["properties"]
|
||||
assert "artifact_id" in wrapper_request["properties"]
|
||||
|
||||
echo_result = await client.call_tool(
|
||||
"fixture.personal.echo_tool",
|
||||
{"text": "hello"},
|
||||
)
|
||||
artifacts_result = await client.call_tool("wf.workflow.list_artifacts")
|
||||
capability_result = await client.call_tool(
|
||||
"wf.workflow.call_capability",
|
||||
{
|
||||
"qualified_name": "wf.std.constant",
|
||||
"payload": {"value": "hello"},
|
||||
},
|
||||
)
|
||||
sources_result = await client.call_tool("wf.admin.list_sources")
|
||||
|
||||
assert _structured(echo_result)["echoed"] == "hello"
|
||||
assert _structured(artifacts_result)["nodes"] == []
|
||||
assert _structured(artifacts_result)["total"] == 0
|
||||
assert _structured(capability_result)["outcome"] == "ok"
|
||||
assert _structured(capability_result)["output"] == {"value": "hello"}
|
||||
source_ids = {
|
||||
source["id"] for source in _structured(sources_result)["sources"]
|
||||
}
|
||||
assert "wf.admin" in source_ids
|
||||
assert "wf.docs" in source_ids
|
||||
assert "wf.std" in source_ids
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_reuses_real_upstream_session_across_workflow_requests() -> None:
|
||||
"""Workflow node calls may share one stateful MCP session across requests."""
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "workflow_persistent_fixture_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_server_client(config)
|
||||
async with client:
|
||||
await client.call_tool(
|
||||
"wf.admin.refresh_connection_catalog",
|
||||
{"connection_id": "fixture.personal"},
|
||||
)
|
||||
first = await client.call_tool(
|
||||
"wf.workflow.call_capability",
|
||||
{
|
||||
"qualified_name": "fixture.personal.echo_tool",
|
||||
"payload": {"text": "one"},
|
||||
},
|
||||
)
|
||||
second = await client.call_tool(
|
||||
"wf.workflow.call_capability",
|
||||
{
|
||||
"qualified_name": "fixture.personal.echo_tool",
|
||||
"payload": {"text": "two"},
|
||||
},
|
||||
)
|
||||
|
||||
assert _structured(first)["output"]["echoed"] == "one"
|
||||
assert _structured(second)["output"]["echoed"] == "two"
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_can_hide_admin_tools() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_no_admin_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_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
assert "fixture.personal.echo_tool" in names
|
||||
assert "wf.workflow.list_artifacts" in names
|
||||
assert "wf.admin.list_connections" not in names
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_search_mode_pins_stable_control_and_workflow_tools() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "server_search_mode_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_server_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 "call_tool" in names
|
||||
assert "wf.admin.list_sources" in names
|
||||
assert "wf.admin.list_connections" in names
|
||||
assert "wf.admin.get_connection_statuses" in names
|
||||
assert "wf.admin.reload_config" in names
|
||||
assert "wf.admin.list_proxy_tools" in names
|
||||
assert "wf.admin.get_proxy_tool" in names
|
||||
assert "wf.workflow.list_artifacts" in names
|
||||
assert "wf.workflow.list_capabilities" in names
|
||||
assert "wf.workflow.inspect_capability" in names
|
||||
assert "wf.workflow.call_capability" in names
|
||||
assert "wf.workflow.list_draft_workspaces" in names
|
||||
assert "wf.workflow.create_draft_workspace" in names
|
||||
assert "wf.workflow.get_draft_workspace" in names
|
||||
assert "wf.workflow.delete_draft_workspace" in names
|
||||
assert "wf.workflow.patch_draft_workspace" in names
|
||||
assert "wf.workflow.validate_draft_workspace" in names
|
||||
assert "wf.workflow.set_draft_name" in names
|
||||
assert "wf.workflow.set_draft_route" in names
|
||||
assert "wf.workflow.set_step_input_map" in names
|
||||
assert "wf.workflow.set_step_output_map" in names
|
||||
assert "wf.workflow.create_minimal_draft_workspace" in names
|
||||
assert "wf.workflow.create_artifact_from_workspace" in names
|
||||
assert "wf.workflow.create_wrapper_from_workspace" in names
|
||||
assert "wf.workflow.inspect_artifact" in names
|
||||
assert "wf.workflow.list_deployments" in names
|
||||
assert "wf.workflow.inspect_deployment" in names
|
||||
assert "wf.workflow.save_deployment" in names
|
||||
assert "wf.workflow.validate_deployment" in names
|
||||
assert "wf.workflow.run_deployment" in names
|
||||
|
||||
assert "wf.workflow.validate_draft" not in names
|
||||
assert "wf.workflow.compile_draft" not in names
|
||||
assert "wf.workflow.create_artifact_from_plan" not in names
|
||||
assert "wf.workflow.create_artifact_from_draft" not in names
|
||||
assert "wf.workflow.patch_draft" not in names
|
||||
assert "wf.admin.call_tool" not in names
|
||||
assert "fixture.personal.echo_tool" not in names
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_search_mode_can_use_safe_tool_names() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "server_search_safe_names_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_server_client(
|
||||
config,
|
||||
search_tools=True,
|
||||
safe_tool_names=True,
|
||||
)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
|
||||
assert "search_tools" in names
|
||||
assert "call_tool" in names
|
||||
assert "wf_admin_list_sources" in names
|
||||
assert "wf_workflow_call_capability" in names
|
||||
assert "wf.admin.list_sources" not in names
|
||||
|
||||
result = await _assert_safe_tool_maps(
|
||||
client,
|
||||
original_name="wf.admin.list_sources",
|
||||
safe_name="wf_admin_list_sources",
|
||||
)
|
||||
source_ids = {source["id"] for source in result["sources"]}
|
||||
assert "wf.std" in source_ids
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_safe_tool_names_adapts_dotted_runtime_names() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "server_safe_tool_names_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_server_client(config, safe_tool_names=True)
|
||||
async with client:
|
||||
artifacts = await _assert_safe_tool_maps(
|
||||
client,
|
||||
original_name="wf.workflow.list_artifacts",
|
||||
safe_name="wf_workflow_list_artifacts",
|
||||
)
|
||||
echo = await _assert_safe_tool_maps(
|
||||
client,
|
||||
original_name="fixture.personal.echo_tool",
|
||||
safe_name="fixture_personal_echo_tool",
|
||||
arguments={"text": "hello"},
|
||||
)
|
||||
|
||||
assert artifacts["nodes"] == []
|
||||
assert artifacts["total"] == 0
|
||||
assert echo["echoed"] == "hello"
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_workflow_tools_have_human_metadata() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_metadata_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
by_name = {tool.name: tool for tool in tools}
|
||||
list_artifacts = by_name["wf.workflow.list_artifacts"]
|
||||
run_deployment = by_name["wf.workflow.run_deployment"]
|
||||
inspect_run = by_name["wf.workflow.inspect_run"]
|
||||
read_run_trace = by_name["wf.workflow.read_run_trace"]
|
||||
|
||||
assert list_artifacts.title == "List Workflow Artifacts"
|
||||
assert "saved workflow artifacts" in (list_artifacts.description or "")
|
||||
assert "query" in list_artifacts.inputSchema["properties"]
|
||||
assert "kind" in list_artifacts.inputSchema["properties"]
|
||||
assert "cursor" in list_artifacts.inputSchema["properties"]
|
||||
assert "limit" in list_artifacts.inputSchema["properties"]
|
||||
assert run_deployment.title == "Run Workflow Deployment"
|
||||
assert "deployment_id" in (run_deployment.description or "")
|
||||
assert "trace_range" in run_deployment.inputSchema["properties"]
|
||||
trace_range_schema = run_deployment.inputSchema["properties"]["trace_range"]
|
||||
assert "Debug traces" in trace_range_schema.get("description", "")
|
||||
assert "null" in [
|
||||
option.get("type") for option in trace_range_schema["anyOf"]
|
||||
]
|
||||
assert inspect_run.title == "Inspect Workflow Run"
|
||||
assert "trace" in (inspect_run.description or "").lower()
|
||||
read_trace_schema = read_run_trace.inputSchema["properties"]["trace_range"]
|
||||
assert "Debug traces" in read_trace_schema.get("description", "")
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_create_artifact_from_plan_exposes_plan_as_plain_object() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_create_artifact_schema_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
by_name = {tool.name: tool for tool in tools}
|
||||
schema = by_name["wf.workflow.create_artifact_from_plan"].inputSchema
|
||||
plan_schema = schema["properties"]["plan"]
|
||||
|
||||
assert plan_schema["type"] == "object"
|
||||
assert plan_schema.get("additionalProperties") is True
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_draft_tools_expose_plain_object_and_patch_array_schemas() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_draft_schema_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
by_name = {tool.name: tool for tool in tools}
|
||||
|
||||
validate_schema = by_name["wf.workflow.validate_draft"].inputSchema
|
||||
validate_draft_schema = validate_schema["properties"]["draft"]
|
||||
create_schema = by_name[
|
||||
"wf.workflow.create_artifact_from_draft"
|
||||
].inputSchema
|
||||
create_draft_schema = create_schema["properties"]["draft"]
|
||||
patch_schema = by_name["wf.workflow.patch_draft"].inputSchema
|
||||
patch_draft_schema = patch_schema["properties"]["draft"]
|
||||
patch_patch_schema = patch_schema["properties"]["patch"]
|
||||
|
||||
assert validate_draft_schema["type"] == "object"
|
||||
assert validate_draft_schema.get("additionalProperties") is True
|
||||
assert create_draft_schema["type"] == "object"
|
||||
assert create_draft_schema.get("additionalProperties") is True
|
||||
assert patch_draft_schema["type"] == "object"
|
||||
assert patch_patch_schema["type"] == "array"
|
||||
assert "$defs" not in patch_patch_schema
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_exposes_platform_documentation_resources() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_docs_resource_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
resources = await client.list_resources()
|
||||
uris = [str(resource.uri) for resource in resources]
|
||||
assert "wf://docs/operator-manual" in uris
|
||||
assert "wf://docs/workflow-capabilities" in uris
|
||||
assert "wf://docs/workflow-drafts" in uris
|
||||
|
||||
result = await client.read_resource("wf://docs/operator-manual")
|
||||
assert isinstance(result[0], mcp_types.TextResourceContents)
|
||||
assert "wf_mcp Operator Manual" in result[0].text
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_exposes_platform_documentation_prompts() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_docs_prompt_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, admin_tools=False)
|
||||
async with client:
|
||||
prompts = await client.list_prompts()
|
||||
names = [prompt.name for prompt in prompts]
|
||||
assert "wf.docs.operator_guide" in names
|
||||
|
||||
result = await client.get_prompt("wf.docs.operator_guide")
|
||||
content = result.messages[0].content
|
||||
assert isinstance(content, mcp_types.TextContent)
|
||||
assert "wf://docs/operator-manual" in content.text
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_admin_tools_can_read_local_documentation_source() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_admin_docs_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(
|
||||
config,
|
||||
resources_as_tools=True,
|
||||
prompts_as_tools=True,
|
||||
safe_tool_names=True,
|
||||
)
|
||||
async with client:
|
||||
resource = await client.call_tool(
|
||||
"wf_admin_read_resource",
|
||||
{"qualified_name": "wf.docs.workflow_capabilities"},
|
||||
)
|
||||
prompt = await client.call_tool(
|
||||
"wf_admin_render_prompt",
|
||||
{"qualified_name": "wf.docs.workflow_authoring_guide"},
|
||||
)
|
||||
|
||||
resource_payload = _structured(resource)
|
||||
prompt_payload = _structured(prompt)
|
||||
assert resource_payload["contents"][0]["uri"] == (
|
||||
"wf://docs/workflow-capabilities"
|
||||
)
|
||||
assert "# Workflow Capabilities" in resource_payload["contents"][0]["text"]
|
||||
assert (
|
||||
"wf://docs/workflow-capabilities"
|
||||
in (prompt_payload["messages"][0]["content"]["text"])
|
||||
)
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_server_reload_syncs_service_connection_source_enabled_state() -> None:
|
||||
tmp_path = local_temp_root() / "unified_reload_service_source_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,
|
||||
"metadata": {
|
||||
"transport": "stdio",
|
||||
"command": sys.executable,
|
||||
"args": [fixture_server_path()],
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
config = load_broker_config(config_path)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config, config_path=config_path)
|
||||
async with client:
|
||||
await client.call_tool(
|
||||
"wf.admin.refresh_connection_catalog",
|
||||
{"connection_id": "fixture.personal"},
|
||||
)
|
||||
before = await client.call_tool(
|
||||
"wf.workflow.list_capabilities",
|
||||
{"source_id": "fixture.personal"},
|
||||
)
|
||||
assert _structured(before)["capabilities"] == []
|
||||
|
||||
await client.call_tool(
|
||||
"wf.admin.enable_connection",
|
||||
{"connection_id": "fixture.personal"},
|
||||
)
|
||||
await client.call_tool("wf.admin.reload_config")
|
||||
|
||||
sources = await client.call_tool("wf.admin.list_sources", {"limit": 100})
|
||||
fixture_source = next(
|
||||
source
|
||||
for source in _structured(sources)["sources"]
|
||||
if source["id"] == "fixture.personal"
|
||||
)
|
||||
assert fixture_source["enabled"] is True
|
||||
|
||||
after = await client.call_tool(
|
||||
"wf.workflow.list_capabilities",
|
||||
{"source_id": "fixture.personal"},
|
||||
)
|
||||
names = [
|
||||
capability["name"] for capability in _structured(after)["capabilities"]
|
||||
]
|
||||
assert "fixture.personal.echo_tool" in names
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_admin_tools_have_human_metadata() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "unified_admin_metadata_store",
|
||||
connections=[],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_server_client(config)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
by_name = {tool.name: tool for tool in tools}
|
||||
list_connections = by_name["wf.admin.list_connections"]
|
||||
reload_config = by_name["wf.admin.reload_config"]
|
||||
|
||||
assert list_connections.title == "List Connections"
|
||||
assert "configured MCP connections" in (list_connections.description or "")
|
||||
assert reload_config.title == "Reload Config"
|
||||
assert "remount" in (reload_config.description or "")
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
Reference in New Issue
Block a user