14 KiB
MCP Runtime Full Operation Surface 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: Make the persistent MCP runtime pool implement the same MCP operation surface as the one-shot backend adapter, then route broker operations through the persistent runtime whenever it is configured.
Architecture: BackendAdapter and StatefulMcpRuntime both represent "execute MCP operations for a configured source"; they should differ by session strategy, not by operation set. McpSdkAdapter remains the one-shot implementation. McpRuntimePool becomes the persistent implementation and should be preferred by UpstreamTransportService for tool calls, resource/prompt operations, catalog listings, raw methods, notifications, and liveness probes when available.
Tech Stack: Python 3.14, MCP SDK ClientSession, McpSourceClient, persistent owner-task queue, pytest-asyncio, ruff, basedpyright.
Hard Boundaries
- Do not remove
McpSdkAdapter; it remains the one-shot fallback. - Do not remove
BackendAdapterorStatefulMcpRuntimenames in this slice. - Do not change public JSON-RPC or CLI payload shapes.
- Do not change source registry or config file formats.
- Do not add e2e JSON-RPC session-reuse tests in this slice; that is the next QA slice.
- Do not expose raw
ClientSessionoutsidewf_sources_mcp.runtimeinternals. - Do not dispatch owner-task operations via
getattr(client, operation); continue passing explicit callables. - Do not commit unless the caller explicitly asks for a commit.
File Map
- Modify
src/wf_sources_mcp/sdk/protocols.py: expandStatefulMcpRuntimeto full operation surface or introduce a shared full-surface protocol. - Modify
src/wf_sources_mcp/runtime/session.py: add persistent session methods forlist_tools,get_connection_metadata,invoke_method, andsend_notification. - Modify
src/wf_sources_mcp/runtime/factory.py: route those new methods through_SessionOwner.submit(). - Modify
src/wf_sources_mcp/runtime/pool.py: expose full operation methods. - Modify
src/wf_sources_mcp/client/source_client.py: ensure all operations needed by runtime owner already exist and stay reusable. - Modify
src/wf_mcp/broker/service/upstream_transport.py: preferstateful_runtimefor all MCP operations that can use an existing session. - Modify tests:
tests/wf_sources_mcp/test_sdk_protocols.pytests/wf_sources_mcp/test_runtime.pytests/wf_mcp/service/test_upstream_transport.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: Expand the Runtime Protocol Surface
Files:
-
Modify:
src/wf_sources_mcp/sdk/protocols.py -
Modify:
tests/wf_sources_mcp/test_sdk_protocols.py -
Step 1: Add protocol tests first
In tests/wf_sources_mcp/test_sdk_protocols.py, add or extend a static-shape test that assigns an object implementing all operations to both BackendAdapter and StatefulMcpRuntime.
The object should include:
get_connection_metadatalist_toolslist_resourceslist_promptscall_toolread_resourceget_promptinvoke_methodsend_notification
Use existing test fakes where possible.
- Step 2: Update protocol definitions
In src/wf_sources_mcp/sdk/protocols.py, prefer this shape:
class McpSourceOperations(Protocol):
async def list_tools(...): ...
async def list_resources(...): ...
async def list_prompts(...): ...
async def get_connection_metadata(...): ...
async def read_resource(...): ...
async def get_prompt(...): ...
async def invoke_method(...): ...
async def send_notification(...): ...
async def call_tool(...): ...
class BackendAdapter(McpSourceOperations, Protocol):
"""One-shot or adapter-style MCP operation executor."""
class StatefulMcpRuntime(McpSourceOperations, Protocol):
"""Persistent MCP operation executor for configured sources."""
Keep existing narrow aliases (ToolRuntime, ResourceRuntime, PromptRuntime, ToolExecutor) for compatibility.
Add McpSourceOperations to __all__ and package exports in src/wf_sources_mcp/sdk/__init__.py.
- Step 3: Run protocol tests
Run:
uv run pytest tests/wf_sources_mcp/test_sdk_protocols.py -q
Expected: pass.
Task 2: Add Full Operations to PersistentMcpSession
Files:
-
Modify:
src/wf_sources_mcp/runtime/session.py -
Modify:
tests/wf_sources_mcp/test_runtime.py -
Step 1: Add tests for session-level operations
Extend fake client/session tests in tests/wf_sources_mcp/test_runtime.py to cover:
list_tools()get_connection_metadata()invoke_method()send_notification()
For injected/fake client path, verify:
-
list tools are converted to
DiscoveredTool; -
metadata is local
{server, transport}; -
invoke returns dumped payload;
-
notification records that it was sent.
-
Step 2: Add callback types and methods
In src/wf_sources_mcp/runtime/session.py, add callback types:
RawToolLister = Callable[[], Awaitable[list[DiscoveredTool]]]
RawMetadataGetter = Callable[[], Awaitable[dict[str, Any]]]
RawMethodInvoker = Callable[[str, dict[str, Any] | None], Awaitable[dict[str, Any]]]
RawNotificationSender = Callable[[str, dict[str, Any] | None], Awaitable[None]]
Add corresponding dataclass fields.
Implement methods:
list_tools()get_connection_metadata()invoke_method(method, params=None)send_notification(method, params=None)
Use callback first, then injected client fallback where applicable. Metadata can be computed locally from self.connection without client.
- Step 3: Run runtime tests
Run:
uv run pytest tests/wf_sources_mcp/test_runtime.py -q
Expected: pass.
Task 3: Route Full Operations Through the Owner Task
Files:
-
Modify:
src/wf_sources_mcp/runtime/factory.py -
Modify:
tests/wf_sources_mcp/test_runtime.py -
Step 1: Add owner-routing tests
In tests/wf_sources_mcp/test_runtime.py, add or extend a test proving PersistentSessionFactory routes these operations through _SessionOwner.submit():
list_toolsinvoke_methodsend_notification
Existing tests already cover resources/prompts/tool calls. Keep all operations explicit; do not test implementation internals like queue item class unless existing patterns do.
- Step 2: Wire callbacks in
PersistentSessionFactory.create()
In create(), pass:
list_tools_callback=owner.list_tools
get_connection_metadata_callback=owner.get_connection_metadata
invoke_method_callback=owner.invoke_method
send_notification_callback=owner.send_notification
- Step 3: Add
_SessionOwnermethods
In _SessionOwner, add:
async def list_tools(self) -> list[DiscoveredTool]:
return await self.submit("list_tools", lambda client: client.list_tools())
async def get_connection_metadata(self) -> dict[str, Any]:
return await self.submit(
"get_connection_metadata",
lambda client: client.get_connection_metadata(),
)
async def invoke_method(self, method: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
return await self.submit(
"invoke_method",
lambda client: client.invoke_method(method, params),
)
async def send_notification(self, method: str, params: dict[str, Any] | None = None) -> None:
await self.submit(
"send_notification",
lambda client: client.send_notification(method, params),
)
- Step 4: Run runtime tests
Run:
uv run pytest tests/wf_sources_mcp/test_runtime.py -q
Expected: pass.
Task 4: Add Full Operations to McpRuntimePool
Files:
-
Modify:
src/wf_sources_mcp/runtime/pool.py -
Modify:
tests/wf_sources_mcp/test_runtime.py -
Step 1: Add pool-level tests
Add tests proving the pool reuses one session for:
list_toolsinvoke_methodsend_notification
Also extend the public protocol/static-shape test so McpRuntimePool satisfies StatefulMcpRuntime.
- Step 2: Implement pool methods
Add methods to McpRuntimePool:
async def list_tools(...)
async def get_connection_metadata(...)
async def invoke_method(...)
async def send_notification(...)
Each method should:
- call
get_session(connection, auth); - delegate to the matching
PersistentMcpSessionmethod.
- Step 3: Run runtime tests
Run:
uv run pytest tests/wf_sources_mcp/test_runtime.py tests/wf_sources_mcp/test_sdk_protocols.py -q
Expected: pass.
Task 5: Route Broker Upstream Operations Through Stateful Runtime
Files:
-
Modify:
src/wf_mcp/broker/service/upstream_transport.py -
Modify:
tests/wf_mcp/service/test_upstream_transport.py -
Step 1: Add routing tests
Extend _StatefulRuntime fake in tests/wf_mcp/service/test_upstream_transport.py to implement the full operation surface.
Add tests proving that when stateful_runtime is configured:
invoke_method()uses runtime, not adapter;send_notification()uses runtime, not adapter;refresh_connection_catalog()uses runtime for discovery/listing operations;deployment_diagnostics()uses runtime for livelist_tools()checks.
Use an adapter fake that raises if called for the operation under test.
- Step 2: Add a local operation selector helper
In UpstreamTransportService, add a private helper:
def _operations_for(self, connection: ConnectionConfig) -> StatefulMcpRuntime | BackendAdapter:
if self.stateful_runtime is not None:
return self.stateful_runtime
return require_adapter(connection, self.adapters)
If basedpyright cannot narrow the union cleanly, keep explicit branches in each method instead of introducing a helper. Correctness is more important than DRY.
- Step 3: Update
invoke_method()andsend_notification()
Prefer stateful_runtime when present. Fall back to adapter when absent. Preserve event names and payloads.
- Step 4: Update
refresh_connection_catalog()
For discovery/listing, prefer stateful_runtime when present:
operations = self.stateful_runtime or require_adapter(connection, self.adapters)
capabilities = await discover_connection_capabilities(
connection=source_connection,
auth=auth,
adapter=operations,
)
This requires StatefulMcpRuntime to satisfy the same protocol as BackendAdapter.
- Step 5: Update
deployment_diagnostics()
Use stateful_runtime.list_tools() when present; otherwise adapter fallback.
- Step 6: Run upstream transport tests
Run:
uv run pytest tests/wf_mcp/service/test_upstream_transport.py tests/wf_mcp/service/test_content_access.py tests/wf_mcp/service/test_catalog.py -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-mcp-runtime-full-operation-surface.mdtodocs/historical/superpowers/plans/2026-06-08-mcp-runtime-full-operation-surface.md -
Step 1: Update roadmap
Under the MCP runtime/source cleanup section, add:
Persistent MCP runtime now implements the full upstream MCP operation
surface used by the one-shot adapter. Broker upstream operations prefer
the shared runtime pool when configured, with one-shot adapters retained
as fallback.
- Step 2: Update long-lived boundary spec
Add a completed numbered item:
24. Complete: `McpRuntimePool` implements the full MCP operation surface
(`list_tools`, resources, prompts, tools, raw methods, notifications, and
local metadata). Broker upstream operations prefer the persistent runtime
when configured and fall back to one-shot adapters.
Renumber pending items if needed.
- Step 3: Archive the plan
Run:
git mv docs/superpowers/plans/2026-06-08-mcp-runtime-full-operation-surface.md docs/historical/superpowers/plans/2026-06-08-mcp-runtime-full-operation-surface.md
Expected: plan moved 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/test_runtime.py tests/wf_sources_mcp/test_sdk_protocols.py tests/wf_sources_mcp/test_sdk_adapter.py tests/wf_mcp/service/test_upstream_transport.py tests/wf_mcp/service/test_content_access.py tests/wf_mcp/service/test_catalog.py -q
Expected: pass.
- Step 2: Run env-gated everything-server test if
.envhas settings
If .env contains MCP_EVERYTHING_COMMAND or MCP_EVERYTHING_URL, run:
uv run --env-file .env pytest tests/wf_mcp/test_sdk_adapter.py::test_mcp_sdk_adapter_can_probe_everything_server -q
Expected: pass or skip only for environment permission issues. Do not hide real failures.
- Step 3: Run lint
Run:
uv run ruff check src/wf_sources_mcp/runtime src/wf_sources_mcp/sdk src/wf_mcp/broker/service/upstream_transport.py tests/wf_sources_mcp/test_runtime.py tests/wf_sources_mcp/test_sdk_protocols.py tests/wf_mcp/service/test_upstream_transport.py
Expected: All checks passed!
- Step 4: Run typecheck
Run:
uv run basedpyright --level error src/wf_sources_mcp/runtime src/wf_sources_mcp/sdk src/wf_mcp/broker/service/upstream_transport.py tests/wf_sources_mcp/test_runtime.py tests/wf_sources_mcp/test_sdk_protocols.py tests/wf_mcp/service/test_upstream_transport.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
McpRuntimePoolsatisfies the full MCP operation protocol. - Confirmation that
UpstreamTransportServiceprefers stateful runtime for all MCP operations when configured. - Confirmation that
McpSdkAdapterremains the one-shot fallback. - Any deviations from this plan.
Do not claim "full suite passed" unless the full suite was actually run.