Files
lda-wf/docs/historical/superpowers/plans/2026-06-04-rpc-server-mcp-config-hookup.md
T

13 KiB

RPC Server MCP Config Hookup 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: Let wf-rpc-server serve a real MCP-backed WorkflowServer from an MCP broker config file, so wf --url ... can target a long-lived server with MCP sources and source-registry/admin surfaces.

Architecture: Keep JSON-RPC transport code as the place that selects a server composition for the process. --mcp-config loads legacy MCP broker config and calls wf_mcp.broker.build_workflow_server_from_config; existing --store-root / neutral --config behavior continues to build the local/static server. The transport still receives only a neutral WorkflowServer and calls create_rpc_app(server).

Tech Stack: Python 3.14, Typer, wf_transport_rpc_http, wf_mcp.broker, wf_server, pytest, ruff, basedpyright.


Current Context

Implemented before this plan:

  • wf_mcp.broker.server.build_workflow_server_from_config(config) returns a neutral WorkflowServer.
  • wf_transport_rpc_http.cli.serve() currently always calls build_local_static_workflow_server(...).
  • Existing CLI tests in tests/wf_transport_rpc_http/test_cli.py monkeypatch server construction, create_rpc_app, and uvicorn.run; use that pattern.

Out of scope:

  • No new neutral config schema for MCP source transports.
  • No server hot reload.
  • No process manager/daemon work.
  • No auth redesign.
  • No real socket startup in tests.

File Structure

  • Modify src/wf_transport_rpc_http/cli.py
    • Add --mcp-config.
    • Select MCP-backed server when --mcp-config is supplied.
    • Keep local/static path unchanged for existing config/store-root flows.
  • Modify tests/wf_transport_rpc_http/test_cli.py
    • Add help assertion for --mcp-config.
    • Add server-selection tests.
  • Modify docs/wf_cli.md
    • Document local/static server and MCP-backed server startup examples.
  • Modify docs/current_roadmap.md
    • Mark RPC server CLI MCP config hookup complete.
  • Modify docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md
    • Record Slice 5 status.

Task 1: Add MCP Config Server Selection to RPC CLI

Files:

  • Modify: src/wf_transport_rpc_http/cli.py

  • Modify: tests/wf_transport_rpc_http/test_cli.py

  • Step 1: Add failing help and selection tests

In tests/wf_transport_rpc_http/test_cli.py, update test_rpc_server_cli_help_mentions_store_root:

def test_rpc_server_cli_help_mentions_store_root() -> None:
    result = CliRunner().invoke(app, ["--help"])

    assert result.exit_code == 0
    assert "--store-root" in result.output
    assert "--mcp-config" in result.output
    assert "--host" in result.output
    assert "--port" in result.output

Add:

def test_rpc_server_cli_uses_mcp_config_server(monkeypatch, tmp_path) -> None:
    config_path = tmp_path / "wf_mcp.config.json"
    config_path.write_text(
        json.dumps(
            {
                "store_root": str(tmp_path / "store"),
                "connections": [],
            }
        ),
        encoding="utf-8",
    )
    captured: dict[str, object] = {}

    def fake_load_broker_config(path):
        captured["mcp_config_path"] = path
        return "broker-config"

    def fake_build_mcp_server(config):
        captured["mcp_config"] = config
        return object()

    def fake_create_rpc_app(server, *, rpc_path="/rpc"):
        captured["server"] = server
        captured["rpc_path"] = rpc_path
        return object()

    def fake_uvicorn_run(app_obj, *, host, port, access_log):
        captured["app"] = app_obj
        captured["host"] = host
        captured["port"] = port
        captured["access_log"] = access_log

    monkeypatch.setattr("wf_transport_rpc_http.cli.load_broker_config", fake_load_broker_config)
    monkeypatch.setattr(
        "wf_transport_rpc_http.cli.build_workflow_server_from_config",
        fake_build_mcp_server,
    )
    monkeypatch.setattr("wf_transport_rpc_http.cli.create_rpc_app", fake_create_rpc_app)
    monkeypatch.setattr("wf_transport_rpc_http.cli.uvicorn.run", fake_uvicorn_run)

    result = CliRunner().invoke(
        app,
        [
            "--mcp-config",
            str(config_path),
            "--host",
            "127.0.0.9",
            "--port",
            "9988",
        ],
    )

    assert result.exit_code == 0, result.output
    assert captured["mcp_config_path"] == config_path
    assert captured["mcp_config"] == "broker-config"
    assert captured["server"] is not None
    assert captured["rpc_path"] == "/rpc"
    assert captured["host"] == "127.0.0.9"
    assert captured["port"] == 9988
    assert captured["access_log"] is False

