487 lines
15 KiB
Markdown
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.
|