Files
lda-wf/docs/superpowers/research/2026-06-01-wf-api-extraction-map.md
T
2026-06-02 23:34:50 +07:00

34 KiB

wf_api Extraction Map

Executive Summary

WorkflowSurfaceHandlers (src/wf_mcp/workflow_surface/handlers.py:104) is a 1,130-line application-service class that owns all workflow-facing operations: capability discovery, artifact CRUD, draft workspace management, deployment validation, and durable run lifecycle. It currently depends on WfMcpService (src/wf_mcp/broker/service/core.py:79) via a single self.service field, but only uses a narrow slice of that 960-line god object.

The extraction seam is clean: WorkflowSurfaceHandlers touches ~6 distinct capabilities of WfMcpService (artifact store, draft store, run store, capability sources, workflow execution, event bus). None of these require MCP transport, connection adapters, or proxy machinery. The class is already used protocol-neutrally by wf_cli via CliContext (src/wf_cli/context.py:14).

Recommended first slice: Option B — introduce a WorkflowApi facade backed by a small WorkflowApiBackend ports object, adapt WfMcpService into it, then move the facade to wf_api. This avoids the large-class move (Option A) and the premature domain split (Option C).

Public Operation Inventory

WorkflowSurfaceHandlers — Public Methods

Method Line Category Input Parameters Return Shape Direct Dependencies Callers/Tests
list_artifacts 110 artifacts query, kind, cursor, limit {nodes, next_cursor, total} self.service.artifact_store, self.service.workflow_artifact_catalog_entry tools.py:49, artifacts.py:41, test_artifacts.py:11
list_capabilities 147 capabilities query, source_id, cursor, limit {capabilities, next_cursor, total} self.service.capability_sources tools.py:91, caps.py:41, test_capabilities.py:20
inspect_capability 193 capabilities qualified_name capability contract dict self.service.capability_sources tools.py:132, caps.py:65, test_capabilities.py:102
call_capability 213 capabilities qualified_name, payload, deployment_id {qualified_name, source_id, kind, deployment_id, outcome, output, diagnostics} self.service._get_qualified_spec, self.service.capability_sources, self.service.run_workflow_from_plan, self.service.artifact_store tools.py:144, test_capabilities.py:50, test_wrappers.py:69
save_artifact 416 artifacts artifact dict {artifact_id, version, saved} self.service.artifact_store, self.service._record_event tools.py:162
create_artifact_from_plan 437 artifacts artifact_id, version, title, plan, outcomes, kind, description, required_capabilities, source_bindings, created_from_catalog_version {artifact_id, version, saved} self.service.artifact_store, self.service._record_event tools.py:186, test_wrappers.py:15
validate_draft 492 drafts draft dict {status, diagnostics, compiled_plan} (none — pure wf_artifacts) tools.py:170, test_drafts.py:23
compile_draft 498 drafts draft dict {compiled_plan, required_capabilities} self.service (via _required_capabilities_for_plan) tools.py:178
create_artifact_from_draft 511 drafts/artifacts artifact_id, version, title, draft, outcomes, kind, description, required_capabilities, source_bindings, created_from_catalog_version {artifact_id, version, saved, required_logical_sources, suggested_bindings} self.service.artifact_store, self.service._record_event tools.py:229, test_drafts.py:61
patch_draft 570 drafts draft, patch patched draft dict (none — pure wf_artifacts) tools.py:269
list_draft_workspaces 583 draft workspaces (none) {workspaces} self.service.draft_workspace_store tools.py:280, test_drafts.py
create_draft_workspace 593 draft workspaces workspace_id, draft, title workspace summary self.service.draft_workspace_store tools.py:293, test_drafts.py
get_draft_workspace 607 draft workspaces workspace_id, include_draft workspace summary self.service.draft_workspace_store tools.py:309, test_drafts.py
delete_draft_workspace 619 draft workspaces workspace_id {workspace_id, deleted, status} self.service.draft_workspace_store tools.py:325, test_drafts.py
validate_draft_workspace 627 draft workspaces workspace_id workspace summary self.service.draft_workspace_store tools.py:360, test_drafts.py
patch_draft_workspace 641 draft workspaces workspace_id, revision, patch workspace summary self.service.draft_workspace_store tools.py:341, test_drafts.py
set_draft_name 655 draft workspaces workspace_id, revision, name workspace summary (delegates to patch_draft_workspace) tools.py:372, test_drafts.py
set_draft_route 668 draft workspaces workspace_id, revision, step_id, outcome, target workspace summary (delegates to patch_draft_workspace) tools.py:386, test_drafts.py
set_step_input_map 692 draft workspaces workspace_id, revision, step_id, input_map workspace summary (delegates to patch_draft_workspace) tools.py:405, test_drafts.py
set_step_output_map 712 draft workspaces workspace_id, revision, step_id, output_map workspace summary (delegates to patch_draft_workspace) tools.py:425, test_drafts.py
create_minimal_draft_workspace 732 draft workspaces workspace_id, name, capability_name, schemas, bindings workspace summary self.service.draft_workspace_store (via _draft_store), self.service._get_qualified_spec (via _outcomes_for_capability) tools.py:446, test_drafts.py
create_draft_workspace_from_capability 802 draft workspaces workspace_id, capability_name, optional schemas/bindings workspace summary + wrapper_hints + next_actions (delegates to inspect_capability + create_minimal_draft_workspace) tools.py:474, test_drafts.py
create_artifact_from_workspace 847 artifacts workspace_id, artifact_id, version, title, outcomes, kind, description, required_capabilities, source_bindings, created_from_catalog_version save result or validation error self.service.draft_workspace_store tools.py:502, test_drafts.py
create_wrapper_from_workspace 884 artifacts same as above (without kind) save result (delegates to create_artifact_from_workspace with kind="wrapper") tools.py:536, test_drafts.py
inspect_artifact 917 artifacts artifact_id, version full artifact dict self.service.artifact_store tools.py:563, test_artifacts.py:58
list_deployments 925 deployments (none) {deployments} self.service.artifact_store tools.py:577, test_deployments.py
inspect_deployment 935 deployments deployment_id full deployment dict self.service.artifact_store tools.py:585, test_deployments.py
save_deployment 942 deployments deployment dict {deployment_id, artifact_id, artifact_version, saved} self.service.artifact_store, self.service._record_event tools.py:593, test_deployments.py
delete_deployment 965 deployments deployment_id {deployment_id, deleted} self.service.artifact_store, self.service._record_event tools.py:604, test_deployments.py
validate_deployment 979 deployments deployment_id, live_check {deployment_id, artifact_id, artifact_version, status, diagnostics, next_actions} self.service.artifact_store, self.service.capability_sources, self.service.connections, self.service.adapters, self.service.load_auth tools.py:617, test_deployments.py:39
run_deployment 1010 runs deployment_id, workflow_input, trace_range run payload dict self.service.artifact_store, self.service.run_workflow_from_plan, self.service.run_store tools.py:650, test_runs.py:40
resume_run 1074 runs run_id, resume_payload, resume_outcome, trace_range run payload dict self.service.run_store, self.service.resume_workflow_from_plan, self.service.capability_sources tools.py:681, test_runs.py
inspect_run 1154 runs run_id run payload dict self.service.run_store tools.py:712, test_runs.py:78
read_run_trace 1172 runs run_id, trace_range run payload dict with trace self.service.run_store tools.py:722, test_runs.py:79

