Files
lda-wf/docs/wf_mcp_end_to_end_runbook.md
T

18 KiB

wf_mcp End-To-End Runbook

This runbook shows one complete successful path through the platform:

configured connection
  -> refreshed catalog
  -> source/capability discovery
  -> direct capability test
  -> saved workflow artifact
  -> saved deployment
  -> validated deployment
  -> deployment run

The example uses an imaginary upstream MCP connection:

demo.personal

which exposes one workflow-ready capability:

demo.personal.echo_tool

The saved artifact uses the logical source alias demo, so the same workflow can later be deployed against demo.work or another compatible account.

Minimal Lifecycle Summary

The shortest dependable lifecycle is:

list_capabilities
inspect_capability
create_draft_workspace_from_capability
patch_draft_workspace
validate_draft_workspace
create_artifact_from_workspace
save_deployment
validate_deployment
run_deployment
inspect_run or read_run_trace only when needed
resume_run only when status is interrupted
delete_deployment for temporary deployments

Do not expect a newly saved workflow to appear as a new MCP tool in an existing client session. Use run_deployment and call_capability as stable front doors.

0. Know The Three Names

This example deliberately uses three related names:

Name Meaning
demo.personal concrete configured connection/source
demo.personal.echo_tool concrete discovered workflow capability
demo.echo_tool logical capability ref stored in the saved artifact

The deployment later binds:

{
  "demo": "demo.personal"
}

That is the important separation between reusable workflow definition and concrete account choice.

1. Add Or Confirm The Connection

If the connection does not exist yet:

tool: wf.admin.add_connection
arguments:
{
  "connection_id": "demo.personal",
  "server": "demo",
  "account": "personal",
  "metadata": {
    "transport": "stdio",
    "command": "python",
    "args": ["path/to/demo_server.py"]
  }
}

If it already exists, inspect the configured set:

tool: wf.admin.list_connections
arguments: {}

Expected idea:

[
  {
    "id": "demo.personal",
    "server": "demo",
    "account": "personal",
    "enabled": true
  }
]

2. Refresh The Upstream Catalog

tool: wf.admin.refresh_connection_catalog
arguments:
{
  "connection_id": "demo.personal"
}

This is the discovery step. It asks the upstream MCP server what it currently exposes and updates the stored catalog snapshot.

After this succeeds, these views become useful:

tool: wf.admin.get_catalog
arguments: {}

for the raw upstream MCP snapshot, and:

tool: wf.admin.get_planner_catalog
arguments: {}

for the planner-facing node-spec view.

Prefer the progressive-discovery tools below for ordinary use; full catalogs can be large.

3. Discover The Source

First list compact source summaries:

tool: wf.admin.list_sources
arguments:
{
  "limit": 20
}

Then inspect only the source you care about:

tool: wf.admin.inspect_source
arguments:
{
  "source_id": "demo.personal"
}

You want to confirm:

  • the source is enabled
  • it is planner-visible
  • it owns the expected generated node spec / workflow capability

4. Discover And Inspect The Workflow Capability

Find the workflow-ready capability:

tool: wf.workflow.list_capabilities
arguments:
{
  "source_id": "demo.personal",
  "query": "echo",
  "limit": 20
}

The list response is intentionally compact. It includes source_id, outcomes, and top-level input_fields / output_fields, but not full JSON schemas.

Then inspect its full contract:

tool: wf.workflow.inspect_capability
arguments:
{
  "qualified_name": "demo.personal.echo_tool"
}

This is where you learn:

  • input schema
  • output schema
  • declared outcomes
  • whether it is async
  • whether it is actually a good workflow-facing contract

5. Test The Capability Directly

Before composing a workflow, call the node contract once:

tool: wf.workflow.call_capability
arguments:
{
  "qualified_name": "demo.personal.echo_tool",
  "payload": {
    "text": "hello"
  }
}

Expected shape:

{
  "outcome": "ok",
  "output": {
    "echoed": "hello"
  }
}

This is the authoring REPL step. It tests the workflow-facing contract, not just the raw upstream MCP tool call.

6. Save A Workflow Artifact

