# Content Access Stateful Runtime Routing Implementation Plan > **Historical:** This plan has been implemented. The `StatefulMcpRuntime` protocol > is in `wf_sources_mcp.sdk`, upstream transport prefers it for content reads, > and `WfMcpService` wires the configured runtime pool as both `tool_executor` > and `stateful_runtime`. > **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:** Route broker content reads (`read_resource`, `render_prompt`) through a stateful MCP runtime when one is configured, while preserving one-shot adapter fallback. **Architecture:** Config-built services already pass `McpRuntimePool` as `tool_executor`; that runtime now supports `call_tool`, `read_resource`, and `get_prompt`. This slice makes the policy explicit with a `StatefulMcpRuntime` protocol instead of ad-hoc `hasattr` or string dispatch. Discovery/catalog refresh remains one-shot adapter based; content access prefers stateful runtime and falls back to the adapter only when no stateful runtime is configured. **Tech Stack:** Python 3.14, MCP Python SDK, pytest/pytest-asyncio, ruff, basedpyright. --- ## File Structure - Modify `src/wf_sources_mcp/sdk/protocols.py` - Add `StatefulMcpRuntime` protocol with `call_tool`, `read_resource`, `get_prompt`. - Modify `src/wf_sources_mcp/sdk/__init__.py` - Export `StatefulMcpRuntime`. - Modify `src/wf_mcp/sdk/base.py` - Re-export `StatefulMcpRuntime` through compatibility shim. - Modify `src/wf_mcp/sdk/__init__.py` - Re-export `StatefulMcpRuntime` through old package. - Modify `src/wf_mcp/broker/service/upstream_transport.py` - Add `stateful_runtime: StatefulMcpRuntime | None`. - Keep `tool_executor` compatibility but derive stateful runtime from it when possible. - Prefer stateful runtime in `read_resource` and `render_prompt`. - Keep adapter fallback. - Modify `src/wf_mcp/broker/service/core.py` - Pass the configured runtime pool as both `tool_executor` and `stateful_runtime` where available. - Modify `src/wf_mcp/broker/config.py` - If construction currently passes only `tool_executor=McpRuntimePool(...)`, update service construction if needed. - Add/modify tests: - `tests/wf_sources_mcp/test_sdk_protocols.py` - `tests/wf_mcp/test_compat_imports.py` - `tests/wf_mcp/service/test_upstream_transport.py` - `tests/wf_mcp/service/test_content_access.py` ## Hard Boundaries - Do not route catalog refresh/discovery listing through stateful runtime in this slice. - Do not use `getattr(runtime, "read_resource")` dispatch. - Do not remove adapter fallback. - Do not add runtime raw method or notification support. - Do not assume `list_resources` / `list_prompts` are stateless. --- ### Task 1: Add `StatefulMcpRuntime` Protocol **Files:** - Modify: `src/wf_sources_mcp/sdk/protocols.py` - Modify: `src/wf_sources_mcp/sdk/__init__.py` - Modify: `tests/wf_sources_mcp/test_sdk_protocols.py` - [ ] **Step 1: Add protocol test** Append to `tests/wf_sources_mcp/test_sdk_protocols.py`: ```python def test_stateful_mcp_runtime_protocol_shape() -> None: from wf_sources_mcp.sdk import StatefulMcpRuntime, ToolExecutor assert StatefulMcpRuntime.__name__ == "StatefulMcpRuntime" assert ToolExecutor.__name__ == "ToolExecutor" ``` - [ ] **Step 2: Add the protocol** In `src/wf_sources_mcp/sdk/protocols.py`, add after `ToolExecutor`: ```python class StatefulMcpRuntime(ToolExecutor, Protocol): """Stateful execution/read boundary for configured MCP sources. Implementations keep source session state across calls. Discovery/catalog refresh may still use one-shot adapters by policy. """ async def read_resource( self, connection: McpSourceConnection, auth: AuthRecord | None, uri: str, ) -> dict[str, Any]: ... async def get_prompt( self, connection: McpSourceConnection, auth: AuthRecord | None, prompt_name: str, arguments: dict[str, str] | None = None, ) -> dict[str, Any]: ... ``` Update `__all__`: ```python __all__ = [ "BackendAdapter", "StatefulMcpRuntime", "ToolCallResult", "ToolExecutor", ] ``` - [ ] **Step 3: Export protocol from package root** In `src/wf_sources_mcp/sdk/__init__.py`, import/export `StatefulMcpRuntime`: ```python from .protocols import BackendAdapter, StatefulMcpRuntime, ToolCallResult, ToolExecutor ``` and include `"StatefulMcpRuntime"` in `__all__`. - [ ] **Step 4: Run protocol tests** Run: ```bash uv run pytest tests/wf_sources_mcp/test_sdk_protocols.py -q ``` Expected: pass. --- ### Task 2: Preserve Compatibility Exports **Files:** - Modify: `src/wf_mcp/sdk/base.py` - Modify: `src/wf_mcp/sdk/__init__.py` - Modify: `tests/wf_mcp/test_compat_imports.py` - [ ] **Step 1: Update compatibility shims** In `src/wf_mcp/sdk/base.py`, re-export `StatefulMcpRuntime` with the existing protocol exports: ```python from wf_sources_mcp.sdk import BackendAdapter, StatefulMcpRuntime, ToolCallResult __all__ = ["BackendAdapter", "StatefulMcpRuntime", "ToolCallResult"] ``` In `src/wf_mcp/sdk/__init__.py`, update imports and `__all__`: ```python from wf_sources_mcp.sdk import ( BackendAdapter, McpSdkAdapter, StatefulMcpRuntime, ToolCallResult, ) __all__ = ["BackendAdapter", "McpSdkAdapter", "StatefulMcpRuntime", "ToolCallResult"] ``` - [ ] **Step 2: Extend compatibility test** In `tests/wf_mcp/test_compat_imports.py`, extend `test_wf_mcp_sdk_protocol_shims_reexport_wf_sources_mcp_sdk`: ```python from wf_mcp.sdk import StatefulMcpRuntime as CompatStatefulMcpRuntime from wf_mcp.sdk.base import StatefulMcpRuntime as CompatBaseStatefulMcpRuntime from wf_sources_mcp.sdk import StatefulMcpRuntime assert CompatStatefulMcpRuntime is StatefulMcpRuntime assert CompatBaseStatefulMcpRuntime is StatefulMcpRuntime ``` - [ ] **Step 3: Run compatibility test** Run: ```bash uv run pytest tests/wf_mcp/test_compat_imports.py -q ``` Expected: pass. --- ### Task 3: Add Stateful Runtime Routing to Upstream Transport **Files:** - Modify: `src/wf_mcp/broker/service/upstream_transport.py` - Modify: `tests/wf_mcp/service/test_upstream_transport.py` - [ ] **Step 1: Add fake stateful runtime and adapter tests** Append to `tests/wf_mcp/service/test_upstream_transport.py`: ```python class _StatefulRuntime: def __init__(self) -> None: self.resources: list[tuple[str, str]] = [] self.prompts: list[tuple[str, str, dict[str, str] | None]] = [] async def call_tool(self, connection, auth, tool_name, payload): raise AssertionError("not used by these tests") async def read_resource(self, connection, auth, uri: str): self.resources.append((connection.id, uri)) return {"contents": [{"uri": uri, "text": "stateful resource"}]} async def get_prompt( self, connection, auth, prompt_name: str, arguments: dict[str, str] | None = None, ): self.prompts.append((connection.id, prompt_name, arguments)) return { "messages": [ { "role": "user", "content": {"type": "text", "text": "stateful prompt"}, } ] } class _ExplodingContentAdapter(FakeAdapter): async def read_resource(self, connection, auth, uri): raise AssertionError("adapter read_resource should not be used") async def get_prompt(self, connection, auth, prompt_name, arguments=None): raise AssertionError("adapter get_prompt should not be used") ``` Add tests: ```python async def test_upstream_transport_prefers_stateful_runtime_for_resource_reads( tmp_path: Path, ) -> None: events: list[McpEvent] = [] runtime = _StatefulRuntime() transport = UpstreamTransportService( auth_store=FileStore(tmp_path), catalog_store=FileStore(tmp_path), event_sink=events.append, stateful_runtime=runtime, ) transport.register_adapter("demo", _ExplodingContentAdapter()) connection = ConnectionConfig( id="demo.personal", server="demo", account="personal", metadata=_fake_transport_metadata(), ) result = await transport.read_resource( connection, "demo.personal.resource.welcome", "fixture://docs/welcome", ) assert result["contents"][0]["text"] == "stateful resource" assert runtime.resources == [("demo.personal", "fixture://docs/welcome")] assert [event.kind for event in events] == [ "resource_read_started", "resource_read_completed", ] async def test_upstream_transport_prefers_stateful_runtime_for_prompts( tmp_path: Path, ) -> None: events: list[McpEvent] = [] runtime = _StatefulRuntime() transport = UpstreamTransportService( auth_store=FileStore(tmp_path), catalog_store=FileStore(tmp_path), event_sink=events.append, stateful_runtime=runtime, ) transport.register_adapter("demo", _ExplodingContentAdapter()) connection = ConnectionConfig( id="demo.personal", server="demo", account="personal", metadata=_fake_transport_metadata(), ) result = await transport.render_prompt( connection, "demo.personal.prompt.summarize", "prompt.summarize", {"text": "hello"}, ) assert result["messages"][0]["content"]["text"] == "stateful prompt" assert runtime.prompts == [ ("demo.personal", "prompt.summarize", {"text": "hello"}) ] assert [event.kind for event in events] == [ "prompt_get_started", "prompt_get_completed", ] ``` - [ ] **Step 2: Add `stateful_runtime` field and import protocol** In `src/wf_mcp/broker/service/upstream_transport.py`, change import: ```python from wf_sources_mcp.sdk import BackendAdapter, StatefulMcpRuntime, ToolExecutor ``` Add field to `UpstreamTransportService`: ```python stateful_runtime: StatefulMcpRuntime | None = None ``` - [ ] **Step 3: Prefer stateful runtime for resource reads** In `read_resource`, replace: ```python adapter = require_adapter(connection, self.adapters) auth = self.load_connection_auth(connection) # Compatibility boundary... source_connection = mcp_source_connection_from_connection_config(connection) ``` with: ```python auth = self.load_connection_auth(connection) # Compatibility boundary: broker callers still pass ConnectionConfig. source_connection = mcp_source_connection_from_connection_config(connection) ``` Then replace the execution line: ```python result = await adapter.read_resource(source_connection, auth, uri) ``` with: ```python if self.stateful_runtime is not None: result = await self.stateful_runtime.read_resource( source_connection, auth, uri, ) else: adapter = require_adapter(connection, self.adapters) result = await adapter.read_resource(source_connection, auth, uri) ``` - [ ] **Step 4: Prefer stateful runtime for prompt gets** In `render_prompt`, remove early adapter lookup and replace execution: ```python if self.stateful_runtime is not None: result = await self.stateful_runtime.get_prompt( source_connection, auth, local_name, arguments, ) else: adapter = require_adapter(connection, self.adapters) result = await adapter.get_prompt(source_connection, auth, local_name, arguments) ``` Keep existing start/completed events unchanged. - [ ] **Step 5: Run upstream transport tests** Run: ```bash uv run pytest tests/wf_mcp/service/test_upstream_transport.py -q ``` Expected: pass. --- ### Task 4: Wire Configured Runtime as Stateful Runtime **Files:** - Modify: `src/wf_mcp/broker/service/core.py` - Modify: `src/wf_mcp/broker/config.py` if needed. - Modify: `tests/wf_mcp/service/test_content_access.py` - [ ] **Step 1: Wire service field** In `src/wf_mcp/broker/service/core.py`, when constructing `UpstreamTransportService`, pass: ```python stateful_runtime=self.tool_executor if isinstance(self.tool_executor, object) else None, ``` Do not use that exact `isinstance(..., object)` if basedpyright dislikes it. Prefer a simple assignment after adding a `stateful_runtime` field to `WfMcpService` if needed: ```python stateful_runtime: StatefulMcpRuntime | None = None ``` Then: ```python stateful_runtime = self.stateful_runtime self.upstream = UpstreamTransportService( auth_store=auth_store, catalog_store=catalog_store, event_sink=self.events.record_event, tool_executor=self.tool_executor, stateful_runtime=stateful_runtime, ) ``` For config-built services that construct `McpRuntimePool`, set both: ```python runtime_pool = McpRuntimePool(runtime_factory.create) service = WfMcpService( ..., tool_executor=runtime_pool, stateful_runtime=runtime_pool, ) ``` If current `build_service_from_config` already constructs the pool inline, refactor to a local `runtime_pool` variable. - [ ] **Step 2: Add content access service test with stateful runtime** Append to `tests/wf_mcp/service/test_content_access.py`: ```python class _StatefulRuntime: def __init__(self) -> None: self.resources: list[str] = [] self.prompts: list[str] = [] async def call_tool(self, connection, auth, tool_name, payload): raise AssertionError("not used") async def read_resource(self, connection, auth, uri: str): self.resources.append(uri) return {"contents": [{"uri": uri, "text": "stateful resource"}]} async def get_prompt(self, connection, auth, prompt_name, arguments=None): self.prompts.append(prompt_name) return { "messages": [ { "role": "user", "content": {"type": "text", "text": "stateful prompt"}, } ] } async def test_content_access_uses_stateful_runtime_for_upstream_content() -> None: runtime = _StatefulRuntime() service = WfMcpService( store=FileStore(local_temp_root() / "content_stateful_runtime"), tool_executor=runtime, stateful_runtime=runtime, ) service.register_connection( ConnectionConfig( id="demo.personal", server="demo", account="personal", metadata={"transport": "stdio", "command": "fake-mcp-server"}, ) ) service.register_adapter("demo", FakeAdapter()) await service.refresh_connection_catalog("demo.personal") resource = await service.content_access.read_resource( "demo.personal.resource.welcome" ) prompt = await service.content_access.render_prompt( "demo.personal.prompt.summarize", arguments={"text": "hello"}, ) assert resource["contents"][0]["text"] == "stateful resource" assert prompt["messages"][0]["content"]["text"] == "stateful prompt" assert runtime.resources == ["fixture://resource/welcome"] assert runtime.prompts == ["prompt.summarize"] ``` Adjust expected URI if `FakeAdapter` catalog fixture uses a different resource URI; read it from the failure and keep the exact catalog URI. - [ ] **Step 3: Run content tests** Run: ```bash uv run pytest tests/wf_mcp/service/test_content_access.py tests/wf_mcp/service/test_upstream_transport.py -q ``` Expected: pass. --- ### Task 5: Docs and Plan Archive **Files:** - Modify: `docs/current_roadmap.md` - Modify: `docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md` - Move after completion: `docs/superpowers/plans/2026-06-07-content-access-stateful-runtime-routing.md` to `docs/historical/superpowers/plans/2026-06-07-content-access-stateful-runtime-routing.md` - [ ] **Step 1: Update roadmap** In `docs/current_roadmap.md`, under the MCP upstream source runtime cleanup bullets, add: ```markdown - Completed: broker content access now prefers a configured stateful MCP runtime for `read_resource` and `get_prompt`, with one-shot adapter fallback. Catalog refresh/discovery remains one-shot by policy. ``` - [ ] **Step 2: Update long-lived API boundary spec** In `docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md`, add after the prompt runtime item: ```markdown 13. Complete: broker content access now prefers configured `StatefulMcpRuntime` for resource and prompt reads, falling back to the one-shot adapter when no stateful runtime is configured. Catalog refresh/discovery remains one-shot. ``` Renumber following item if needed. - [ ] **Step 3: Archive completed plan after implementation** Run only after all code/tests pass: ```bash git mv docs/superpowers/plans/2026-06-07-content-access-stateful-runtime-routing.md docs/historical/superpowers/plans/2026-06-07-content-access-stateful-runtime-routing.md ``` --- ### Task 6: Final Verification **Files:** - All changed files. - [ ] **Step 1: Run focused tests** Run: ```bash uv run pytest tests/wf_sources_mcp tests/wf_mcp/service/test_upstream_transport.py tests/wf_mcp/service/test_content_access.py tests/wf_mcp/test_compat_imports.py -q ``` Expected: pass. - [ ] **Step 2: Run source typecheck** Run: ```bash uv run basedpyright --level error src ``` Expected: `0 errors, 0 warnings, 0 notes`. - [ ] **Step 3: Run focused test typecheck** Run: ```bash uv run basedpyright --level error tests/wf_mcp/service/test_upstream_transport.py tests/wf_mcp/service/test_content_access.py tests/wf_sources_mcp/test_sdk_protocols.py ``` Expected: `0 errors, 0 warnings, 0 notes`. - [ ] **Step 4: Run focused lint** Run: ```bash uv run ruff check src/wf_sources_mcp/sdk src/wf_mcp/sdk src/wf_mcp/broker/service/upstream_transport.py src/wf_mcp/broker/service/core.py tests/wf_mcp/service/test_upstream_transport.py tests/wf_mcp/service/test_content_access.py ``` Expected: `All checks passed!` - [ ] **Step 5: Check no discovery routing changed** Run: ```bash rg -n "stateful_runtime\\.(list_tools|list_resources|list_prompts)" src ``` Expected: no matches. - [ ] **Step 6: Check whitespace** Run: ```bash git diff --check ``` Expected: no whitespace errors. CRLF warnings are acceptable on Windows. --- ## Self-Review - Spec coverage: The plan makes stateful-vs-one-shot routing explicit and only for content reads. - Placeholder scan: No placeholder steps remain. - Type consistency: `StatefulMcpRuntime` is a protocol satisfied by `McpRuntimePool`. - Policy boundary: discovery/catalog refresh still uses one-shot adapter; no assumption that resource/prompt listing is stateless.