Private Methods

Method Line Notes
_wrapper_artifact_for_capability_name 272 helper for wrapper resolution
_wrapper_capability_summaries 297 projects wrappers into capability discovery
_wrapper_capability_detail 340 NodeSpec-like contract for wrappers
_call_wrapper_artifact 371 executes wrapper through workflow runner
_draft_store 578 accessor for self.service.draft_workspace_store
_outcomes_for_capability 911 delegates to self.service._get_qualified_spec
_run_store 1196 accessor for self.service.run_store
_deployment_validation 1202 shared validation logic for deployment operations

Module-Level Private Helpers

Function Line Used By Dependencies
_available_sources 1238 _deployment_validation, resume_run, _live_source_diagnostics service.capability_sources
_live_source_diagnostics 1277 validate_deployment service.connections, service.adapters, service.load_auth
_required_live_sources 1326 _live_source_diagnostics (pure)
_required_capabilities_for_plan 1341 compile_draft service (for _observed_node_specs)
_required_capability_payloads 1373 multiple (pure)
_suggested_self_bindings 1382 create_artifact_from_draft (pure)
_observed_node_specs 1389 create_artifact_from_plan, create_artifact_from_draft, _required_capabilities_for_plan service.capability_sources
_schema_field_names 1400 list_capabilities (pure)
_draft_input_maps 1408 create_minimal_draft_workspace (pure)
_draft_output_map 1436 create_minimal_draft_workspace (pure)
_draft_input_bindings_payload 1449 create_minimal_draft_workspace, set_step_input_map (pure)
_draft_output_bindings_payload 1463 create_minimal_draft_workspace, set_step_output_map (pure)
_graph_path_payload 1471 multiple (pure)
_local_path_payload 1476 multiple (pure)
_state_path_payload 1480 multiple (pure)
_escape_json_pointer 1484 draft patch helpers (pure)
_draft_name_from_capability 1489 create_draft_workspace_from_capability (pure)
_source_id_for_capability 1494 call_capability (pure)
_capability_name 1505 _available_sources (pure)
_artifact_capability_id 1516 multiple (pure)
_raw_plan_from_artifact 1526 run_deployment, resume_run, _call_wrapper_artifact (pure)
_plan_field 1543 _raw_plan_from_artifact (pure)
_plan_nodes 1553 _required_capabilities_for_plan (pure)
_run_payload 1558 run_deployment, resume_run, inspect_run, read_run_trace (pure)
_interrupt_payload 1608 _run_payload (pure)
_deployment_summary 1621 list_deployments (pure)

