Files
lda-wf/docs/historical/superpowers/plans/2026-06-08-wf-sources-mcp-broker-dto-conversion-boundary.md
T

487 lines
15 KiB
Markdown

# 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.connections` imports `wf_mcp.broker.models.ConnectionConfig` for type checking.
- `wf_sources_mcp.source_registry` imports `wf_mcp.models.ConnectionConfig` at 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 `ConnectionConfig` itself in this slice.
- Do not change `ConnectionConfig` fields or behavior.
- Do not change source registry JSON shape.
- Do not change `McpSourceRegistryEntry` fields.
- Do not remove `wf_mcp.source_registry` compatibility imports.
- Do not import `wf_mcp` from any `src/wf_sources_mcp/*.py` file.
- 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`: replace `ConnectionConfig` type 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 from `wf_mcp.source_registry`.
- Modify `src/wf_mcp/broker/service/connection_service.py`: import broker DTO construction helpers from `wf_mcp.source_registry`.
- Modify tests:
- `tests/wf_sources_mcp/test_connections.py`
- `tests/wf_sources_mcp/test_source_registry.py`
- `tests/wf_mcp/test_source_registry.py`
- `tests/wf_sources_mcp/test_import_direction_guard.py`
- Modify docs:
- `docs/current_roadmap.md`
- `docs/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 `ConnectionConfig` type import with protocols**
In `src/wf_sources_mcp/connections.py`, remove:
```python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from wf_mcp.broker.models import ConnectionConfig
```
Add:
```python
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:
```python
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 supports `command`, `args`, `env`, `cwd`;
- flat HTTP aliases support `url`, `headers`;
- missing transport returns `None`;
- unsupported transport raises `ValueError`.
- [ ] **Step 3: Add a no-`wf_mcp` fake test**
In `tests/wf_sources_mcp/test_connections.py`, add:
```python
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:
```bash
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:
```python
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_config`
- `workflow_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:
```python
"""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 `_LegacyConnectionLike` dataclass instead of importing `wf_mcp.models.ConnectionConfig`;
- remove tests for `registry_entry_to_connection_config`;
- remove tests for `workflow_mcp_source_to_connection_config` if 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:
```bash
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`:
```python
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`:
```python
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:
```bash
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:
```python
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:
```bash
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_config`
- `workflow_mcp_source_to_connection_config`
Keep:
- `connection_config_to_registry_entry`
- `mcp_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:
```bash
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.md` to `docs/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:
```markdown
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:
```markdown
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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_mcp` has no production imports from `wf_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.