16 KiB
MCP Adapter Helper Move 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: Move the upstream MCP adapter lookup helper from wf_mcp.broker.service.adapters into canonical wf_sources_mcp.adapters.
Architecture: wf_sources_mcp.adapters.require_adapter will accept a structural source reference so both legacy ConnectionConfig(server=...) and typed McpSourceConnection(provider=...) can resolve adapters. The type seam should model "either server or provider", not "both". The old wf_mcp.broker.service.adapters module remains a compatibility shim. UpstreamTransportService imports the canonical helper.
Tech Stack: Python 3.14, structural Protocol, Mapping, wf_sources_mcp.sdk.BackendAdapter, pytest, ruff, basedpyright.
Hard Boundaries
- Do not move
UpstreamTransportServicein this slice. - Do not change adapter registry shape: it remains
dict[str, BackendAdapter]. - Do not change error type: missing adapter should still raise
KeyError. - Do not import
wf_mcpfromsrc/wf_sources_mcp/adapters.py. - Keep
wf_mcp.broker.service.adapters.require_adapterimport-compatible via a shim. - Do not commit unless the caller explicitly asks for a commit.
File Map
- Create
src/wf_sources_mcp/adapters.py: canonical adapter-ref protocols andrequire_adapter. - Modify
src/wf_sources_mcp/__init__.py: export adapter-ref protocols andrequire_adapter. - Replace
src/wf_mcp/broker/service/adapters.py: compatibility shim. - Modify
src/wf_mcp/broker/service/upstream_transport.py: importrequire_adapterfromwf_sources_mcp.adapters. - Create
tests/wf_sources_mcp/test_adapters.py: canonical helper tests. - Modify
tests/wf_mcp/test_compat_imports.py: shim identity test. - Modify
tests/wf_sources_mcp/test_import_direction_guard.py: forbid old broker service adapter imports. - Modify docs:
docs/current_roadmap.mdanddocs/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: Add Canonical Adapter Helper Tests
Files:
-
Create:
tests/wf_sources_mcp/test_adapters.py -
Step 1: Write tests for legacy and typed source refs
Create tests/wf_sources_mcp/test_adapters.py:
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
import pytest
from wf_sources_mcp.adapters import require_adapter
from wf_sources_mcp.auth import AuthRecord
from wf_sources_mcp.catalog import DiscoveredPrompt, DiscoveredResource, DiscoveredTool
from wf_sources_mcp.connections import McpSourceConnection
from wf_sources_mcp.sdk import BackendAdapter, ToolCallResult
from wf_sources_mcp.transports import StdioSourceTransport
class _Adapter:
async def list_tools(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
) -> list[DiscoveredTool]:
return []
async def list_resources(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
) -> list[DiscoveredResource]:
return []
async def list_prompts(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
) -> list[DiscoveredPrompt]:
return []
async def get_connection_metadata(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
) -> dict[str, Any]:
return {}
async def read_resource(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
uri: str,
) -> dict[str, Any]:
raise NotImplementedError
async def get_prompt(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
prompt_name: str,
arguments: dict[str, str] | None = None,
) -> dict[str, Any]:
raise NotImplementedError
async def invoke_method(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
method: str,
params: dict[str, Any] | None = None,
) -> dict[str, Any]:
raise NotImplementedError
async def send_notification(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
method: str,
params: dict[str, Any] | None = None,
) -> None:
raise NotImplementedError
async def call_tool(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
tool_name: str,
payload: dict[str, Any],
) -> ToolCallResult:
raise NotImplementedError
@dataclass(slots=True)
class _LegacyConnection:
server: str
def test_require_adapter_uses_legacy_server_field() -> None:
adapter = _Adapter()
result = require_adapter(
_LegacyConnection(server="demo"),
{"demo": adapter},
)
assert result is adapter
def test_require_adapter_uses_typed_source_provider_field() -> None:
adapter = _Adapter()
connection = McpSourceConnection(
id="demo.default",
provider="demo",
account="default",
transport=StdioSourceTransport(command="demo-mcp"),
)
result = require_adapter(connection, {"demo": adapter})
assert result is adapter
def test_require_adapter_raises_useful_key_error() -> None:
with pytest.raises(KeyError, match="no adapter registered for source 'missing'"):
require_adapter(_LegacyConnection(server="missing"), {})
def test_require_adapter_has_backend_adapter_static_shape() -> None:
adapter: BackendAdapter = _Adapter()
assert adapter is not None
- Step 2: Run tests and verify failure before implementation
Run:
uv run pytest tests/wf_sources_mcp/test_adapters.py -q
Expected: fail with ModuleNotFoundError or import error for wf_sources_mcp.adapters.
Task 2: Create Canonical wf_sources_mcp.adapters
Files:
-
Create:
src/wf_sources_mcp/adapters.py -
Step 1: Add structural helper implementation
Create src/wf_sources_mcp/adapters.py:
from __future__ import annotations
from collections.abc import Mapping
from typing import Protocol
from wf_sources_mcp.sdk import BackendAdapter
class SourceAdapterRef(Protocol):
"""Typed source identity used by `McpSourceConnection`."""
provider: str
class LegacyAdapterRef(Protocol):
"""Legacy broker source identity used by `ConnectionConfig`."""
server: str
type AdapterLookupRef = SourceAdapterRef | LegacyAdapterRef
def _adapter_key(source: object) -> str:
server = getattr(source, "server", None)
if isinstance(server, str):
return server
provider = getattr(source, "provider", None)
if isinstance(provider, str):
return provider
raise TypeError("source must expose a string 'server' or 'provider' attribute")
def require_adapter(
source: AdapterLookupRef,
adapters: Mapping[str, BackendAdapter],
) -> BackendAdapter:
"""Return the adapter for a source or raise a useful lookup error."""
key = _adapter_key(source)
adapter = adapters.get(key)
if adapter is None:
raise KeyError(f"no adapter registered for source {key!r}")
return adapter
__all__ = ["AdapterLookupRef", "LegacyAdapterRef", "SourceAdapterRef", "require_adapter"]
Note: _adapter_key still uses runtime attribute checks because this helper is a compatibility boundary. The public type alias must express "legacy server ref OR typed provider ref"; do not require both attributes.
- Step 2: Run canonical tests
Run:
uv run pytest tests/wf_sources_mcp/test_adapters.py -q
Expected: pass.
Task 3: Export Adapter Helper From wf_sources_mcp
Files:
-
Modify:
src/wf_sources_mcp/__init__.py -
Modify:
tests/wf_sources_mcp/test_adapters.py -
Step 1: Add package-root exports
Update src/wf_sources_mcp/__init__.py so this works:
from wf_sources_mcp import AdapterLookupRef, LegacyAdapterRef, SourceAdapterRef, require_adapter
If the package uses lazy __getattr__, add these names to __all__ and route them to .adapters:
if name in {"AdapterLookupRef", "LegacyAdapterRef", "SourceAdapterRef", "require_adapter"}:
from . import adapters
return getattr(adapters, name)
- Step 2: Add package-root export test
Append to tests/wf_sources_mcp/test_adapters.py:
def test_adapter_helper_exports_from_package_root() -> None:
from wf_sources_mcp import require_adapter as root_require_adapter
from wf_sources_mcp.adapters import require_adapter
assert root_require_adapter is require_adapter
- Step 3: Run canonical tests
Run:
uv run pytest tests/wf_sources_mcp/test_adapters.py -q
Expected: pass.
Task 4: Replace Broker Service Adapter Module With Shim
Files:
-
Modify:
src/wf_mcp/broker/service/adapters.py -
Modify:
tests/wf_mcp/test_compat_imports.py -
Step 1: Replace old implementation with shim
Replace src/wf_mcp/broker/service/adapters.py with:
"""Compatibility shim for upstream MCP adapter lookup.
Canonical implementation lives in `wf_sources_mcp.adapters`.
"""
from __future__ import annotations
from wf_sources_mcp.adapters import (
AdapterLookupRef,
LegacyAdapterRef,
SourceAdapterRef,
require_adapter,
)
__all__ = ["AdapterLookupRef", "LegacyAdapterRef", "SourceAdapterRef", "require_adapter"]
- Step 2: Add shim identity test
Append to tests/wf_mcp/test_compat_imports.py:
def test_wf_mcp_broker_service_adapter_shim_reexports_wf_sources_mcp_adapter_helper() -> None:
from wf_mcp.broker.service.adapters import require_adapter as compat_require_adapter
from wf_sources_mcp.adapters import require_adapter
assert compat_require_adapter is require_adapter
- Step 3: Run compatibility test
Run:
uv run pytest tests/wf_mcp/test_compat_imports.py::test_wf_mcp_broker_service_adapter_shim_reexports_wf_sources_mcp_adapter_helper -q
Expected: pass.
Task 5: Update Upstream Transport Import
Files:
-
Modify:
src/wf_mcp/broker/service/upstream_transport.py -
Step 1: Replace import
Change:
from .adapters import require_adapter
to:
from wf_sources_mcp.adapters import require_adapter
- Step 2: Run upstream transport tests
Run:
uv run pytest tests/wf_mcp/service/test_upstream_transport.py tests/wf_mcp/service/test_catalog.py tests/wf_mcp/service/test_events.py::test_service_records_tool_call_events -q
Expected: pass.
Task 6: Add Import Guard for Old Adapter Helper
Files:
-
Modify:
tests/wf_sources_mcp/test_import_direction_guard.py -
Step 1: Add forbidden old adapter import test
Append to tests/wf_sources_mcp/test_import_direction_guard.py:
def test_wf_sources_mcp_does_not_import_old_broker_service_adapter_module() -> None:
root = Path(__file__).resolve().parents[2] / "src" / "wf_sources_mcp"
forbidden = {"wf_mcp.broker.service.adapters"}
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 old wf_mcp broker service adapter module:\n"
+ "\n".join(f" {violation}" for violation in violations)
)
- Step 2: Run import guard
Run:
uv run pytest tests/wf_sources_mcp/test_import_direction_guard.py -q
Expected: pass.
Task 7: 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-adapter-helper.mdtodocs/historical/superpowers/plans/2026-06-08-wf-sources-mcp-adapter-helper.md -
Step 1: Update
docs/current_roadmap.md
Under the wf_sources_mcp cleanup section, add:
- Completed: upstream MCP adapter lookup (`require_adapter`) now lives in
`wf_sources_mcp.adapters`, with `wf_mcp.broker.service.adapters` retained
as a compatibility shim.
- Step 2: Update
docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md
Add a completed numbered item before the pending upstream transport/discovery/session services item:
21. Complete: upstream MCP adapter lookup (`require_adapter`) moved to
`wf_sources_mcp.adapters`, with `wf_mcp.broker.service.adapters` retained
as a compatibility shim.
If numbering differs because new items landed meanwhile, keep the completed item before the broad pending item and renumber.
- Step 3: Archive the plan
Run:
git mv docs/superpowers/plans/2026-06-08-wf-sources-mcp-adapter-helper.md docs/historical/superpowers/plans/2026-06-08-wf-sources-mcp-adapter-helper.md
Expected: git status --short shows an R rename for the plan.
Task 8: 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/test_adapters.py tests/wf_sources_mcp/test_import_direction_guard.py tests/wf_mcp/test_compat_imports.py tests/wf_mcp/service/test_upstream_transport.py tests/wf_mcp/service/test_catalog.py tests/wf_mcp/service/test_events.py::test_service_records_tool_call_events -q
Expected: all selected tests pass.
- Step 2: Run source-provider tests
Run:
uv run pytest tests/wf_sources_mcp -q
Expected: all wf_sources_mcp tests pass.
- Step 3: Run lint
Run:
uv run ruff check src/wf_sources_mcp/adapters.py src/wf_mcp/broker/service/adapters.py src/wf_mcp/broker/service/upstream_transport.py tests/wf_sources_mcp/test_adapters.py tests/wf_sources_mcp/test_import_direction_guard.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/adapters.py src/wf_mcp/broker/service/adapters.py src/wf_mcp/broker/service/upstream_transport.py tests/wf_sources_mcp/test_adapters.py tests/wf_mcp/test_compat_imports.py
Expected: 0 errors, 0 warnings, 0 notes
- Step 5: Check old adapter helper import usage
Run:
rg -n "require_adapter|wf_mcp\\.broker\\.service\\.adapters|from \\.adapters import" src tests
Expected:
-
src/wf_sources_mcp/adapters.pyowns canonicalrequire_adapter. -
src/wf_mcp/broker/service/adapters.pyremains as a shim. -
src/wf_mcp/broker/service/upstream_transport.pyimports fromwf_sources_mcp.adapters. -
tests/wf_mcp/test_compat_imports.pymay reference the old shim path. -
src/wf_sources_mcpmust not importwf_mcp.broker.service.adapters. -
Step 6: 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 canonical
require_adapterlives inwf_sources_mcp.adapters. - Confirmation that
wf_mcp.broker.service.adaptersis a compatibility shim. - Confirmation that
UpstreamTransportServiceimports canonicalrequire_adapter. - Any deviations from this plan.
Do not claim "full suite passed" unless the full suite was actually run.