15 KiB
MCP Specs From Discovered Tools 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 neutral specs_from_discovered_tools logic from wf_mcp.broker.discovery into wf_sources_mcp.discovery, leaving broker McpEvent projection in the compatibility adapter.
Architecture: wf_sources_mcp.discovery.specs_from_discovered_tools will accept McpSourceConnection, AuthRecord, ToolExecutor, DiscoveredTool list, and neutral ToolWrapperEventSink. wf_mcp.broker.discovery.specs_from_discovered_tools remains as the broker compatibility wrapper: it converts ConnectionConfig to McpSourceConnection and projects neutral ToolWrapperEvent into broker McpEvent.
Tech Stack: Python 3.14, wf_authoring.NodeSpec, wf_sources_mcp typed connection/auth/sdk/tool wrapper DTOs, pytest, ruff, basedpyright.
Hard Boundaries
- Do not remove
wf_mcp.broker.discovery.specs_from_discovered_tools; keep it as a compatibility adapter. - Do not change broker event kinds or payload shapes.
- Do not change generated
NodeSpecbehavior. - Do not import
wf_mcpfromsrc/wf_sources_mcp/discovery.py. - Keep
discover_connection_capabilitiesinwf_sources_mcp.discovery. - Do not touch
UpstreamTransportServicebehavior beyond imports if needed. - Do not commit unless the caller explicitly asks for a commit.
File Map
- Modify
src/wf_sources_mcp/discovery.py: add canonicalspecs_from_discovered_tools. - Modify
src/wf_sources_mcp/__init__.py: exportspecs_from_discovered_tools. - Modify
src/wf_mcp/broker/discovery.py: import canonical specs helper under an alias and keep the broker adapter. - Create or modify
tests/wf_sources_mcp/test_discovery.py: add canonical specs tests. - Modify
tests/wf_mcp/test_compat_imports.py: add/extend compatibility test for broker adapter if useful. - 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 Specs Tests
Files:
-
Modify:
tests/wf_sources_mcp/test_discovery.py -
Step 1: Add imports needed for specs tests
In tests/wf_sources_mcp/test_discovery.py, extend imports:
from wf_authoring import build_async_registry
from wf_core import RuntimeContext
from wf_sources_mcp.discovery import (
discover_connection_capabilities,
specs_from_discovered_tools,
)
Keep existing imports for AuthRecord, DiscoveredTool, McpSourceConnection, ToolCallResult, and BackendAdapter.
- Step 2: Add a recording executor fake
Append after existing adapter fakes:
class _RecordingExecutor:
def __init__(self) -> None:
self.calls: list[dict[str, Any]] = []
async def call_tool(
self,
connection: McpSourceConnection,
auth: AuthRecord | None,
tool_name: str,
payload: dict[str, Any],
) -> ToolCallResult:
self.calls.append(
{
"connection": connection,
"auth": auth,
"tool_name": tool_name,
"payload": payload,
}
)
return ToolCallResult(
outcome="ok",
output={"content": [{"type": "text", "text": "Echo: hello"}]},
meta={"duration_ms": 3},
)
- Step 3: Add canonical specs behavior test
Append:
async def test_specs_from_discovered_tools_wraps_tools_with_neutral_events() -> None:
executor = _RecordingExecutor()
events = []
connection = _connection()
specs = specs_from_discovered_tools(
connection=connection,
auth=None,
executor=executor,
tools=[
DiscoveredTool(
name="echo",
title="Echo",
description="Echo input",
input_schema={
"type": "object",
"properties": {"message": {"type": "string"}},
"required": ["message"],
},
output_schema={
"type": "object",
"properties": {"content": {"type": "array"}},
},
)
],
emit_event=events.append,
)
handler = build_async_registry(*specs)["echo"]
result = await handler(
{"message": "hello"},
RuntimeContext(current_node_id="echo"),
)
assert result["outcome"] == "ok"
assert result["output"]["content"][0]["text"] == "Echo: hello"
assert executor.calls[0]["connection"] is connection
assert executor.calls[0]["tool_name"] == "echo"
assert executor.calls[0]["payload"] == {"message": "hello"}
assert [event.kind for event in events] == [
"tool_call_started",
"tool_call_completed",
]
assert events[0].capability_id == "demo.default.echo"
assert events[1].payload == {"outcome": "ok", "meta": {"duration_ms": 3}}
- Step 4: Add package-root export assertion
Append:
def test_specs_from_discovered_tools_exports_from_package_root() -> None:
from wf_sources_mcp import specs_from_discovered_tools as root_specs_from_tools
from wf_sources_mcp.discovery import specs_from_discovered_tools
assert root_specs_from_tools is specs_from_discovered_tools
- Step 5: Run tests and verify failure before implementation
Run:
uv run pytest tests/wf_sources_mcp/test_discovery.py -q
Expected: fail because wf_sources_mcp.discovery.specs_from_discovered_tools is not defined/exported yet.
Task 2: Add Canonical specs_from_discovered_tools
Files:
-
Modify:
src/wf_sources_mcp/discovery.py -
Step 1: Add imports
In src/wf_sources_mcp/discovery.py, add:
from wf_authoring import NodeSpec
from wf_sources_mcp.tool_events import ToolWrapperEventSink
from wf_sources_mcp.tool_wrappers import wrap_discovered_tool
from wf_sources_mcp.sdk import BackendAdapter, ToolExecutor
If BackendAdapter is already imported, merge the import:
from wf_sources_mcp.sdk import BackendAdapter, ToolExecutor
- Step 2: Add canonical function
Append below discover_connection_capabilities:
def specs_from_discovered_tools(
*,
connection: McpSourceConnection,
auth: AuthRecord | None,
executor: ToolExecutor,
tools: list[DiscoveredTool],
emit_event: ToolWrapperEventSink | None = None,
) -> list[NodeSpec[Any, Any]]:
return [
wrap_discovered_tool(
connection=connection,
auth=auth,
executor=executor,
tool=tool,
emit_event=emit_event,
)
for tool in tools
]
- Step 3: Update
__all__
Change:
__all__ = ["DiscoveredConnectionCapabilities", "discover_connection_capabilities"]
to:
__all__ = [
"DiscoveredConnectionCapabilities",
"discover_connection_capabilities",
"specs_from_discovered_tools",
]
- Step 4: Run canonical discovery tests
Run:
uv run pytest tests/wf_sources_mcp/test_discovery.py -q
Expected: tests still fail only if package-root export is not wired yet. Direct module tests should pass.
Task 3: Export specs_from_discovered_tools From Package Root
Files:
-
Modify:
src/wf_sources_mcp/__init__.py -
Step 1: Add lazy export
Add "specs_from_discovered_tools" to __all__.
In the existing discovery __getattr__ branch, include this name:
if name in {
"DiscoveredConnectionCapabilities",
"discover_connection_capabilities",
"specs_from_discovered_tools",
}:
from . import discovery
return getattr(discovery, name)
- Step 2: Run canonical discovery tests
Run:
uv run pytest tests/wf_sources_mcp/test_discovery.py -q
Expected: all tests pass.
Task 4: Make Broker Discovery a Compatibility Adapter
Files:
-
Modify:
src/wf_mcp/broker/discovery.py -
Step 1: Import canonical specs helper under an alias
Change imports:
from wf_sources_mcp.discovery import (
DiscoveredConnectionCapabilities,
discover_connection_capabilities,
)
to:
from wf_sources_mcp.discovery import (
DiscoveredConnectionCapabilities,
discover_connection_capabilities,
specs_from_discovered_tools as source_specs_from_discovered_tools,
)
- Step 2: Remove now-unneeded imports
Remove:
from wf_authoring import NodeSpec
from wf_sources_mcp.catalog import DiscoveredTool
from wf_sources_mcp.tool_wrappers import wrap_discovered_tool
Keep:
from typing import Any
from collections.abc import Callable
from wf_sources_mcp.connections import mcp_source_connection_from_connection_config
from wf_sources_mcp.sdk import ToolExecutor
from wf_sources_mcp.tool_events import ToolWrapperEvent
If basedpyright needs the return type annotation, keep NodeSpec and DiscoveredTool; otherwise prefer keeping the public signature unchanged:
) -> list[NodeSpec[Any, Any]]:
In that case, keep from wf_authoring import NodeSpec and from wf_sources_mcp.catalog import DiscoveredTool.
- Step 3: Replace function body to delegate to canonical helper
Inside specs_from_discovered_tools, keep source_connection and emit_tool_event, then replace the list comprehension with:
return source_specs_from_discovered_tools(
connection=source_connection,
auth=auth,
executor=executor,
tools=tools,
emit_event=emit_tool_event if emit_event is not None else None,
)
- Step 4: Run broker event behavior test
Run:
uv run pytest tests/wf_mcp/service/test_events.py::test_service_records_tool_call_events -q
Expected: pass.
Task 5: Add Compatibility Test for Broker Adapter
Files:
-
Modify:
tests/wf_mcp/test_compat_imports.py -
Step 1: Add assertion that broker adapter remains distinct but callable
Append:
def test_wf_mcp_broker_discovery_keeps_specs_adapter() -> None:
from wf_mcp.broker.discovery import specs_from_discovered_tools as broker_specs
from wf_sources_mcp.discovery import specs_from_discovered_tools as source_specs
assert broker_specs is not source_specs
assert broker_specs.__name__ == "specs_from_discovered_tools"
This is intentionally not an identity test because the broker function adapts ConnectionConfig and broker McpEvent.
- Step 2: Run compatibility tests
Run:
uv run pytest tests/wf_mcp/test_compat_imports.py::test_wf_mcp_broker_discovery_keeps_specs_adapter -q
Expected: pass.
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-specs-from-discovered-tools.mdtodocs/historical/superpowers/plans/2026-06-08-wf-sources-mcp-specs-from-discovered-tools.md -
Step 1: Update
docs/current_roadmap.md
Under the wf_sources_mcp cleanup section, add:
- Completed: neutral `specs_from_discovered_tools` now lives in
`wf_sources_mcp.discovery`. `wf_mcp.broker.discovery` remains as the
broker adapter for `ConnectionConfig` and `McpEvent` projection.
- 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:
20. Complete: neutral `specs_from_discovered_tools` moved to
`wf_sources_mcp.discovery`. `wf_mcp.broker.discovery` remains as the broker
adapter for legacy `ConnectionConfig` input and `McpEvent` projection.
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-specs-from-discovered-tools.md docs/historical/superpowers/plans/2026-06-08-wf-sources-mcp-specs-from-discovered-tools.md
Expected: git status --short shows an R rename for the plan.
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/test_discovery.py tests/wf_sources_mcp/test_tool_wrappers.py tests/wf_sources_mcp/test_import_direction_guard.py tests/wf_mcp/test_compat_imports.py tests/wf_mcp/service/test_events.py::test_service_records_tool_call_events tests/wf_mcp/service/test_catalog.py tests/wf_mcp/service/test_upstream_transport.py -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/discovery.py src/wf_mcp/broker/discovery.py tests/wf_sources_mcp/test_discovery.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/discovery.py src/wf_mcp/broker/discovery.py tests/wf_sources_mcp/test_discovery.py tests/wf_mcp/test_compat_imports.py
Expected: 0 errors, 0 warnings, 0 notes
- Step 5: Check remaining broker discovery import usage
Run:
rg -n "specs_from_discovered_tools|wf_mcp\\.broker\\.discovery|from \\.discovery import" src tests
Expected:
-
src/wf_sources_mcp/discovery.pyowns neutralspecs_from_discovered_tools. -
src/wf_mcp/broker/discovery.pyowns broker adapterspecs_from_discovered_tools. -
src/wf_mcp/broker/service/upstream_transport.pymay still import broker adapter. -
src/wf_sources_mcpmust not importwf_mcp.broker.discovery. -
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 neutral
specs_from_discovered_toolsis canonical inwf_sources_mcp.discovery. - Confirmation that
wf_mcp.broker.discovery.specs_from_discovered_toolsremains as a broker adapter. - Confirmation that broker tool-call events still emit
McpEvent. - Any deviations from this plan.
Do not claim "full suite passed" unless the full suite was actually run.