Create a one-node workflow that:

  • accepts input.text
  • calls the discovered echo capability
  • stores echoed into workflow state
  • ends on the node's ok outcome
tool: wf.workflow.create_artifact_from_draft
arguments:
{
  "artifact_id": "echo",
  "version": 1,
  "title": "Echo",
  "outcomes": ["completed"],
  "source_bindings": {
    "demo": "demo.personal"
  },
  "draft": {
    "name": "echo",
    "input_schema": {
      "type": "object",
      "properties": {
        "text": {
          "type": "string"
        }
      },
      "required": ["text"]
    },
    "state_schema": {
      "type": "object",
      "properties": {
        "echoed": {
          "type": "string",
          "reducer": "wf.std.replace"
        }
      }
    },
    "output_schema": {
      "type": "object",
      "properties": {
        "echoed": {
          "type": "string"
        }
      },
      "required": ["echoed"]
    },
    "start": "echo",
    "steps": {
      "echo": {
        "use": "demo.personal.echo_tool",
        "input": [
          {
            "target": "text",
            "path": "input.text"
          }
        ],
        "output": [
          {
            "source": "echoed",
            "target": "state.echoed"
          }
        ]
      }
    },
    "routes": {
      "echo": {
        "ok": "__end__"
      }
    }
  }
}

Important behavior:

  • because source_bindings says "demo": "demo.personal", the saved artifact is normalized to logical node ref demo.echo_tool
  • the saved dependency contract records what was observed from demo.personal.echo_tool at creation time
  • the draft is compiled into a raw workflow plan and validated before saving

Inspect the saved result if needed:

tool: wf.workflow.inspect_artifact
arguments:
{
  "artifact_id": "echo",
  "version": 1
}

7. Save A Deployment

Artifacts are reusable definitions. Deployments decide which concrete sources run them.

tool: wf.workflow.save_deployment
arguments:
{
  "deployment": {
    "id": "echo.personal",
    "artifact_id": "echo",
    "artifact_version": 1,
    "bindings": {
      "demo": "demo.personal",
      "wf.std": "wf.std"
    }
  }
}

The wf.std self-binding is present when the saved graph depends on standard library capabilities. This tiny echo graph does not need much from wf.std, but keeping local system-source bindings explicit is the current general pattern.

System-source bindings can look redundant:

{
  "wf.std": "wf.std",
  "wf.mcp": "wf.mcp"
}

They mean "bind the artifact's logical local source to the concrete local source with the same id." They are not external account bindings. Keep them explicit for now when validation reports binding_missing for wf.std or wf.mcp. Later the platform may make system-source self-bindings implicit, but current artifacts and deployments use one uniform binding mechanism for both local and external sources.

8. Validate Before Running

tool: wf.workflow.validate_deployment
arguments:
{
  "deployment_id": "echo.personal"
}

You want a runnable result with no blocking diagnostics.

Validation catches problems such as:

  • a bound source is disabled or missing
  • a required capability disappeared
  • the saved dependency schema drifted from the live capability

Optional Live Source Check

Use this before a real run when you need to know whether the bound upstream source can currently answer.

tool: wf.workflow.validate_deployment
arguments:
  deployment_id: "echo.personal"
  live_check: true

Expected successful shape:

{
  "deployment_id": "echo.personal",
  "status": "runnable",
  "diagnostics": []
}

If a bound upstream source is down, expect status="unrunnable" and a diagnostic with code="source_unreachable".

9. Run The Deployment

tool: wf.workflow.run_deployment
arguments:
{
  "deployment_id": "echo.personal",
  "workflow_input": {
    "text": "hello"
  }
}

Expected shape:

{
  "status": "completed",
  "output": {
    "echoed": "hello"
  },
  "diagnostics": [],
  "trace_count": 1
}

Do not request trace detail by default. run_deployment returns trace_count as the total original trace length and supports explicit ranged debug reads such as "trace_range": {"start": 0, "limit": 10}. Trace entries may contain resolved inputs, outputs, and state changes.

Capture the returned run_id. Use wf.workflow.inspect_run to read the stored summary later, wf.workflow.read_run_trace for small debug slices, and wf.workflow.resume_run only when the run status is interrupted. The full operational contract lives in durable_run_operations.md.