Add conflict test:

def test_rpc_server_cli_rejects_mcp_config_with_store_root(tmp_path) -> None:
    config_path = tmp_path / "wf_mcp.config.json"
    config_path.write_text(
        json.dumps({"store_root": str(tmp_path / "store"), "connections": []}),
        encoding="utf-8",
    )

    result = CliRunner().invoke(
        app,
        [
            "--mcp-config",
            str(config_path),
            "--store-root",
            str(tmp_path / "other"),
        ],
    )

    assert result.exit_code != 0
    assert "--mcp-config cannot be combined with --store-root" in result.output

Run:

uv run pytest tests/wf_transport_rpc_http/test_cli.py -q

Expected: FAIL because --mcp-config is not implemented.

  • Step 2: Implement --mcp-config option

In src/wf_transport_rpc_http/cli.py, import:

from wf_mcp.broker import build_workflow_server_from_config, load_broker_config

Add option to serve(...):

mcp_config: Path | None = typer.Option(
    None,
    "--mcp-config",
    help="Path to MCP broker config JSON for MCP-backed workflow server.",
),

Update docstring:

"""Serve WorkflowApi over JSON-RPC HTTP."""

Before resolving local/static store root, add validation:

if mcp_config is not None and store_root is not None:
    raise typer.BadParameter("--mcp-config cannot be combined with --store-root")

If mcp_config is supplied, build MCP server:

server = None
if mcp_config is not None:
    broker_config = load_broker_config(mcp_config)
    server = build_workflow_server_from_config(broker_config)

Then keep existing neutral --config parsing for host/port/path. When selecting the final server, only call build_local_static_workflow_server(...) if server is None:

if server is None:
    if resolved_store_root is None:
        raise typer.BadParameter(
            "--store-root is required when --config is not supplied"
        )
    server = build_local_static_workflow_server(resolved_store_root)

Important:

  • --config may still be used with --mcp-config for transport host/port/path.

  • --mcp-config owns the workflow server/store/source side.

  • --store-root is local/static-only and must conflict with --mcp-config.

  • Step 3: Run CLI tests

Run:

uv run pytest tests/wf_transport_rpc_http/test_cli.py -q

Expected: PASS.

  • Step 4: Commit
git add src/wf_transport_rpc_http/cli.py tests/wf_transport_rpc_http/test_cli.py
git commit -m "feat: serve mcp backed rpc server"

Task 2: Prove MCP Config Server Supports Registry RPC Through CLI Path

Files:

  • Modify: tests/wf_transport_rpc_http/test_cli.py

  • Step 1: Add integration-style construction test

Add this test to tests/wf_transport_rpc_http/test_cli.py:

def test_rpc_server_cli_mcp_config_builds_registry_capable_server(monkeypatch, tmp_path) -> None:
    config_path = tmp_path / "wf_mcp.config.json"
    config_path.write_text(
        json.dumps(
            {
                "store_root": str(tmp_path / "store"),
                "connections": [],
            }
        ),
        encoding="utf-8",
    )
    captured: dict[str, object] = {}

    def fake_create_rpc_app(server, *, rpc_path="/rpc"):
        captured["source_registry_admin"] = server.source_registry_admin
        captured["rpc_path"] = rpc_path
        return object()

    def fake_uvicorn_run(app_obj, *, host, port, access_log):
        captured["host"] = host
        captured["port"] = port

    monkeypatch.setattr("wf_transport_rpc_http.cli.create_rpc_app", fake_create_rpc_app)
    monkeypatch.setattr("wf_transport_rpc_http.cli.uvicorn.run", fake_uvicorn_run)

    result = CliRunner().invoke(app, ["--mcp-config", str(config_path)])

    assert result.exit_code == 0, result.output
    assert captured["source_registry_admin"] is not None
    assert captured["rpc_path"] == "/rpc"
    assert captured["host"] == "127.0.0.1"
    assert captured["port"] == 8765

