Files
lda-wf/docs/historical/superpowers/plans/2026-06-07-content-access-stateful-runtime-routing.md
T

19 KiB

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:

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:

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__:

__all__ = [
    "BackendAdapter",
    "StatefulMcpRuntime",
    "ToolCallResult",
    "ToolExecutor",
]
  • Step 3: Export protocol from package root

In src/wf_sources_mcp/sdk/__init__.py, import/export StatefulMcpRuntime:

from .protocols import BackendAdapter, StatefulMcpRuntime, ToolCallResult, ToolExecutor

and include "StatefulMcpRuntime" in __all__.

  • Step 4: Run protocol tests

Run:

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:

from wf_sources_mcp.sdk import BackendAdapter, StatefulMcpRuntime, ToolCallResult

__all__ = ["BackendAdapter", "StatefulMcpRuntime", "ToolCallResult"]

In src/wf_mcp/sdk/__init__.py, update imports and __all__:

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:

    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:

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:

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:

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:

from wf_sources_mcp.sdk import BackendAdapter, StatefulMcpRuntime, ToolExecutor

Add field to UpstreamTransportService:

    stateful_runtime: StatefulMcpRuntime | None = None
  • Step 3: Prefer stateful runtime for resource reads

In read_resource, replace:

        adapter = require_adapter(connection, self.adapters)
        auth = self.load_connection_auth(connection)
        # Compatibility boundary...
        source_connection = mcp_source_connection_from_connection_config(connection)

with:

        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:

        result = await adapter.read_resource(source_connection, auth, uri)

with:

        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:

        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:

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:

            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:

    stateful_runtime: StatefulMcpRuntime | None = None

Then:

        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:

    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:

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:

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:

   - 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:

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:

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:

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:

uv run basedpyright --level error src

Expected: 0 errors, 0 warnings, 0 notes.

  • Step 3: Run focused test typecheck

Run:

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:

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:

rg -n "stateful_runtime\\.(list_tools|list_resources|list_prompts)" src

Expected: no matches.

  • Step 6: Check whitespace

Run:

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.