10. Rebind The Same Artifact Later

If another compatible account appears:

demo.work

you do not need to rewrite artifact version 1.

Create another deployment:

{
  "id": "echo.work",
  "artifact_id": "echo",
  "artifact_version": 1,
  "bindings": {
    "demo": "demo.work",
    "wf.std": "wf.std"
  }
}

Then validate it. If demo.work.echo_tool is compatible with the saved contract, the same workflow artifact can run there.

Full Minimal Sequence

For a client that already has an enabled connection, the normal minimal flow is:

wf.admin.refresh_connection_catalog
wf.admin.list_sources
wf.workflow.list_capabilities
wf.workflow.inspect_capability
wf.workflow.call_capability
wf.workflow.validate_draft
wf.workflow.create_artifact_from_draft
wf.workflow.save_deployment
wf.workflow.validate_deployment
wf.workflow.run_deployment

wf.workflow.list_capabilities only lists graph-usable workflow capabilities. Control tools such as wf.workflow.inspect_run, wf.workflow.read_run_trace, draft workspace mutation helpers, and admin operations are discovered through MCP tools/list or the client's search-tools surface.

Common Failure Points

The connection exists but nothing is discoverable

Check:

  1. wf.admin.list_connections
  2. wf.admin.refresh_connection_catalog
  3. wf.admin.inspect_source

A configured connection can exist with zero loaded capabilities until refresh succeeds.

The upstream server has tools but no prompts/resources

That is valid. resources/list and prompts/list are optional MCP families. A tools-only server should still refresh successfully.

The raw proxy tool is callable but not pleasant in a graph

That is the raw-tool versus workflow-capability distinction. Use or build a workflow-facing wrapper when the raw tool's shape is provider-centric.

The draft is close but has one wrong field

Use wf.workflow.patch_draft instead of asking the client to rewrite the whole workflow. Draft patching uses JSON Patch and revalidates the patched result.

The deployment used to run but now fails validation

Likely causes:

  • source disabled
  • capability removed
  • schema drift from the saved dependency snapshot

Inspect:

wf.admin.inspect_source
wf.workflow.inspect_artifact
wf.workflow.validate_deployment

Workspace Variant

If the client is iterating with an LLM, prefer a draft workspace:

  1. Create a minimal workspace from the selected capability.
  2. Fetch the workspace by id when context is needed.
  3. Patch it by id and revision.
  4. Save an artifact from the workspace after validation is clean.

This avoids resending the whole draft object every turn. The saved artifact is still immutable and should be deployed through the normal deployment path.

Concrete MCP sequence:

  1. wf.workflow.list_capabilities with a query such as echo.
  2. wf.workflow.call_capability with a small payload to verify the selected capability behaves as expected.
  3. wf.workflow.create_minimal_draft_workspace with a request object that contains schemas plus canonical input and output binding lists.
  4. wf.workflow.list_draft_workspaces if the client needs to rediscover existing workspace ids.
  5. wf.workflow.get_draft_workspace with include_draft=true if the client needs to inspect the full current draft.
  6. Use focused helpers such as wf.workflow.set_draft_name or wf.workflow.set_draft_route, or call wf.workflow.patch_draft_workspace with the current revision for arbitrary JSON Patch edits.
  7. wf.workflow.validate_draft_workspace if capabilities changed or you want to refresh diagnostics without editing the draft.
  8. wf.workflow.create_artifact_from_workspace after validation is clean. Use wf.workflow.create_wrapper_from_workspace instead when the workspace is a reusable wrapper around a raw capability.
  9. wf.workflow.save_deployment, then validate_deployment, then run_deployment.
  10. wf.workflow.delete_draft_workspace when the mutable authoring session is no longer needed.

create_artifact_from_workspace also uses a request object:

{
  "request": {
    "workspace_id": "echo_draft",
    "artifact_id": "echo",
    "version": 1,
    "title": "Echo",
    "outcomes": ["completed"],
    "source_bindings": {
      "demo": "demo.personal",
      "wf.std": "wf.std"
    }
  }
}

