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" ].output_schema 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" ].output_schema 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"].input_schema assert ( "inspect_source" in list_sources_schema["properties"]["limit"]["description"] ) inspect_source_schema = tools_by_name[ "wf.admin.inspect_source" ].input_schema assert ( "Exact source id" in inspect_source_schema["properties"]["source_id"]["description"] ) minimal_workspace_input = tools_by_name[ "wf.workflow.create_minimal_draft_workspace" ].input_schema 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" ].input_schema 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" ].input_schema 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" ].input_schema 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" ].input_schema 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" ].input_schema 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" ].input_schema 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"].input_schema 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" ].input_schema 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" ].input_schema 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" ].output_schema 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.output_schema run_output = run_deployment.output_schema 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" ].input_schema 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())