This test uses the real load_broker_config() and build_workflow_server_from_config() but still avoids starting uvicorn.

  • Step 2: Run CLI tests

Run:

uv run pytest tests/wf_transport_rpc_http/test_cli.py -q

Expected: PASS.

  • Step 3: Commit
git add tests/wf_transport_rpc_http/test_cli.py
git commit -m "test: cover mcp config rpc server path"

Task 3: Update User-Facing Docs and Roadmap

Files:

  • Modify: docs/wf_cli.md

  • Modify: docs/current_roadmap.md

  • Modify: docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md

  • Step 1: Add RPC server startup docs

In docs/wf_cli.md, after the opening config paragraph, add:

## Remote Server

Start a local/static JSON-RPC workflow server:

```bash
wf-rpc-server --store-root .wf_store --host 127.0.0.1 --port 8765

Start a JSON-RPC server backed by MCP broker config and MCP-capable sources:

wf-rpc-server --mcp-config wf_mcp.config.json --host 127.0.0.1 --port 8765

Then point wf at it:

wf --url http://127.0.0.1:8765/rpc cap list
wf --url http://127.0.0.1:8765/rpc admin registry list

--mcp-config owns the server's workflow stores, MCP connections, and source registry. --store-root is for the local/static server path and cannot be combined with --mcp-config.


- [ ] **Step 2: Update roadmap**

In `docs/current_roadmap.md`, under **Durable API service shape**, add:

```markdown
   - Completed: `wf-rpc-server --mcp-config wf_mcp.config.json` starts the
      JSON-RPC transport over an MCP-backed `WorkflowServer`, making the remote
      CLI path usable with MCP sources and desired source registry operations.
  • Step 3: Update long-lived API spec

In docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md, update status from "Slices 1-4 implemented" to "Slices 1-5 implemented", and add under implementation status:

- Slice 5 complete: `wf-rpc-server --mcp-config <path>` starts JSON-RPC over an
  MCP-backed `WorkflowServer`; `--store-root` remains local/static-only.
  • Step 4: Run docs grep

Run:

rg -n "wf-rpc-server --mcp-config|Slices 1-5 implemented|--store-root.*--mcp-config" docs/wf_cli.md docs/current_roadmap.md docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md

Expected: all three docs mention the new server path.

  • Step 5: Commit
git add docs/wf_cli.md docs/current_roadmap.md docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md
git commit -m "docs: document mcp backed rpc server"

Task 4: Final Verification

Files:

  • Verify only.

  • Step 1: Run focused tests

Run:

uv run pytest tests/wf_transport_rpc_http/test_cli.py tests/wf_transport_rpc_http/test_mcp_backed_server_rpc.py tests/wf_mcp/test_mcp_workflow_server.py -q

Expected: all tests pass.

  • Step 2: Run lint and type checks

Run:

uv run ruff check src/wf_transport_rpc_http/cli.py tests/wf_transport_rpc_http/test_cli.py
uv run basedpyright --level error src/wf_transport_rpc_http/cli.py tests/wf_transport_rpc_http/test_cli.py
git diff --check

Expected: all commands exit 0. CRLF warnings from Git are acceptable; whitespace errors are not.

  • Step 3: Final report

Report:

  • changed files
  • verification output
  • exact command a user can run for MCP-backed RPC server
  • any deviations from this plan

Do not run the full suite unless the focused verification is green.


Self-Review

  • Product fit: this is the smallest visible hook after MCP-backed WorkflowServer construction.
  • Boundary: wf_transport_rpc_http.cli imports wf_mcp.broker only for process startup selection; JSON-RPC app/method modules still take a neutral WorkflowServer.
  • Config semantics: --mcp-config and --store-root conflict because they own different server composition paths.
  • Testing: no real socket startup; tests monkeypatch uvicorn.run.