docs: plan stateful runtime listing
This commit is contained in:
@@ -0,0 +1,638 @@
|
|||||||
|
# Stateful Runtime List Resources Prompts 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:** Complete the stateful MCP runtime's safe client surface by adding persistent `list_resources()` and `list_prompts()`, and split runtime protocols into tool/resource/prompt slices.
|
||||||
|
|
||||||
|
**Architecture:** `McpSourceClient` already wraps initialized MCP client operations for tools, resources, and prompts. `McpRuntimePool` should be able to route the same safe resource/prompt listing operations through its owner-task queue so session-scoped listings can observe runtime state. Protocols should describe the slices explicitly: tool execution, resource operations, prompt operations, and their combined stateful runtime.
|
||||||
|
|
||||||
|
**Tech Stack:** Python 3.14, MCP Python SDK, pytest/pytest-asyncio, ruff, basedpyright.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
- Modify `src/wf_sources_mcp/sdk/protocols.py`
|
||||||
|
- Add `ToolRuntime`, `ResourceRuntime`, `PromptRuntime`.
|
||||||
|
- Make `StatefulMcpRuntime(ToolRuntime, ResourceRuntime, PromptRuntime, Protocol)`.
|
||||||
|
- Keep `ToolExecutor` as a compatibility alias/subprotocol for workflow node execution.
|
||||||
|
- Modify `src/wf_sources_mcp/sdk/__init__.py`
|
||||||
|
- Export new protocol slices.
|
||||||
|
- Modify compatibility shims:
|
||||||
|
- `src/wf_mcp/sdk/base.py`
|
||||||
|
- `src/wf_mcp/sdk/__init__.py`
|
||||||
|
- Modify runtime:
|
||||||
|
- `src/wf_sources_mcp/runtime/session.py`
|
||||||
|
- `src/wf_sources_mcp/runtime/factory.py`
|
||||||
|
- `src/wf_sources_mcp/runtime/pool.py`
|
||||||
|
- Modify tests:
|
||||||
|
- `tests/wf_sources_mcp/test_sdk_protocols.py`
|
||||||
|
- `tests/wf_sources_mcp/test_runtime.py`
|
||||||
|
- `tests/wf_mcp/test_compat_imports.py`
|
||||||
|
- Update docs:
|
||||||
|
- `docs/current_roadmap.md`
|
||||||
|
- `docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md`
|
||||||
|
|
||||||
|
## Hard Boundaries
|
||||||
|
|
||||||
|
- Do not add raw `invoke_method` or `send_notification`.
|
||||||
|
- Do not add runtime `list_tools` in this slice.
|
||||||
|
- Do not use runtime `isinstance` checks or `@runtime_checkable` protocols.
|
||||||
|
- Do not route catalog refresh/discovery through stateful runtime in this slice.
|
||||||
|
- Do not assume resource/prompt listings are stateless; these new methods exist because they can be session-scoped.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Split Protocol Slices
|
||||||
|
|
||||||
|
**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 shape tests**
|
||||||
|
|
||||||
|
Append to `tests/wf_sources_mcp/test_sdk_protocols.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_stateful_runtime_protocol_slices_export() -> None:
|
||||||
|
from wf_sources_mcp.sdk import (
|
||||||
|
PromptRuntime,
|
||||||
|
ResourceRuntime,
|
||||||
|
StatefulMcpRuntime,
|
||||||
|
ToolExecutor,
|
||||||
|
ToolRuntime,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert ToolRuntime.__name__ == "ToolRuntime"
|
||||||
|
assert ResourceRuntime.__name__ == "ResourceRuntime"
|
||||||
|
assert PromptRuntime.__name__ == "PromptRuntime"
|
||||||
|
assert ToolExecutor.__name__ == "ToolExecutor"
|
||||||
|
assert StatefulMcpRuntime.__name__ == "StatefulMcpRuntime"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add protocol slices**
|
||||||
|
|
||||||
|
In `src/wf_sources_mcp/sdk/protocols.py`, replace the current `ToolExecutor` / `StatefulMcpRuntime` section with:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ToolRuntime(Protocol):
|
||||||
|
"""Runtime boundary for executing MCP tools from workflow nodes."""
|
||||||
|
|
||||||
|
async def call_tool(
|
||||||
|
self,
|
||||||
|
connection: McpSourceConnection,
|
||||||
|
auth: AuthRecord | None,
|
||||||
|
tool_name: str,
|
||||||
|
payload: dict[str, Any],
|
||||||
|
) -> ToolCallResult: ...
|
||||||
|
|
||||||
|
|
||||||
|
class ToolExecutor(ToolRuntime, Protocol):
|
||||||
|
"""Compatibility name for workflow-node tool execution."""
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceRuntime(Protocol):
|
||||||
|
"""Stateful resource operations for configured MCP sources."""
|
||||||
|
|
||||||
|
async def list_resources(
|
||||||
|
self,
|
||||||
|
connection: McpSourceConnection,
|
||||||
|
auth: AuthRecord | None,
|
||||||
|
) -> list[DiscoveredResource]: ...
|
||||||
|
|
||||||
|
async def read_resource(
|
||||||
|
self,
|
||||||
|
connection: McpSourceConnection,
|
||||||
|
auth: AuthRecord | None,
|
||||||
|
uri: str,
|
||||||
|
) -> dict[str, Any]: ...
|
||||||
|
|
||||||
|
|
||||||
|
class PromptRuntime(Protocol):
|
||||||
|
"""Stateful prompt operations for configured MCP sources."""
|
||||||
|
|
||||||
|
async def list_prompts(
|
||||||
|
self,
|
||||||
|
connection: McpSourceConnection,
|
||||||
|
auth: AuthRecord | None,
|
||||||
|
) -> list[DiscoveredPrompt]: ...
|
||||||
|
|
||||||
|
async def get_prompt(
|
||||||
|
self,
|
||||||
|
connection: McpSourceConnection,
|
||||||
|
auth: AuthRecord | None,
|
||||||
|
prompt_name: str,
|
||||||
|
arguments: dict[str, str] | None = None,
|
||||||
|
) -> dict[str, Any]: ...
|
||||||
|
|
||||||
|
|
||||||
|
class StatefulMcpRuntime(ToolRuntime, ResourceRuntime, PromptRuntime, Protocol):
|
||||||
|
"""Stateful execution/read/list boundary for configured MCP sources.
|
||||||
|
|
||||||
|
Implementations keep source session state across calls. Catalog refresh may
|
||||||
|
still use one-shot adapters by policy.
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `__all__`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
__all__ = [
|
||||||
|
"BackendAdapter",
|
||||||
|
"PromptRuntime",
|
||||||
|
"ResourceRuntime",
|
||||||
|
"StatefulMcpRuntime",
|
||||||
|
"ToolCallResult",
|
||||||
|
"ToolExecutor",
|
||||||
|
"ToolRuntime",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Export slices from package root**
|
||||||
|
|
||||||
|
In `src/wf_sources_mcp/sdk/__init__.py`, import/export the new symbols:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from .protocols import (
|
||||||
|
BackendAdapter,
|
||||||
|
PromptRuntime,
|
||||||
|
ResourceRuntime,
|
||||||
|
StatefulMcpRuntime,
|
||||||
|
ToolCallResult,
|
||||||
|
ToolExecutor,
|
||||||
|
ToolRuntime,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Include `"PromptRuntime"`, `"ResourceRuntime"`, and `"ToolRuntime"` 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 all protocol slices:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_sources_mcp.sdk import (
|
||||||
|
BackendAdapter,
|
||||||
|
PromptRuntime,
|
||||||
|
ResourceRuntime,
|
||||||
|
StatefulMcpRuntime,
|
||||||
|
ToolCallResult,
|
||||||
|
ToolRuntime,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"BackendAdapter",
|
||||||
|
"PromptRuntime",
|
||||||
|
"ResourceRuntime",
|
||||||
|
"StatefulMcpRuntime",
|
||||||
|
"ToolCallResult",
|
||||||
|
"ToolRuntime",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
In `src/wf_mcp/sdk/__init__.py`, update imports/exports similarly while keeping `McpSdkAdapter`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_sources_mcp.sdk import (
|
||||||
|
BackendAdapter,
|
||||||
|
McpSdkAdapter,
|
||||||
|
PromptRuntime,
|
||||||
|
ResourceRuntime,
|
||||||
|
StatefulMcpRuntime,
|
||||||
|
ToolCallResult,
|
||||||
|
ToolRuntime,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"BackendAdapter",
|
||||||
|
"McpSdkAdapter",
|
||||||
|
"PromptRuntime",
|
||||||
|
"ResourceRuntime",
|
||||||
|
"StatefulMcpRuntime",
|
||||||
|
"ToolCallResult",
|
||||||
|
"ToolRuntime",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **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 PromptRuntime as CompatPromptRuntime
|
||||||
|
from wf_mcp.sdk import ResourceRuntime as CompatResourceRuntime
|
||||||
|
from wf_mcp.sdk import ToolRuntime as CompatToolRuntime
|
||||||
|
from wf_mcp.sdk.base import PromptRuntime as CompatBasePromptRuntime
|
||||||
|
from wf_mcp.sdk.base import ResourceRuntime as CompatBaseResourceRuntime
|
||||||
|
from wf_mcp.sdk.base import ToolRuntime as CompatBaseToolRuntime
|
||||||
|
from wf_sources_mcp.sdk import PromptRuntime, ResourceRuntime, ToolRuntime
|
||||||
|
|
||||||
|
assert CompatPromptRuntime is PromptRuntime
|
||||||
|
assert CompatResourceRuntime is ResourceRuntime
|
||||||
|
assert CompatToolRuntime is ToolRuntime
|
||||||
|
assert CompatBasePromptRuntime is PromptRuntime
|
||||||
|
assert CompatBaseResourceRuntime is ResourceRuntime
|
||||||
|
assert CompatBaseToolRuntime is ToolRuntime
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run compatibility test**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_mcp/test_compat_imports.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: pass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Add Persistent Resource/Prompt Listing Methods
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/wf_sources_mcp/runtime/session.py`
|
||||||
|
- Modify: `src/wf_sources_mcp/runtime/factory.py`
|
||||||
|
- Modify: `src/wf_sources_mcp/runtime/pool.py`
|
||||||
|
- Modify: `tests/wf_sources_mcp/test_runtime.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Extend fake client with list methods**
|
||||||
|
|
||||||
|
In `tests/wf_sources_mcp/test_runtime.py`, add imports:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mcp.types import ListPromptsResult, ListResourcesResult, Prompt, Resource
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `_FakeClient` inside `_FakeFactory._create_with_stack()`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def list_resources(self) -> ListResourcesResult:
|
||||||
|
return ListResourcesResult(
|
||||||
|
resources=[
|
||||||
|
Resource(
|
||||||
|
uri=AnyUrl("fixture://docs/runtime"),
|
||||||
|
name="resource.runtime",
|
||||||
|
title="Runtime Resource",
|
||||||
|
description="Runtime-scoped resource.",
|
||||||
|
mimeType="text/plain",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
async def list_prompts(self) -> ListPromptsResult:
|
||||||
|
return ListPromptsResult(
|
||||||
|
prompts=[
|
||||||
|
Prompt(
|
||||||
|
name="prompt.runtime",
|
||||||
|
title="Runtime Prompt",
|
||||||
|
description="Runtime-scoped prompt.",
|
||||||
|
arguments=[],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add runtime tests**
|
||||||
|
|
||||||
|
Append to `tests/wf_sources_mcp/test_runtime.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_persistent_session_factory_routes_resource_and_prompt_lists() -> None:
|
||||||
|
factory = _FakeFactory()
|
||||||
|
session = await factory.create(_connection(), None)
|
||||||
|
|
||||||
|
resources = await session.list_resources()
|
||||||
|
prompts = await session.list_prompts()
|
||||||
|
await session.close()
|
||||||
|
|
||||||
|
assert resources[0].name == "resource.runtime"
|
||||||
|
assert resources[0].uri == "fixture://docs/runtime"
|
||||||
|
assert prompts[0].name == "prompt.runtime"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_runtime_pool_reuses_session_for_resource_and_prompt_lists() -> None:
|
||||||
|
factory = _FakeFactory()
|
||||||
|
pool = McpRuntimePool(factory.create)
|
||||||
|
connection = _connection()
|
||||||
|
|
||||||
|
resources = await pool.list_resources(connection, None)
|
||||||
|
prompts = await pool.list_prompts(connection, None)
|
||||||
|
await pool.close_all()
|
||||||
|
|
||||||
|
assert resources[0].name == "resource.runtime"
|
||||||
|
assert prompts[0].name == "prompt.runtime"
|
||||||
|
assert factory.created_connections == [connection]
|
||||||
|
```
|
||||||
|
|
||||||
|
Update public surface test:
|
||||||
|
|
||||||
|
```python
|
||||||
|
assert "list_resources" in public_operations
|
||||||
|
assert "list_prompts" in public_operations
|
||||||
|
assert "invoke_method" not in public_operations
|
||||||
|
assert "send_notification" not in public_operations
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add session callback types and methods**
|
||||||
|
|
||||||
|
In `src/wf_sources_mcp/runtime/session.py`, import DTOs:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_sources_mcp.catalog import DiscoveredPrompt, DiscoveredResource
|
||||||
|
```
|
||||||
|
|
||||||
|
Add callback types:
|
||||||
|
|
||||||
|
```python
|
||||||
|
RawResourceLister = Callable[[], Awaitable[list[DiscoveredResource]]]
|
||||||
|
RawPromptLister = Callable[[], Awaitable[list[DiscoveredPrompt]]]
|
||||||
|
```
|
||||||
|
|
||||||
|
Add fields:
|
||||||
|
|
||||||
|
```python
|
||||||
|
list_resources_callback: RawResourceLister | None = None
|
||||||
|
list_prompts_callback: RawPromptLister | None = None
|
||||||
|
```
|
||||||
|
|
||||||
|
Add methods:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def list_resources(self) -> list[DiscoveredResource]:
|
||||||
|
"""List MCP resources through the owner task or injected session."""
|
||||||
|
if self.list_resources_callback is not None:
|
||||||
|
return await self.list_resources_callback()
|
||||||
|
if self.client is not None:
|
||||||
|
from wf_sources_mcp.sdk.converters import resource_to_discovered
|
||||||
|
|
||||||
|
result = await self.client.list_resources()
|
||||||
|
return [resource_to_discovered(resource) for resource in result.resources]
|
||||||
|
raise RuntimeError("persistent MCP session has no resource list transport")
|
||||||
|
|
||||||
|
async def list_prompts(self) -> list[DiscoveredPrompt]:
|
||||||
|
"""List MCP prompts through the owner task or injected session."""
|
||||||
|
if self.list_prompts_callback is not None:
|
||||||
|
return await self.list_prompts_callback()
|
||||||
|
if self.client is not None:
|
||||||
|
from wf_sources_mcp.sdk.converters import prompt_to_discovered
|
||||||
|
|
||||||
|
result = await self.client.list_prompts()
|
||||||
|
return [prompt_to_discovered(prompt) for prompt in result.prompts]
|
||||||
|
raise RuntimeError("persistent MCP session has no prompt list transport")
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Wire owner methods**
|
||||||
|
|
||||||
|
In `src/wf_sources_mcp/runtime/factory.py`, add callbacks in `PersistentSessionFactory.create()`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
list_resources_callback=owner.list_resources,
|
||||||
|
list_prompts_callback=owner.list_prompts,
|
||||||
|
```
|
||||||
|
|
||||||
|
Add owner methods:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def list_resources(self) -> list[DiscoveredResource]:
|
||||||
|
"""Submit resource listing through the generic owner-task operation queue."""
|
||||||
|
return await self.submit(
|
||||||
|
operation="list_resources",
|
||||||
|
run=lambda client: client.list_resources(),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def list_prompts(self) -> list[DiscoveredPrompt]:
|
||||||
|
"""Submit prompt listing through the generic owner-task operation queue."""
|
||||||
|
return await self.submit(
|
||||||
|
operation="list_prompts",
|
||||||
|
run=lambda client: client.list_prompts(),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Import DTOs:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_sources_mcp.catalog import DiscoveredPrompt, DiscoveredResource
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Add pool methods**
|
||||||
|
|
||||||
|
In `src/wf_sources_mcp/runtime/pool.py`, import DTOs:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_sources_mcp.catalog import DiscoveredPrompt, DiscoveredResource
|
||||||
|
```
|
||||||
|
|
||||||
|
Add below `get_prompt()`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def list_resources(
|
||||||
|
self,
|
||||||
|
connection: McpSourceConnection,
|
||||||
|
auth: AuthRecord | None,
|
||||||
|
) -> list[DiscoveredResource]:
|
||||||
|
session = await self.get_session(connection, auth)
|
||||||
|
return await session.list_resources()
|
||||||
|
|
||||||
|
async def list_prompts(
|
||||||
|
self,
|
||||||
|
connection: McpSourceConnection,
|
||||||
|
auth: AuthRecord | None,
|
||||||
|
) -> list[DiscoveredPrompt]:
|
||||||
|
session = await self.get_session(connection, auth)
|
||||||
|
return await session.list_prompts()
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: Run runtime tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_sources_mcp/test_runtime.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: pass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Static Protocol Conformance Tests
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `tests/wf_sources_mcp/test_runtime.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add assignment-based type conformance test**
|
||||||
|
|
||||||
|
Append:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_runtime_pool_satisfies_stateful_protocol_static_shape() -> None:
|
||||||
|
from wf_sources_mcp.sdk import (
|
||||||
|
PromptRuntime,
|
||||||
|
ResourceRuntime,
|
||||||
|
StatefulMcpRuntime,
|
||||||
|
ToolRuntime,
|
||||||
|
)
|
||||||
|
|
||||||
|
factory = _FakeFactory()
|
||||||
|
pool = McpRuntimePool(factory.create)
|
||||||
|
|
||||||
|
tool_runtime: ToolRuntime = pool
|
||||||
|
resource_runtime: ResourceRuntime = pool
|
||||||
|
prompt_runtime: PromptRuntime = pool
|
||||||
|
stateful_runtime: StatefulMcpRuntime = pool
|
||||||
|
|
||||||
|
assert tool_runtime is pool
|
||||||
|
assert resource_runtime is pool
|
||||||
|
assert prompt_runtime is pool
|
||||||
|
assert stateful_runtime is pool
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a static typecheck target. Do not add `isinstance` runtime checks.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run focused typecheck**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run basedpyright --level error tests/wf_sources_mcp/test_runtime.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `0 errors, 0 warnings, 0 notes`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: 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 after completion: `docs/superpowers/plans/2026-06-07-stateful-runtime-list-resources-prompts.md` to `docs/historical/superpowers/plans/2026-06-07-stateful-runtime-list-resources-prompts.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update roadmap**
|
||||||
|
|
||||||
|
In `docs/current_roadmap.md`, under the MCP upstream source runtime cleanup bullets, add:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- Completed: stateful MCP runtime now has protocol slices for tools,
|
||||||
|
resources, and prompts, and can route session-scoped `list_resources` and
|
||||||
|
`list_prompts` through the owner task. Catalog refresh still uses one-shot
|
||||||
|
adapter 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 content-access routing item:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
14. Complete: stateful MCP runtime protocols split into tool/resource/prompt
|
||||||
|
slices. Runtime can route `list_resources` and `list_prompts` through the
|
||||||
|
owner task for session-scoped listings; catalog refresh 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-stateful-runtime-list-resources-prompts.md docs/historical/superpowers/plans/2026-06-07-stateful-runtime-list-resources-prompts.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/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_sources_mcp/test_runtime.py tests/wf_sources_mcp/test_sdk_protocols.py tests/wf_mcp/test_compat_imports.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_sources_mcp/runtime src/wf_mcp/sdk tests/wf_sources_mcp/test_runtime.py tests/wf_sources_mcp/test_sdk_protocols.py tests/wf_mcp/test_compat_imports.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `All checks passed!`
|
||||||
|
|
||||||
|
- [ ] **Step 5: Check raw runtime methods remain absent**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n "def (invoke_method|send_notification)" src/wf_sources_mcp/runtime
|
||||||
|
```
|
||||||
|
|
||||||
|
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 adds session-scoped resource/prompt listings and splits protocols by capability without expanding raw MCP method support.
|
||||||
|
- Placeholder scan: No placeholder steps remain.
|
||||||
|
- Type consistency: resource/prompt list methods return `list[DiscoveredResource]` and `list[DiscoveredPrompt]`.
|
||||||
|
- Policy boundary: catalog refresh/discovery policy remains separate and one-shot.
|
||||||
|
|
||||||
Reference in New Issue
Block a user