15 KiB
MCP Broker DTO Conversion Boundary 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: Remove wf_mcp.ConnectionConfig imports from wf_sources_mcp by making source-side conversion structural and moving broker DTO construction to wf_mcp.
Architecture: wf_sources_mcp owns source-provider DTOs and may adapt legacy connection-like inputs by structural protocol. It must not construct broker runtime DTOs. wf_mcp.source_registry becomes the compatibility/broker conversion module: it re-exports canonical source registry models and owns helpers that produce ConnectionConfig.
Tech Stack: Python 3.14, structural Protocol, dataclasses in tests, pytest, ruff, basedpyright, AST import guards.
Why This Slice Exists
After the ID cleanup, the remaining real wf_sources_mcp -> wf_mcp dependencies are legacy broker DTO conversions:
wf_sources_mcp.connectionsimportswf_mcp.broker.models.ConnectionConfigfor type checking.wf_sources_mcp.source_registryimportswf_mcp.models.ConnectionConfigat runtime to construct broker configs.
Those conversions are compatibility edges. wf_sources_mcp should own MCP source objects, not broker runtime objects.
Hard Boundaries
- Do not move
ConnectionConfigitself in this slice. - Do not change
ConnectionConfigfields or behavior. - Do not change source registry JSON shape.
- Do not change
McpSourceRegistryEntryfields. - Do not remove
wf_mcp.source_registrycompatibility imports. - Do not import
wf_mcpfrom anysrc/wf_sources_mcp/*.pyfile. - Keep existing broker call sites working.
- Do not commit unless the caller explicitly asks for a commit.
File Map
- Modify
src/wf_sources_mcp/connections.py: replaceConnectionConfigtype import with structural protocol. - Modify
src/wf_sources_mcp/source_registry.py: keep canonical models/store and input-only conversion; remove broker DTO construction helpers. - Modify
src/wf_mcp/source_registry.py: re-export canonical models/store and define broker DTO construction helpers. - Modify
src/wf_mcp/broker/config.py: import broker DTO construction helpers fromwf_mcp.source_registry. - Modify
src/wf_mcp/broker/service/connection_service.py: import broker DTO construction helpers fromwf_mcp.source_registry. - Modify tests:
tests/wf_sources_mcp/test_connections.pytests/wf_sources_mcp/test_source_registry.pytests/wf_mcp/test_source_registry.pytests/wf_sources_mcp/test_import_direction_guard.py
- Modify docs:
docs/current_roadmap.mddocs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md
- Move this plan to
docs/historical/superpowers/plans/after implementation is verified.
Task 1: Make Legacy Connection Input Structural in connections.py
Files:
-
Modify:
src/wf_sources_mcp/connections.py -
Modify:
tests/wf_sources_mcp/test_connections.py -
Step 1: Replace
ConnectionConfigtype import with protocols
In src/wf_sources_mcp/connections.py, remove:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from wf_mcp.broker.models import ConnectionConfig
Add:
from collections.abc import Mapping
from typing import Protocol
class LegacyConnectionConfigLike(Protocol):
"""Structural shape needed from legacy broker connection configs."""
id: str
server: str
account: str
enabled: bool
metadata: Mapping[str, object]
Update signatures:
def mcp_source_connection_from_connection_config(
connection: LegacyConnectionConfigLike,
) -> McpSourceConnection:
...
def _transport_from_connection_metadata(
connection: LegacyConnectionConfigLike,
) -> SourceTransport | None:
...
Add LegacyConnectionConfigLike to __all__.
- Step 2: Preserve metadata handling
Keep the exact current metadata logic:
-
dict
metadata["transport"]supports{"kind": "stdio"}and{"kind": "http"}; -
flat
"stdio"metadata supportscommand,args,env,cwd; -
flat HTTP aliases support
url,headers; -
missing transport returns
None; -
unsupported transport raises
ValueError. -
Step 3: Add a no-
wf_mcpfake test
In tests/wf_sources_mcp/test_connections.py, add:
from dataclasses import dataclass, field
from typing import Any
@dataclass(slots=True)
class _LegacyConnectionLike:
id: str
server: str
account: str
enabled: bool = True
metadata: dict[str, object] = field(default_factory=dict)
Add a test using _LegacyConnectionLike with stdio metadata and assert conversion works. Existing tests that import wf_mcp.broker.models.ConnectionConfig may remain temporarily, but at least one canonical test must prove the converter does not need the concrete broker class.
- Step 4: Run connection tests
Run:
uv run pytest tests/wf_sources_mcp/test_connections.py -q
Expected: pass.
Task 2: Move Broker DTO Construction Helpers to wf_mcp.source_registry
Files:
-
Modify:
src/wf_sources_mcp/source_registry.py -
Modify:
src/wf_mcp/source_registry.py -
Modify:
tests/wf_sources_mcp/test_source_registry.py -
Modify:
tests/wf_mcp/test_source_registry.py -
Step 1: Keep input-only seed conversion in
wf_sources_mcp.source_registry
In src/wf_sources_mcp/source_registry.py, remove all wf_mcp.models.ConnectionConfig imports.
Add a structural protocol:
from collections.abc import Mapping
from typing import Protocol
class LegacyConnectionConfigLike(Protocol):
id: str
server: str
account: str
enabled: bool
metadata: Mapping[str, object]
Keep connection_config_to_registry_entry(connection: LegacyConnectionConfigLike) -> McpSourceRegistryEntry.
This helper is allowed to stay in wf_sources_mcp because it converts legacy-shaped input into canonical source registry state and does not construct broker DTOs.
- Step 2: Remove broker-output helpers from canonical
__all__
Remove these functions from src/wf_sources_mcp/source_registry.py:
registry_entry_to_connection_configworkflow_mcp_source_to_connection_config
Remove them from __all__.
- Step 3: Define broker-output helpers in
src/wf_mcp/source_registry.py
Replace the pure shim with a mixed compatibility module:
"""Compatibility and broker conversion helpers for MCP source registry state.
Canonical registry models and stores live in `wf_sources_mcp.source_registry`.
Helpers that construct `ConnectionConfig` stay here because `ConnectionConfig`
is a broker compatibility DTO.
"""
from __future__ import annotations
from wf_sources_mcp.source_registry import (
FileSourceRegistryStore,
HttpSourceTransport,
McpSourceRegistryEntry,
SourceRegistryFile,
SourceRegistryStore,
SourceTransport,
StdioSourceTransport,
connection_config_to_registry_entry,
)
from .models import ConnectionConfig
def registry_entry_to_connection_config(entry: McpSourceRegistryEntry) -> ConnectionConfig:
...
def workflow_mcp_source_to_connection_config(source: object) -> ConnectionConfig:
...
Move the current implementations of registry_entry_to_connection_config and workflow_mcp_source_to_connection_config from wf_sources_mcp.source_registry into this module unchanged except for imports.
Ensure __all__ includes all re-exported canonical names plus the broker-output helpers.
- Step 4: Move broker-output tests to
wf_mcp
In tests/wf_sources_mcp/test_source_registry.py:
- keep tests for
McpSourceRegistryEntry; - keep tests for
SourceRegistryFile; - keep tests for
FileSourceRegistryStore; - keep tests for
connection_config_to_registry_entry, but use a local_LegacyConnectionLikedataclass instead of importingwf_mcp.models.ConnectionConfig; - remove tests for
registry_entry_to_connection_config; - remove tests for
workflow_mcp_source_to_connection_configif present.
In tests/wf_mcp/test_source_registry.py:
-
keep or add tests for
registry_entry_to_connection_config; -
keep or add tests for
workflow_mcp_source_to_connection_config; -
assert these helpers return concrete
wf_mcp.models.ConnectionConfig. -
Step 5: Run source registry tests
Run:
uv run pytest tests/wf_sources_mcp/test_source_registry.py tests/wf_mcp/test_source_registry.py -q
Expected: pass.
Task 3: Update Broker Call Sites to Import Broker Conversions
Files:
-
Modify:
src/wf_mcp/broker/config.py -
Modify:
src/wf_mcp/broker/service/connection_service.py -
Step 1: Update imports in broker config
In src/wf_mcp/broker/config.py, import canonical models/stores from wf_sources_mcp.source_registry only when they are pure source registry objects.
Import broker-output helper from wf_mcp.source_registry:
from wf_mcp.source_registry import workflow_mcp_source_to_connection_config
Do not import workflow_mcp_source_to_connection_config from wf_sources_mcp.source_registry.
- Step 2: Update imports in connection service
In src/wf_mcp/broker/service/connection_service.py:
from wf_mcp.source_registry import (
connection_config_to_registry_entry,
registry_entry_to_connection_config,
)
connection_config_to_registry_entry may be re-exported from wf_mcp.source_registry for consistency at broker call sites, even though canonical implementation remains in wf_sources_mcp.source_registry.
- Step 3: Run broker source registry tests
Run:
uv run pytest tests/wf_mcp/test_workflow_config_bridge.py tests/wf_mcp/service/test_connection_service.py tests/wf_mcp/test_source_registry.py -q
Expected: pass.
Task 4: Add Import Guards for Broker DTO Dependencies
Files:
-
Modify:
tests/wf_sources_mcp/test_import_direction_guard.py -
Step 1: Add forbidden broker DTO import test
Append:
def test_wf_sources_mcp_does_not_import_wf_mcp_broker_dtos() -> None:
root = Path(__file__).resolve().parents[2] / "src" / "wf_sources_mcp"
forbidden = {"wf_mcp.models", "wf_mcp.broker.models"}
violations: list[str] = []
for py_file in sorted(root.rglob("*.py")):
rel = py_file.relative_to(root.parent)
module = str(rel.with_suffix("")).replace("/", ".").replace("\\", ".")
tree = ast.parse(py_file.read_text(encoding="utf-8"), filename=str(py_file))
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and node.module in forbidden:
violations.append(f"{module}:{node.lineno}: from {node.module} import ...")
elif isinstance(node, ast.Import):
for alias in node.names:
if alias.name in forbidden:
violations.append(f"{module}:{node.lineno}: import {alias.name}")
assert violations == [], (
"wf_sources_mcp still imports wf_mcp broker DTO modules:\n"
+ "\n".join(f" {violation}" for violation in violations)
)
- Step 2: Run import guards
Run:
uv run pytest tests/wf_sources_mcp/test_import_direction_guard.py -q
Expected: pass.
Task 5: Update Package Root Exports
Files:
-
Modify:
src/wf_sources_mcp/__init__.py -
Step 1: Remove broker-output helper exports
Remove these names from wf_sources_mcp.__all__ and __getattr__ routing:
registry_entry_to_connection_configworkflow_mcp_source_to_connection_config
Keep:
connection_config_to_registry_entrymcp_source_connection_from_connection_config
Those remaining helpers must be structural/input-only and must not import wf_mcp.
- Step 2: Run package export tests
Run:
uv run pytest tests/wf_sources_mcp tests/wf_mcp/test_compat_imports.py -q
Expected: pass.
If tests expect broker-output helpers at the wf_sources_mcp package root, update them to import from wf_mcp.source_registry. Do not keep broker-output helpers at the source-provider package root.
Task 6: Update Docs and Archive Plan
Files:
-
Modify:
docs/current_roadmap.md -
Modify:
docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md -
Move:
docs/superpowers/plans/2026-06-08-wf-sources-mcp-broker-dto-conversion-boundary.mdtodocs/historical/superpowers/plans/2026-06-08-wf-sources-mcp-broker-dto-conversion-boundary.md -
Step 1: Update roadmap
Under the wf_sources_mcp cleanup section, add:
Broker DTO construction moved out of `wf_sources_mcp`: source-provider
modules use structural legacy inputs only, while `wf_mcp.source_registry`
owns helpers that construct `ConnectionConfig`.
- Step 2: Update long-lived boundary spec
In docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md, add a completed numbered item after the source ID item:
23. Complete: broker DTO construction removed from `wf_sources_mcp`.
`wf_sources_mcp` accepts legacy-shaped inputs structurally, while
`wf_mcp.source_registry` owns helpers that construct `ConnectionConfig`.
Renumber the pending broad item if needed.
- Step 3: Archive the plan
Run:
git mv docs/superpowers/plans/2026-06-08-wf-sources-mcp-broker-dto-conversion-boundary.md docs/historical/superpowers/plans/2026-06-08-wf-sources-mcp-broker-dto-conversion-boundary.md
Expected: git status --short shows the plan under docs/historical/....
Task 7: Final Verification
Files:
-
No code edits unless verification finds a real issue.
-
Step 1: Run focused tests
Run:
uv run pytest tests/wf_sources_mcp tests/wf_mcp/test_source_registry.py tests/wf_mcp/test_workflow_config_bridge.py tests/wf_mcp/service/test_connection_service.py tests/wf_mcp/test_compat_imports.py -q
Expected: all selected tests pass.
- Step 2: Run import dependency check
Run:
rg -n "^from wf_mcp|^import wf_mcp|wf_mcp\\." src/wf_sources_mcp
Expected: no production-code imports. A package docstring mention may remain only if it explains a compatibility concern, but prefer updating stale wording if it no longer applies.
- Step 3: Run lint
Run:
uv run ruff check src/wf_sources_mcp src/wf_mcp/source_registry.py src/wf_mcp/broker/config.py src/wf_mcp/broker/service/connection_service.py tests/wf_sources_mcp tests/wf_mcp/test_source_registry.py tests/wf_mcp/test_compat_imports.py
Expected: All checks passed!
- Step 4: Run typecheck
Run:
uv run basedpyright --level error src/wf_sources_mcp src/wf_mcp/source_registry.py src/wf_mcp/broker/config.py src/wf_mcp/broker/service/connection_service.py tests/wf_sources_mcp tests/wf_mcp/test_source_registry.py
Expected: 0 errors, 0 warnings, 0 notes.
- Step 5: Check whitespace
Run:
git diff --check
Expected: no whitespace errors. CRLF warnings on Windows are acceptable.
Expected Final Report
The implementer should report:
- Files created, modified, and moved.
- Exact verification commands and pass/fail output.
- Confirmation that
src/wf_sources_mcphas no production imports fromwf_mcp. - Confirmation that broker DTO construction helpers live in
wf_mcp.source_registry. - Confirmation that source-provider conversion helpers use structural protocols.
- Any deviations from this plan.
Do not claim "full suite passed" unless the full suite was actually run.