WfMcpService Dependency Inventory

Members Accessed from WorkflowSurfaceHandlers

Member Type Accessed Via Usage Count Category
artifact_store WorkflowArtifactStore | None self.service.artifact_store ~25 artifact storage
draft_workspace_store DraftWorkspaceStore | None self.service.draft_workspace_store ~8 draft storage
run_store RunStore | None self.service.run_store ~5 run storage
capability_sources dict[str, CapabilitySource] self.service.capability_sources ~8 source/capability inventory
_get_qualified_spec(qualified_name) NodeSpec self.service._get_qualified_spec(...) 3 private helper
_record_event(event) None self.service._record_event(...) 7 private helper (event bus)
run_workflow_from_plan(...) RunState self.service.run_workflow_from_plan(...) 3 workflow execution
resume_workflow_from_plan(...) RunState self.service.resume_workflow_from_plan(...) 1 workflow execution
workflow_artifact_catalog_entry(artifact) WorkflowArtifactCatalogEntry self.service.workflow_artifact_catalog_entry(...) 1 artifact projection
connections ConnectionRegistry self.service.connections 1 MCP-specific (live source check)
adapters dict[str, BackendAdapter] self.service.adapters 1 MCP-specific (live source check)
load_auth(connection_id) AuthRecord | None self.service.load_auth(...) 1 MCP-specific (live source check)

Source/catalog ownership is now split: WfMcpService coordinates broker runtime state, while SourceCatalogService owns capability source maps, planner catalog projection, snapshot hydration, and local docs lookup.

Workflow runtime ownership is now split: WorkflowRuntimeService owns plan compilation, dependency preparation, run, and resume. WfMcpService remains the broker coordinator and compatibility façade.

Event recording ownership is now split: BrokerEventRecorder owns EventBus publication, simple event construction, event history reads, and catalog-change fanout. WfMcpService remains the coordinator and compatibility façade.

Connection registration/config reload reconciliation is now owned by wf_mcp.broker.service.connection_service.ConnectionService. The service owns the ConnectionRegistry; WfMcpService.connections is only a compatibility property.

