Files
lda-wf/docs/historical/superpowers/plans/2026-05-21-mcp-frontend-structural-paths-and-reducers.md
T

11 KiB

MCP Frontend Structural Paths and Reducers Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Expose structural graph paths, canonical builder bindings, and structural reducer refs through the MCP/workflow frontend so LLM clients stop learning dotted-string separator conventions as canonical.

Architecture: Treat MCP as a frontend over the platform model, not the source of truth. The MCP tools should accept compatibility strings where existing users need them, but list/inspect/create responses should prefer canonical structs: input / output binding lists, path {root, parts} objects, and reducer refs with structural ref objects for configured reducers. Existing raw-plan escape hatches remain, but the recommended workflow-authoring path should produce canonical model-shaped JSON.

Tech Stack: Python 3.14, Pydantic v2, wf_mcp.workflow_surface, wf_artifacts.drafts, wf_artifacts.factory, wf_core models, pytest, basedpyright, ruff.


Dependency

Run this plan after:

2026-05-21-reducer-ref-structural-capability.md

because MCP should expose the final ReducerRef model shape, not invent a parallel frontend shape.


Current Problems

MCP/workflow frontend still has several old shapes:

  • workflow plans and draft APIs often show in_map, input_values, out_map
  • state schema examples still use legacy fields
  • reducer refs are often shown as dotted strings only
  • inspect/list tool outputs may not clearly distinguish canonical structs from display strings

The result: an LLM client can build runnable workflows, but it learns the wrong authoring shape and then has to guess separator semantics.


Target Frontend Shape

Recommended node use shape:

{
  "id": "echo",
  "type": "node",
  "node": "demo.echo",
  "input": [
    {
      "target": { "root": "local", "parts": ["text"] },
      "path": { "root": "input", "parts": ["text"] }
    },
    {
      "target": { "root": "local", "parts": ["limit"] },
      "value": 3
    }
  ],
  "output": [
    {
      "source": { "root": "local", "parts": ["echoed"] },
      "target": { "root": "state", "parts": ["echoed"] }
    }
  ]
}

Recommended configured reducer shape:

{
  "ref": { "source": "wf.std", "capability_key": "modulo_add" },
  "config": { "modulus": 10 }
}

Compact unconfigured reducer shorthand remains accepted:

"wf.std.add"

File Structure

  • Inspect/modify: src/wf_mcp/workflow_surface/models.py

    • Request/response models for create/compile/validate/call workflow tools
  • Inspect/modify: src/wf_mcp/workflow_surface/handlers.py

    • create draft/workflow helpers
    • source/capability inspection payloads
  • Inspect/modify: src/wf_mcp/workflow_surface/tools.py

    • MCP tool schemas/descriptions
  • Inspect/modify: src/wf_artifacts/drafts/models.py

    • draft step/input/output shape if drafts still generate raw maps
  • Inspect/modify: src/wf_artifacts/drafts/adapter.py

    • draft-to-builder compile path; should use canonical input / output
  • Inspect/modify docs:

    • docs/wf_mcp_operator_manual.md
    • docs/workflow_drafts.md
    • docs/wf_mcp_end_to_end_runbook.md
    • docs/structural_refs.md
  • Tests:

    • tests/wf_mcp/test_workflow_surface.py
    • tests/wf_mcp/test_workflow_wrapper_hints.py
    • tests/artifacts/test_draft_adapter.py
    • tests/artifacts/test_draft_models.py
    • tests/artifacts/test_draft_api.py

Task 1: Inventory MCP/Draft Surfaces That Emit Map Sugar

Files:

  • Read-only first:

    • src/wf_mcp/workflow_surface/models.py
    • src/wf_mcp/workflow_surface/handlers.py
    • src/wf_artifacts/drafts/models.py
    • src/wf_artifacts/drafts/adapter.py
  • Step 1: Search old map fields

Run:

rg -n '"in_map"|in_map|input_values|"out_map"|out_map|fields' src/wf_mcp src/wf_artifacts tests/wf_mcp tests/artifacts docs -g '*.py' -g '*.md'
  • Step 2: Categorize each hit

Use these categories:

  • compatibility input still accepted

  • canonical output should be changed

  • test fixture using old shape intentionally

  • docs/example should migrate

  • Step 3: Write findings into this plan or a short docs note

Add a small checklist under this task before implementation. Do not blindly replace all strings.

Findings from the first inventory pass:

  • src/wf_artifacts/drafts/adapter.py is the highest-value runtime hit: it still calls WorkflowBuilder.use_ref(..., in_map=..., input_values=..., out_map=...) and WorkflowBuilder.use(..., out_map=...), causing deprecation warnings from MCP draft/workspace tests. This should be changed to canonical binding lists.
  • Raw workflow-plan tests in tests/wf_mcp/test_service.py, tests/wf_mcp/test_broker_server.py, tests/wf_mcp/test_workflow_surface.py, and tests/artifacts/test_factory.py intentionally exercise raw-plan compatibility. Do not bulk-rewrite those while raw-plan escape hatches remain.
  • Draft model tests still use state_schema.fields as compatibility input. That can stay as parse input, but new docs/examples should prefer JSON Schema properties.
  • Docs already explain parse-only compatibility in several places, but older operator/runbook examples still need canonical input / output examples.

Task 2: Draft Adapter Emits Canonical Builder Bindings

Files:

  • Modify: src/wf_artifacts/drafts/adapter.py

  • Modify: tests/artifacts/test_draft_adapter.py

  • Step 1: Add/adjust test

