Files
lda-wf/tests/wf_mcp/server/test_config.py
T

318 lines
15 KiB
Python

from __future__ import annotations
import asyncio
from typing import Any
from wf_mcp.broker.config import build_service_from_config
from wf_mcp.server import create_server_client
from .conftest import server_config, structured
def _resolve_local_ref(schema: Any, root: dict[str, Any]) -> dict[str, Any]:
"""Resolve one local MCP JSON Schema reference for structural assertions."""
assert isinstance(schema, dict)
ref = schema.get("$ref")
if isinstance(ref, str) and ref.startswith("#/$defs/"):
definitions = root.get("$defs")
assert isinstance(definitions, dict)
resolved = definitions.get(ref.removeprefix("#/$defs/"))
assert isinstance(resolved, dict)
return resolved
return schema
def _request_schema(tool_schema: dict[str, Any]) -> dict[str, Any]:
"""Return the request DTO whether MCP exposes it inline or as a local ref."""
request = tool_schema["properties"]["request"]
return _resolve_local_ref(request, tool_schema)
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_bindings" in names
assert "wf.workflow.set_step_output_bindings" in names
assert "wf.workflow.set_step_input_map" in names
assert "wf.workflow.set_step_output_map" in names
assert "wf.workflow.set_workflow_output_bindings" in names
assert "wf.workflow.set_workflow_output_map" in names
assert "wf.workflow.bind" in names
assert "wf.workflow.add_step_from_capability" in names
assert "wf.workflow.update_capability_step" in names
assert "wf.workflow.remove_draft_route" in names
assert "wf.workflow.remove_draft_step" in names
assert "wf.workflow.remove_draft_binding" 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 = _resolve_local_ref(
minimal_workspace_input["properties"]["request"],
minimal_workspace_input,
)
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 = _request_schema(from_capability_input)
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"]
set_input_schema = tools_by_name[
"wf.workflow.set_step_input_map"
].inputSchema
set_input_request = _request_schema(set_input_schema)
assert "merge" in set_input_request["properties"]
set_bindings_schema = tools_by_name[
"wf.workflow.set_step_input_bindings"
].inputSchema
set_bindings_request = _request_schema(set_bindings_schema)
assert "bindings" in set_bindings_request["properties"]
assert "merge" not in set_bindings_request["properties"]
set_output_bindings_schema = tools_by_name[
"wf.workflow.set_step_output_bindings"
].inputSchema
set_output_bindings_request = _request_schema(set_output_bindings_schema)
assert "bindings" in set_output_bindings_request["properties"]
assert "merge" not in set_output_bindings_request["properties"]
canonical_workflow_output_schema = tools_by_name[
"wf.workflow.set_workflow_output_bindings"
].inputSchema
canonical_workflow_output_request = _request_schema(
canonical_workflow_output_schema
)
assert "bindings" in canonical_workflow_output_request["properties"]
assert "merge" not in canonical_workflow_output_request["properties"]
set_workflow_output_schema = tools_by_name[
"wf.workflow.set_workflow_output_map"
].inputSchema
set_workflow_output_request = _request_schema(set_workflow_output_schema)
assert "output_map" in set_workflow_output_request["properties"]
assert "merge" in set_workflow_output_request["properties"]
bind_schema = tools_by_name["wf.workflow.bind"].inputSchema
bind_request = _request_schema(bind_schema)
assert set(bind_request["required"]) == {
"workspace_id",
"revision",
"step_id",
"source_path",
"target_path",
}
assert bind_request["properties"]["step_id"]["minLength"] == 1
assert bind_request["properties"]["source_path"]["minLength"] == 1
assert bind_request["properties"]["target_path"]["minLength"] == 1
add_step_schema = tools_by_name[
"wf.workflow.add_step_from_capability"
].inputSchema
add_step_request = _request_schema(add_step_schema)
assert "capability_name" in add_step_request["properties"]
assert "bind_outputs" in add_step_request["properties"]
assert "input_bindings" in add_step_request["properties"]
assert "desc" in add_step_request["properties"]
assert "retry" in add_step_request["properties"]
assert "timeout_seconds" in add_step_request["properties"]
update_step_schema = tools_by_name[
"wf.workflow.update_capability_step"
].inputSchema
update_step_request = _request_schema(update_step_schema)
assert "update" in update_step_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"]
assert "next_actions" in from_capability_output["properties"]
next_actions_schema = from_capability_output["properties"]["next_actions"]
assert "recommended_next_tool" in next_actions_schema["properties"]
assert "patch_examples" in next_actions_schema["properties"]
assert "can_continue" in next_actions_schema["properties"]
assert (
"Advisory"
in next_actions_schema["properties"]["can_continue"]["description"]
)
assert (
"Advisory"
in next_actions_schema["properties"]["can_save_now"]["description"]
)
validate_deployment = tools_by_name["wf.workflow.validate_deployment"]
run_deployment = tools_by_name["wf.workflow.run_deployment"]
validate_output = validate_deployment.outputSchema
run_output = run_deployment.outputSchema
assert validate_output is not None
assert "next_actions" in validate_output["properties"]
assert (
"recommended_next_tool"
in validate_output["properties"]["next_actions"]["properties"]
)
assert run_output is not None
assert "next_actions" in run_output["properties"]
assert (
"recommended_next_tool"
in run_output["properties"]["next_actions"]["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())