WfMcpService Members NOT Used by WorkflowSurfaceHandlers

These members of WfMcpService (src/wf_mcp/broker/service/core.py) are NOT accessed by WorkflowSurfaceHandlers:

  • store (MCP catalog/auth storage)
  • default_catalog_max_age_seconds
  • include_builtin_specs
  • tool_executor
  • event_bus (only accessed indirectly via _record_event)
  • register_connection
  • sync_connections_from_config
  • register_adapter
  • _tool_executor_for
  • save_auth
  • register_specs
  • get_catalog
  • get_planner_catalog
  • list_sources
  • list_source_summaries
  • inspect_source
  • list_available_specs
  • get_connection_snapshot
  • connection_statuses
  • list_resources
  • list_prompts
  • get_resource
  • get_prompt
  • read_resource
  • invoke_method
  • send_notification
  • render_prompt
  • _local_documentation_resource
  • _local_documentation_prompt
  • refresh_connection_catalog
  • compile_plan
  • _prepare_workflow_runtime
  • register_capability_source
  • _hydrate_connection_source_from_snapshot
  • _spec_from_snapshot_entry
  • _record_catalog_change_events

Protocol-Neutral vs MCP-Specific Dependencies

Dependency Package Today MCP-Specific? Should Live In
WorkflowArtifactStore wf_artifacts No wf_artifacts (already there)
DraftWorkspaceStore wf_artifacts No wf_artifacts (already there)
RunStore wf_artifacts No wf_artifacts (already there)
CapabilitySource wf_platform No wf_platform (already there)
EventBus / McpEvent wf_mcp.events Renamed but protocol-neutral wf_api.events or rename to wf_platform.events
make_event wf_mcp.events Protocol-neutral Move to wf_api or wf_platform
matches_query wf_mcp.shared.listing Protocol-neutral wf_api.shared or wf_platform
paged_list_payload wf_mcp.shared.listing Protocol-neutral wf_api.shared or wf_platform
page_items wf_platform No wf_platform (already there)
RawWorkflowPlan wf_mcp.models No wf_api.models or wf_core
ConnectionRegistry wf_mcp.connections Yes (MCP connection model) stays in wf_mcp
ConnectionConfig wf_mcp.models Yes (MCP connection model) stays in wf_mcp
BackendAdapter wf_mcp.sdk Yes (MCP adapter protocol) stays in wf_mcp
require_adapter wf_mcp.broker.service.adapters Yes (MCP adapter lookup) stays in wf_mcp
AuthRecord wf_mcp.models Yes (MCP auth model) stays in wf_mcp
Store / FileStore wf_mcp.storage Yes (MCP catalog/auth storage) stays in wf_mcp
WorkflowArtifact, WorkflowDeployment, etc. wf_artifacts No wf_artifacts (already there)
CapabilityRef, CapabilitySource wf_platform No wf_platform (already there)
NodeSpec, build_async_registry wf_authoring No wf_authoring (already there)
RuntimeContext wf_core No wf_core (already there)
InputBinding, OutputBinding wf_core.models.steps No wf_core (already there)
GraphSourcePath, LocalPath, StatePath wf_core.paths No wf_core (already there)
TraceRange wf_mcp.workflow_surface.models No wf_api.models
NextActions wf_mcp.workflow_surface.next_actions No wf_api.next_actions
SavedSubgraphTree wf_mcp.workflow_surface.saved_subgraphs No wf_api.saved_subgraphs
resolve_runtime_dependencies wf_mcp.workflow_surface.runtime_dependencies No wf_api.runtime_dependencies
wrapper_hints_for_capability wf_mcp.workflow_surface.wrapper_hints No wf_api.wrapper_hints
parse_workflow_surface_capability_id wf_mcp.workflow_surface.refs No wf_api.refs
run_lifecycle helpers wf_mcp.workflow_surface.run_lifecycle No wf_api.run_lifecycle
constants wf_mcp.workflow_surface.constants No wf_api.constants
create_pinned_environment wf_mcp.workflow_surface.run_lifecycle No wf_api.run_lifecycle

