Files
lda-wf/docs/historical/superpowers/plans/2026-06-05-wf-config-mcp-source-model.md
T

13 KiB
Raw Blame History

wf_config MCP Source Model 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: Add a neutral kind: "mcp" source variant to wf_config.server.sources[] so MCP source definitions can live in the wider workflow config model.

Architecture: Keep this slice pure wf_config: no imports from wf_mcp, no server construction changes, no runtime behavior changes. The new config shape should intentionally mirror wf_mcp.source_registry.McpSourceRegistryEntry, with the legacy ConnectionConfig.source_config_ownership policy renamed to a neutral config/source field.

Tech Stack: Pydantic v2 discriminated unions, AnyHttpUrl, existing tests/wf_config/test_config_models.py.


File Structure

  • Modify src/wf_config/models.py: add transport config models, McpSourceConfig, and include it in the existing SourceConfig union.
  • Modify src/wf_config/__init__.py: export the new config types.
  • Modify tests/wf_config/test_config_models.py: add model parsing/validation tests for MCP sources.
  • Modify docs/current_roadmap.md and docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md: mark this slice complete after implementation.

Current Context

Current neutral config has:

class StdlibSourceConfig(WorkflowConfigModel):
    kind: Literal["stdlib"]
    id: Literal["wf.std", "wf.recipes"]


SourceConfig = Annotated[
    StdlibSourceConfig,
    Field(discriminator="kind"),
]

MCPs desired source shape already exists at src/wf_mcp/source_registry.py as McpSourceRegistryEntry, but that module imports MCP-specific validators and models. Do not import it into wf_config.

The source id rule to mirror is currently in src/wf_mcp/connections.py:

CONNECTION_ID_PATTERN = r"^[A-Za-z0-9_][A-Za-z0-9_.-]*$"

and MCP ids must look like <provider>.<account>.

Task 1: Add MCP Source Config Tests

Files:

  • Modify: tests/wf_config/test_config_models.py

  • Step 1: Add imports

Update the import block from wf_config to include the new classes that will be implemented:

from wf_config import (
    FilesystemStoreConfig,
    HttpSourceTransportConfig,
    LocalTargetConfig,
    McpSourceConfig,
    RpcHttpTargetConfig,
    RpcHttpTransportConfig,
    StdioSourceTransportConfig,
    StdlibSourceConfig,
    WorkflowConfigFile,
    load_workflow_config,
)
  • Step 2: Add stdio MCP source parsing test

Append this test:

def test_workflow_config_parses_mcp_stdio_source() -> None:
    config = WorkflowConfigFile.model_validate(
        {
            "version": 1,
            "server": {
                "sources": [
                    {
                        "kind": "mcp",
                        "id": "everything.default",
                        "enabled": True,
                        "provider": "everything",
                        "account": "default",
                        "profile": "dev",
                        "ownership": "seed",
                        "transport": {
                            "kind": "stdio",
                            "command": "uvx",
                            "args": ["mcp-server-everything"],
                            "env": {"DEBUG": "1"},
                        },
                        "auth_ref": "auth.everything.default",
                        "metadata": {"description": "Everything test server"},
                    }
                ]
            },
        }
    )

    source = config.server.sources[0]
    assert isinstance(source, McpSourceConfig)
    assert source.id == "everything.default"
    assert source.enabled is True
    assert source.provider == "everything"
    assert source.account == "default"
    assert source.profile == "dev"
    assert source.ownership == "seed"
    assert isinstance(source.transport, StdioSourceTransportConfig)
    assert source.transport.command == "uvx"
    assert source.transport.args == ("mcp-server-everything",)
    assert source.transport.env == {"DEBUG": "1"}
    assert source.auth_ref == "auth.everything.default"
    assert source.metadata["description"] == "Everything test server"
  • Step 3: Add HTTP MCP source parsing test

Append this test:

def test_workflow_config_parses_mcp_http_source() -> None:
    config = WorkflowConfigFile.model_validate(
        {
            "version": 1,
            "server": {
                "sources": [
                    {
                        "kind": "mcp",
                        "id": "context7.default",
                        "provider": "context7",
                        "account": "default",
                        "transport": {
                            "kind": "http",
                            "url": "http://127.0.0.1:3000/mcp",
                            "headers": {"X-Test": "yes"},
                        },
                    }
                ]
            },
        }
    )

    source = config.server.sources[0]
    assert isinstance(source, McpSourceConfig)
    assert source.enabled is True
    assert source.ownership == "locked"
    assert isinstance(source.transport, HttpSourceTransportConfig)
    assert str(source.transport.url) == "http://127.0.0.1:3000/mcp"
    assert source.transport.headers == {"X-Test": "yes"}
  • Step 4: Add validation tests

Append these tests:

def test_workflow_config_rejects_mcp_source_without_provider_account_shape() -> None:
    with pytest.raises(ValidationError, match="source id must look like"):
        WorkflowConfigFile.model_validate(
            {
                "version": 1,
                "server": {
                    "sources": [
                        {
                            "kind": "mcp",
                            "id": "everything",
                            "provider": "everything",
                            "account": "default",
                            "transport": {"kind": "stdio", "command": "uvx"},
                        }
                    ]
                },
            }
        )


def test_workflow_config_rejects_unsafe_mcp_source_id() -> None:
    with pytest.raises(ValidationError, match="source id must start"):
        WorkflowConfigFile.model_validate(
            {
                "version": 1,
                "server": {
                    "sources": [
                        {
                            "kind": "mcp",
                            "id": ".hidden.default",
                            "provider": "hidden",
                            "account": "default",
                            "transport": {"kind": "stdio", "command": "uvx"},
                        }
                    ]
                },
            }
        )


def test_workflow_config_rejects_duplicate_source_ids_across_kinds() -> None:
    with pytest.raises(ValidationError, match="duplicate source id"):
        WorkflowConfigFile.model_validate(
            {
                "version": 1,
                "server": {
                    "sources": [
                        {"kind": "stdlib", "id": "wf.std"},
                        {
                            "kind": "mcp",
                            "id": "wf.std",
                            "provider": "wf",
                            "account": "std",
                            "transport": {"kind": "stdio", "command": "uvx"},
                        },
                    ]
                },
            }
        )
  • Step 5: Run tests and verify failure

Run:

uv run pytest tests/wf_config/test_config_models.py -q

Expected: fail with import errors for McpSourceConfig, StdioSourceTransportConfig, and HttpSourceTransportConfig.

Task 2: Implement Neutral MCP Source Config Models

Files:

  • Modify: src/wf_config/models.py

  • Step 1: Add imports

Update imports:

import re
from pathlib import Path
from typing import Annotated, Literal
  • Step 2: Add ownership and source id constants

Place after ServerTransportConfig:

SourceConfigOwnership = Literal["locked", "seed"]
SOURCE_ID_PATTERN = r"^[A-Za-z0-9_][A-Za-z0-9_.-]*$"
  • Step 3: Add transport models

Place before StdlibSourceConfig:

class StdioSourceTransportConfig(WorkflowConfigModel):
    kind: Literal["stdio"] = "stdio"
    command: str = Field(min_length=1)
    args: tuple[str, ...] = ()
    env: dict[str, str] = Field(default_factory=dict)


class HttpSourceTransportConfig(WorkflowConfigModel):
    kind: Literal["http"] = "http"
    url: AnyHttpUrl
    headers: dict[str, str] = Field(default_factory=dict)


SourceTransportConfig = Annotated[
    StdioSourceTransportConfig | HttpSourceTransportConfig,
    Field(discriminator="kind"),
]
  • Step 4: Add MCP source model

Place after StdlibSourceConfig:

class McpSourceConfig(WorkflowConfigModel):
    """Neutral config shape for MCP-backed workflow capability sources.

    This intentionally mirrors `wf_mcp.source_registry.McpSourceRegistryEntry`
    without importing MCP modules. `ownership` carries the old
    `ConnectionConfig.source_config_ownership` policy with neutral terminology.
    """

    kind: Literal["mcp"] = "mcp"
    id: str
    enabled: bool = True
    provider: str = Field(min_length=1)
    account: str = Field(min_length=1)
    profile: str | None = None
    ownership: SourceConfigOwnership = "locked"
    transport: SourceTransportConfig
    auth_ref: str | None = None
    metadata: dict[str, object] = Field(default_factory=dict)

    @field_validator("id")
    @classmethod
    def validate_source_id(cls, value: str) -> str:
        if not re.fullmatch(SOURCE_ID_PATTERN, value):
            raise ValueError(
                "source id must start with alphanumeric or underscore and contain "
                "only [A-Za-z0-9_.-]"
            )
        if "." not in value:
            raise ValueError("source id must look like '<provider>.<account>'")
        provider, account = value.split(".", 1)
        if not provider or not account:
            raise ValueError("source id must look like '<provider>.<account>'")
        return value
  • Step 5: Extend SourceConfig union

Replace the existing union with:

SourceConfig = Annotated[
    StdlibSourceConfig | McpSourceConfig,
    Field(discriminator="kind"),
]
  • Step 6: Run tests

Run:

uv run pytest tests/wf_config/test_config_models.py -q

Expected: pass.

Task 3: Export New Config Types

Files:

  • Modify: src/wf_config/__init__.py

  • Step 1: Add imports

Update the import block to include:

    HttpSourceTransportConfig,
    McpSourceConfig,
    SourceConfigOwnership,
    SourceTransportConfig,
    StdioSourceTransportConfig,
  • Step 2: Add __all__ entries

Add:

    "HttpSourceTransportConfig",
    "McpSourceConfig",
    "SourceConfigOwnership",
    "SourceTransportConfig",
    "StdioSourceTransportConfig",
  • Step 3: Run tests

Run:

uv run pytest tests/wf_config/test_config_models.py -q

Expected: pass.

Task 4: Document Slice Completion

Files:

  • Modify: docs/current_roadmap.md

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

  • Step 1: Update roadmap

In docs/current_roadmap.md, under the "Wider wf_config source model" bullet, append:

    First slice complete: `wf_config.server.sources[]` now accepts
    `kind: "mcp"` entries with stdio/http transport, auth reference, metadata,
    enabled flag, and `locked` / `seed` ownership policy. Runtime composition
    from these entries is the next slice.
  • Step 2: Update spec

In docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md, under the Slice 1 text, append:

   Model slice complete when `wf_config.server.sources[]` accepts `kind: "mcp"`
   entries. The next slice converts those neutral source entries into MCP
   broker runtime connections and server composition.
  • Step 3: Run docs diff

Run:

git diff -- docs/current_roadmap.md docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md

Expected: only the completion notes above.

Task 5: Final Verification and Commit

Files:

  • All touched files from prior tasks.

  • Step 1: Run focused tests

Run:

uv run pytest tests/wf_config/test_config_models.py -q

Expected: all tests pass.

  • Step 2: Run lint/type checks

Run:

uv run ruff check src/wf_config tests/wf_config
uv run basedpyright --level error src/wf_config tests/wf_config

Expected: both pass with 0 errors.

  • Step 3: Commit

Run:

git add src/wf_config tests/wf_config docs/current_roadmap.md docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md
git commit -m "feat: add mcp source config model"

Self-Review Checklist

  • This plan does not import wf_mcp into wf_config.
  • This plan does not change runtime behavior.
  • The ownership field is neutral (ownership), while docs explain its legacy origin.
  • Duplicate source id validation continues to work across all source kinds.