Add a test proving a draft compiles through WorkflowBuilder.use_ref(..., input=[...], output=[...]) or directly produces canonical NodeUse.input / output.

Expected assertion:

node = workflow.nodes[0]
dumped = node.model_dump(mode="json")
assert "in_map" not in dumped
assert "out_map" not in dumped
assert dumped["input"][0]["target"] == {"root": "local", "parts": ["text"]}
assert dumped["input"][0]["path"] == {"root": "input", "parts": ["text"]}
  • Step 2: Update adapter

Where it currently calls:

builder.use_ref(..., in_map=step.in_, input_values=..., out_map=step.out)

convert draft structures into:

input=[...]
output=[...]

If draft models still store maps, transform them into canonical binding dicts at the adapter boundary.

  • Step 3: Run draft adapter tests
uv run --with pytest pytest tests/artifacts/test_draft_adapter.py -q

Expected: pass and no new deprecation warnings from the adapter.


Task 3: Workflow Surface Requests Prefer Canonical Shapes

Files:

  • Modify: src/wf_mcp/workflow_surface/models.py

  • Modify: src/wf_mcp/workflow_surface/handlers.py

  • Modify: tests/wf_mcp/test_workflow_surface.py

  • Step 1: Add schema tests for canonical binding request fields

Find the create/compile draft request model and assert its JSON Schema includes:

"input": {"type": "array", ...}
"output": {"type": "array", ...}

and does not force in_map / out_map as the primary example.

  • Step 2: Update Pydantic models

Prefer these field names in MCP-facing request models:

input: list[InputBindingLike] = Field(default_factory=list, description=...)
output: list[OutputBindingLike] = Field(default_factory=list, description=...)

If compatibility maps remain:

in_map: dict[str, str] | None = Field(default=None, deprecated=True, description=...)
out_map: dict[str, str] | None = Field(default=None, deprecated=True, description=...)

If Pydantic deprecated=True causes schema issues, document deprecation in descriptions instead.

  • Step 3: Update handlers

Handlers should pass canonical lists to builder/artifact APIs.

  • Step 4: Run workflow surface tests
uv run --with pytest pytest tests/wf_mcp/test_workflow_surface.py -q

Expected: pass.


Task 4: Inspect/List Outputs Show Canonical Refs and Display Strings Separately

Files:

  • Modify: src/wf_mcp/workflow_surface/handlers.py

  • Modify: src/wf_platform/sources.py if inventory models need fields

  • Modify: tests in tests/wf_mcp

  • Step 1: Add response-shape assertions

For source/capability inspection responses, assert reducers include enough info:

{
  "name": "wf.std.add",
  "ref": { "source": "wf.std", "capability_key": "add" },
  "description": "..."
}

Use name as display, ref as canonical.

  • Step 2: Update inventory models only if needed

If ReducerInventory currently has only name, add:

ref: CapabilityRef

or a serializable equivalent.

Keep old name for display.

  • Step 3: Run platform/MCP inventory tests
uv run --with pytest pytest tests/platform/test_inventory.py tests/wf_mcp/test_service.py tests/wf_mcp/test_workflow_surface.py -q

Expected: pass.


Task 5: Docs and MCP Tool Descriptions

Files:

  • Modify: docs/wf_mcp_operator_manual.md

  • Modify: docs/workflow_drafts.md

  • Modify: docs/wf_mcp_end_to_end_runbook.md

  • Modify: docs/structural_refs.md

  • Modify MCP tool descriptions in src/wf_mcp/workflow_surface/tools.py if needed

  • Step 1: Replace primary examples

Replace examples that teach:

"in_map": {"input.text": "text"}

with:

"input": [{"target": {"root": "local", "parts": ["text"]}, "path": {"root": "input", "parts": ["text"]}}]
  • Step 2: Keep compatibility notes

Add:

`in_map`, `input_values`, and `out_map` are compatibility inputs. New MCP/JSON
clients should use `input` and `output` binding lists.
  • Step 3: Update reducer examples

Show:

"reducer": "wf.std.add"

for compact unconfigured reducers, and:

"reducer": {
  "ref": {"source": "wf.std", "capability_key": "modulo_add"},
  "config": {"modulus": 10}
}

for configured reducers.


Task 6: Verification

  • Step 1: Focused artifact/MCP tests
uv run --with pytest pytest tests/artifacts/test_draft_adapter.py tests/artifacts/test_draft_models.py tests/artifacts/test_draft_api.py tests/wf_mcp/test_workflow_surface.py tests/wf_mcp/test_workflow_wrapper_hints.py -q
  • Step 2: Full tests
uv run --with pytest pytest -q
  • Step 3: Static checks
uvx ruff check src/wf_mcp src/wf_artifacts tests/wf_mcp tests/artifacts
uvx ruff format --check src/wf_mcp src/wf_artifacts tests/wf_mcp tests/artifacts
uv run basedpyright --level error src/wf_mcp src/wf_artifacts tests/wf_mcp tests/artifacts

Expected:

  • tests pass
  • ruff passes
  • basedpyright reports 0 errors

Self-Review Checklist

  • MCP-facing examples prefer canonical binding lists.
  • Compatibility maps remain accepted where documented.
  • Draft adapter no longer emits deprecated builder sugar warnings.
  • Reducer refs display name and canonical ref distinctly where inventory exposes them.
  • No MCP handler reparses reducer dotted names by first/last dot.