Summary Classification

Protocol-neutral (should move to wf_api):

  • All wf_artifacts types and functions
  • All wf_platform types
  • All wf_authoring types
  • All wf_core types
  • EventBus / McpEvent / make_event (rename from "Mcp" to generic)
  • matches_query, paged_list_payload (listing utilities)
  • RawWorkflowPlan
  • TraceRange, NextActions, SavedSubgraphTree, resolve_runtime_dependencies, wrapper_hints, refs, run_lifecycle, constants

MCP-specific (stays in wf_mcp):

  • ConnectionRegistry, ConnectionConfig
  • BackendAdapter, require_adapter
  • AuthRecord
  • Store, FileStore (MCP catalog/auth persistence)
  • BrokerConfig, build_service_from_config
  • All proxy/MCP transport/session code
  • FastMCP tool registration (register_workflow_tools)

Extraction Options

Option A: Move Class First

Move WorkflowSurfaceHandlers to wf_api.service.WorkflowApi, keep same constructor accepting WfMcpService.

Pros:

  • Minimal code changes — just move the file, update imports
  • Tests keep passing with import-only changes
  • No new abstractions

Cons:

  • wf_api would still depend on WfMcpService (a wf_mcp type)
  • Circular dependency risk: wf_apiwf_mcpwf_api (if wf_mcp tools import from wf_api)
  • Doesn't actually decouple from MCP — just relocates the code
  • Makes future FastAPI adapter harder because the service dependency is still monolithic

Create WorkflowApi that depends on a smaller WorkflowApiBackend/ports object instead of all WfMcpService, then adapt WfMcpService into that backend.

Pros:

  • Clean dependency direction: wf_api → ports/interfaces, wf_mcp → adapts WfMcpService into ports
  • wf_cli can also adapt its own backend (or reuse the wf_mcp adapter)
  • FastAPI later becomes just another adapter over the same ports
  • Testable in isolation — mock the ports
  • Incremental: can introduce ports one domain at a time

Cons:

  • More upfront work — need to define the port interface
  • Risk of over-abstracting if ports are too fine-grained
  • Need to decide what WorkflowApiBackend actually exposes

Option C: Split Handlers by Domain First

Split capabilities/drafts/artifacts/deployments/runs into separate classes before moving packages.

Pros:

  • Each domain class is smaller and easier to reason about
  • Could enable partial extraction (move artifacts first, then drafts, etc.)
  • Better separation of concerns regardless of extraction

Cons:

  • Large refactor with many test changes
  • Still coupled to WfMcpService until ports are introduced
  • May create artificial boundaries — some operations span domains (e.g., create_artifact_from_workspace touches both drafts and artifacts)
  • Doesn't solve the packaging problem by itself

Option B: Introduce API facade with ports, starting with a minimal port surface.

Why

  1. The dependency direction is the core problem. WorkflowSurfaceHandlers currently reaches into WfMcpService for ~6 distinct capabilities. Extracting without ports just moves the coupling.

  2. The port surface is small. Looking at the actual accesses, the port interface needs:

    • artifact_store: WorkflowArtifactStore | None
    • draft_workspace_store: DraftWorkspaceStore | None
    • run_store: RunStore | None
    • capability_sources: dict[str, CapabilitySource]
    • get_qualified_spec(qualified_name: str) -> NodeSpec
    • record_event(event: McpEvent) -> None
    • run_workflow_from_plan(plan, input, ...) -> RunState
    • resume_workflow_from_plan(plan, run, ...) -> RunState
    • workflow_artifact_catalog_entry(artifact) -> WorkflowArtifactCatalogEntry
  3. The live-source-check (_live_source_diagnostics) is the only MCP-specific operation. It can stay in wf_mcp as an extension or be gated behind a protocol-neutral liveness_check callback.