create_wrapper_from_workspace accepts the same request shape except there is no kind field. It always saves kind="wrapper" and the result is discoverable as a workflow capability named workflow.<artifact_id>.v<version>.

Wrapper Happy Path

Use this path when a raw/provider capability is callable but you want a reusable workflow-facing wrapper with explicit schemas, outcomes, and bindings.

1. Inspect The Source Capability

tool: wf.workflow.inspect_capability
arguments:
{
  "qualified_name": "demo.personal.echo_tool"
}

The response includes wrapper_hints. These hints are scaffolding, not final business logic. They suggest draft schemas and basic input/output bindings.

2. Create A Draft Workspace From The Capability

tool: wf.workflow.create_draft_workspace_from_capability
arguments:
{
  "request": {
    "workspace_id": "echo_wrapper_draft",
    "capability_name": "demo.personal.echo_tool",
    "name": "echo_wrapper",
    "title": "Echo Wrapper Draft"
  }
}

This creates a mutable, revisioned workspace using the inspected wrapper_hints.

After create_draft_workspace_from_capability, inspect next_actions. If recommended_next_tool is wf.workflow.patch_draft_workspace, apply or adapt the returned patch_examples before saving. If it recommends wf.workflow.validate_draft_workspace, validate the draft before creating an artifact.

3. Patch Or Validate The Workspace

When patching output bindings, keep the two levels separate:

  • Step-level steps.<id>.output uses source local -> target state.
  • Top-level output uses path graph -> target local output payload.

For explicit final output projection from state, use:

{
  "path": "state.result_text",
  "target": "result_text"
}

Do not use source at top level. source belongs to step output bindings.

If the hints are good enough, validate:

tool: wf.workflow.validate_draft_workspace
arguments:
{
  "request": {
    "workspace_id": "echo_wrapper_draft"
  }
}

If one field is wrong, use a focused helper or JSON Patch. For example, change one route:

tool: wf.workflow.set_draft_route
arguments:
{
  "request": {
    "workspace_id": "echo_wrapper_draft",
    "revision": 1,
    "step_id": "echo",
    "outcome": "error",
    "target": "__end__"
  }
}

4. Save The Workspace As A Wrapper Artifact

tool: wf.workflow.create_wrapper_from_workspace
arguments:
{
  "request": {
    "workspace_id": "echo_wrapper_draft",
    "artifact_id": "echo_wrapper",
    "version": 1,
    "title": "Echo Wrapper",
    "outcomes": ["ok", "error"],
    "source_bindings": {
      "demo": "demo.personal"
    }
  }
}

The saved wrapper appears in workflow capability discovery as:

workflow.echo_wrapper.v1

5. Deploy And Test The Wrapper

Saved wrappers that use logical sources need a deployment binding when called:

tool: wf.workflow.save_deployment
arguments:
{
  "deployment": {
    "id": "echo_wrapper.personal",
    "artifact_id": "echo_wrapper",
    "artifact_version": 1,
    "bindings": {
      "demo": "demo.personal",
      "wf.std": "wf.std"
    }
  }
}

Then test the wrapper through the workflow-facing REPL tool:

tool: wf.workflow.call_capability
arguments:
{
  "qualified_name": "workflow.echo_wrapper.v1",
  "deployment_id": "echo_wrapper.personal",
  "payload": {
    "text": "hello"
  }
}

Expected shape:

{
  "qualified_name": "workflow.echo_wrapper.v1",
  "kind": "wrapper_artifact",
  "outcome": "completed",
  "output": {
    "echoed": "hello"
  },
  "diagnostics": []
}

Current limitation: saved wrappers are executed through the deployment runtime, so call_capability reports the wrapper run status (completed, failed, or interrupted) rather than remapping inner node outcomes. True graph-as-node outcome propagation belongs in wf_core subgraph support.

See examples/mcp_wrapper_authoring_flow.py for this same sequence through the Python handler layer.

Cleanup Temporary Deployments

Temporary test deployments can be removed without touching immutable artifacts.

tool: wf.workflow.delete_deployment
arguments:
  deployment_id: "echo.personal"

Expected:

{
  "deployment_id": "echo.personal",
  "deleted": true
}