First Slice Steps

  1. Define WorkflowApiBackend protocol in wf_api/backend.py with the ~9 members above.
  2. Create WorkflowApi in wf_api/service.py that takes WorkflowApiBackend instead of WfMcpService.
  3. Move WorkflowSurfaceHandlers logic into WorkflowApi (rename or keep as alias).
  4. Create WfMcpServiceBackendAdapter in wf_mcp that adapts WfMcpServiceWorkflowApiBackend.
  5. Update wf_mcp.workflow_surface.tools to create WorkflowApi(WfMcpServiceBackendAdapter(service)).
  6. Update wf_cli.context to create WorkflowApi(WfMcpServiceBackendAdapter(service)).
  7. Move protocol-neutral helpers (matches_query, paged_list_payload, make_event, etc.) to wf_api or wf_platform.
  8. Keep register_workflow_tools in wf_mcp (it's the MCP tool registration layer).

Proposed Package Shape

First Slice (extraction)

src/wf_api/
    __init__.py              # public API: WorkflowApi, WorkflowApiBackend
    backend.py               # WorkflowApiBackend protocol
    service.py               # WorkflowApi (renamed WorkflowSurfaceHandlers)
    models.py                # TraceRange, NextActions, RunPayload, etc.
    refs.py                  # parse_workflow_surface_capability_id
    constants.py             # DEFAULT_CALL_STEP_ID, outcomes, etc.
    next_actions.py          # NextActions model
    saved_subgraphs.py       # SavedSubgraphTree, resolve/validate
    run_lifecycle.py         # create_pinned_environment, persist_stopped_run, etc.
    runtime_dependencies.py  # resolve_runtime_dependencies
    wrapper_hints.py         # wrapper_hints_for_capability

Later Slices

src/wf_api/
    shared/
        listing.py           # matches_query, paged_list_payload (or move to wf_platform)
    events.py                # EventBus, make_event (rename from McpEvent)

What Stays in wf_mcp

src/wf_mcp/
    workflow_surface/
        tools.py             # register_workflow_tools (MCP tool registration)
        __init__.py          # re-exports for backward compat
    broker/
        service/
            core.py          # WfMcpService (unchanged)
            adapters.py      # WfMcpServiceBackendAdapter (NEW)
    connections.py            # ConnectionRegistry
    models.py                # ConnectionConfig, AuthRecord, BrokerConfig, CatalogSnapshot
    storage/                 # Store, FileStore
    sdk/                     # BackendAdapter
    events/                  # EventBus, McpEvent (wf_mcp-specific event bus)
    shared/                  # names, errors, pagination (MCP-specific utilities)
    proxy/                   # MCP proxy mounts
    server/                  # MCP server construction
    runtime/                 # ToolExecutor
    admin_surface/           # admin tools

What Stays in wf_cli

src/wf_cli/
    context.py               # CliContext now uses WorkflowApi instead of WorkflowSurfaceHandlers
    commands/                 # unchanged — they call context.handlers.X()

Test Coverage

Tests That Protect the Extraction

Test File Lines Coverage Area
tests/wf_mcp/workflow_surface/test_artifacts.py 71 list_artifacts, inspect_artifact, paging, filtering
tests/wf_mcp/workflow_surface/test_capabilities.py 206 list_capabilities, inspect_capability, call_capability, wrapper capabilities
tests/wf_mcp/workflow_surface/test_drafts.py 723 validate_draft, compile_draft, create_artifact_from_draft, draft workspaces (CRUD, patch, name, route, input/output maps), create_minimal_draft_workspace, create_draft_workspace_from_capability, create_artifact_from_workspace, create_wrapper_from_workspace
tests/wf_mcp/workflow_surface/test_deployments.py 282 validate_deployment, live_check, save_deployment, delete_deployment
tests/wf_mcp/workflow_surface/test_runs.py 423 run_deployment, resume_run, inspect_run, read_run_trace, interrupts, reducers, saved subgraphs
tests/wf_mcp/workflow_surface/test_wrappers.py 172 create_artifact_from_plan (wrapper), call_capability (wrapper), logical refs
tests/wf_mcp/workflow_surface/test_next_actions.py 148 NextActions model behavior
tests/wf_mcp/workflow_surface/conftest.py 380 Shared fixtures: handlers(), artifact(), echo_artifact(), echo_draft(), etc.
tests/wf_cli/test_context.py 33 load_cli_context builds service and handlers
tests/wf_cli/test_app.py 88 CLI help output (smoke tests)
tests/wf_cli/test_run_deploy.py 218 CLI deploy validate, run start, run inspect, run trace
tests/wf_cli/test_discovery_lifecycle.py 370 CLI cap list, artifact list, draft create-from-capability, deploy save, render formats
tests/wf_cli/test_explain.py 174 explain command (diagnostic code lookup)
tests/wf_mcp/test_workflow_surface_refs.py parse_workflow_surface_capability_id
tests/wf_mcp/test_workflow_wrapper_hints.py wrapper_hints_for_capability
tests/wf_mcp/test_saved_subgraphs.py SavedSubgraphTree resolution and validation

Missing Tests Needed Before Extraction

  • Integration test for WorkflowApi with a mock backend: Verify that WorkflowApi works correctly when given a WorkflowApiBackend that is NOT WfMcpService. This catches accidental coupling.
  • Test that wf_cli works with the new WorkflowApi path: The existing test_run_deploy.py uses patch("wf_cli.commands.runs.load_cli_context", ...) — need to verify the patching surface doesn't change unexpectedly.
  • Negative test for live-source-check separation: Verify that validate_deployment(live_check=True) still works after the MCP-specific liveness probe is decoupled.

Things To Keep In wf_mcp For Now

  • MCP transport/proxy/runtime/session code (src/wf_mcp/proxy/, src/wf_mcp/server/, src/wf_mcp/runtime/)
  • FastMCP registration tools (src/wf_mcp/workflow_surface/tools.pyregister_workflow_tools)
  • Connection adapters (src/wf_mcp/sdk/, src/wf_mcp/broker/service/adapters.py)
  • Connection registry (src/wf_mcp/connections.py)
  • Broker server construction (src/wf_mcp/broker/server.py, src/wf_mcp/broker/config.py)
  • MCP-specific auth (src/wf_mcp/models.py:AuthRecord)
  • MCP catalog storage (src/wf_mcp/storage/)
  • Admin surface (src/wf_mcp/admin_surface/)
  • MCP event bus (src/wf_mcp/events/) — may need a protocol-neutral fork
  • Discovery/refresh (src/wf_mcp/broker/discovery.py)

Risks And Open Questions

Circular Import Risks

  • Current: wf_mcp.workflow_surface.handlers imports from wf_mcp.broker.service (type-checking only via TYPE_CHECKING). No circular import today.
  • After extraction: wf_api.service must NOT import from wf_mcp. The WfMcpServiceBackendAdapter lives in wf_mcp and imports from wf_api. Direction: wf_mcpwf_api, never reverse.
  • Risk: The live_source_diagnostics helper currently imports require_adapter from wf_mcp.broker.service.adapters. This must stay in wf_mcp or be passed as a callback.

Private Method Dependencies

  • self.service._get_qualified_spec(qualified_name) — used 3 times. This is a private method on WfMcpService. Must become a port method.
  • self.service._record_event(event) — used 7 times. This is a private method. Must become a port method (or the event bus itself becomes a port).

Store Ownership

  • wf_artifacts owns workflow store protocols and file-backed implementations.
  • wf_api.stores.WorkflowStores groups the artifact, draft workspace, and run stores as protocol-neutral API dependencies.
  • MCP config construction creates file-backed workflow stores from BrokerConfig.store_root and injects them into WfMcpService.
  • WfMcpService.__post_init__ no longer creates workflow stores from its MCP Store; direct service tests must inject stores when they exercise workflow persistence.
  • Future HTTP/API entrypoints should construct or receive the same WorkflowStores bundle instead of importing wf_mcp.

Naming Confusion

  • McpEvent / EventBus — the name says "Mcp" but the event system is protocol-neutral. Rename to DomainEvent / EventBus or move to wf_platform.
  • WorkflowSurfaceHandlers — the name suggests MCP surface. Rename to WorkflowApi.
  • wf_mcp.workflow_surface — the package name implies MCP. The extracted package becomes wf_api.

Tests That Monkeypatch wf_cli.commands.*.load_cli_context

  • tests/wf_cli/test_run_deploy.py patches wf_cli.commands.deployments.load_cli_context, wf_cli.commands.runs.load_cli_context
  • tests/wf_cli/test_discovery_lifecycle.py patches wf_cli.commands.caps.load_cli_context, tests/wf_cli/test_discovery_lifecycle.py patches multiple command modules
  • These patches inject _load_cli_context_with_specs which calls load_cli_context then register_specs. After extraction, load_cli_context will construct WorkflowApi instead of WorkflowSurfaceHandlers. The patches should still work as long as CliContext.handlers remains the same attribute name.

FastAPI Implications

  • The WorkflowApiBackend port pattern makes FastAPI trivial: create a FastApiBackendAdapter that provides the same stores/sources/execution without MCP.
  • The MCP-specific live-source-check can be an opt-in dependency injected into the API.
  • RawWorkflowPlan should move to wf_api.models or wf_core so FastAPI can use it without importing wf_mcp.

Other Weirdness

  • WorkflowSurfaceHandlers._wrapper_artifact_for_capability_name uses parse_workflow_surface_capability_id from wf_mcp.workflow_surface.refs. This parser understands the workflow.<id>.v<version> naming convention. It's protocol-neutral and should move to wf_api.refs.
  • _available_sources (line 1238) is a module-level function that takes WfMcpService directly. It should take the port interface instead, or become a method on WorkflowApi.
  • _live_source_diagnostics (line 1277) is the only function that needs MCP adapters/connections. It should be injected as an optional callback or stay as a wf_mcp extension.

Suggested Next Plan

  1. Define the port interface (WorkflowApiBackend) in a new src/wf_api/backend.py. Start with the 9 members identified above.
  2. Create WorkflowApi in src/wf_api/service.py by copying WorkflowSurfaceHandlers and changing self.service: WfMcpService to self.backend: WorkflowApiBackend.
  3. Create adapter in src/wf_mcp/broker/service/adapters.py (or a new file) that wraps WfMcpService into WorkflowApiBackend.
  4. Update consumers (wf_mcp.workflow_surface.tools, wf_cli.context) to use WorkflowApi(WfMcpServiceBackendAdapter(service)).
  5. Move protocol-neutral modules (next_actions, saved_subgraphs, run_lifecycle, runtime_dependencies, wrapper_hints, refs, constants, models) to wf_api.
  6. Move listing utilities (matches_query, paged_list_payload) to wf_platform or wf_api.shared.
  7. Rename event types from McpEvent to DomainEvent (or create a protocol-neutral fork).
  8. Run full test suite — all existing tests must pass with import-only changes.
  9. Update CliContext to use WorkflowApi instead of WorkflowSurfaceHandlers.
  10. Add integration test for WorkflowApi with a mock backend (not WfMcpService).
  11. Define store ownership before extracting service construction: remove implicit workflow-store creation from WfMcpService.__post_init__, add config/API hooks for injected stores, and update tests to pass stores explicitly.

Dependency Graph After Extraction

wf_api (new)
  ├── depends on: wf_artifacts, wf_platform, wf_authoring, wf_core
  └── defines: WorkflowApiBackend (protocol), WorkflowApi (implementation)

wf_mcp
  ├── depends on: wf_api, wf_artifacts, wf_platform, wf_authoring, wf_core
  ├── adapts: WfMcpService → WorkflowApiBackend
  ├── registers: FastMCP tools via register_workflow_tools (uses WorkflowApi)
  └── owns: connections, adapters, proxy, server, admin, MCP events, MCP storage

wf_cli
  ├── depends on: wf_api, wf_mcp (for service construction)
  ├── uses: WorkflowApi via CliContext
  └── owns: CLI commands, formatting, I/O