the Stores

This commit is contained in:
lda
2026-06-02 11:03:05 +07:00 Verified
parent 10eaaec737
commit 09995d86d5
20 changed files with 707 additions and 68 deletions
+6 -5
View File
@@ -112,11 +112,12 @@ implementation state.
inventory and deployment metadata instead of reverse-engineering MCP tools. inventory and deployment metadata instead of reverse-engineering MCP tools.
- **Workflow API extraction**: continue the staged extraction in - **Workflow API extraction**: continue the staged extraction in
[wf_api extraction roadmap](./superpowers/plans/2026-06-01-wf-api-extraction-roadmap.md). [wf_api extraction roadmap](./superpowers/plans/2026-06-01-wf-api-extraction-roadmap.md).
The next useful slice is scaffolding a protocol-neutral operation context for Protocol-neutral operation context and domain services now exist behind
stores, capability sources, event recording, and live source calls. Only after `wf_api`; MCP tool schemas and tool registration stay in `wf_mcp`.
that seam exists should the large `WorkflowSurfaceHandlers` implementation be - Workflow store ownership is explicit: entrypoints construct/inject `WorkflowStores`; `WfMcpService` no longer guesses stores from the MCP store root.
split by domain behind `wf_api`; MCP tool schemas and tool registration stay - The next useful slice is removing the remaining double-delegation path so
in `wf_mcp`. `WorkflowApi` composes domain services directly instead of routing through
`WfMcpWorkflowApiBackend` and `WorkflowSurfaceHandlers`.
Frame stress points remaining for native subgraphs and future fork/gather: Frame stress points remaining for native subgraphs and future fork/gather:
@@ -0,0 +1,505 @@
# wf_api Store Ownership 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 workflow artifact, draft workspace, and run store ownership explicit so `wf_api`, CLI, MCP, and future HTTP entrypoints can share stores without relying on `WfMcpService.__post_init__`.
**Architecture:** Add a protocol-neutral `WorkflowStores` bundle in `wf_api` that groups the three workflow stores. MCP config construction remains responsible for creating file-backed stores from `BrokerConfig.store_root`; `WfMcpService` receives stores but no longer manufactures them from its MCP `Store`. Existing process-local behavior stays intact through `build_service_from_config`.
**Tech Stack:** Python 3.14, dataclasses, `wf_api`, `wf_artifacts`, `wf_mcp`, pytest, ruff, basedpyright.
---
## Current Problem
`src/wf_mcp/broker/service/core.py` currently does this in `WfMcpService.__post_init__`:
```python
if self.artifact_store is None:
self.artifact_store = FileWorkflowArtifactStore(_store_root(self.store))
if self.draft_workspace_store is None:
self.draft_workspace_store = FileDraftWorkspaceStore(_store_root(self.store))
if self.run_store is None:
self.run_store = FileRunStore(_store_root(self.store))
```
That makes a protocol-specific service decide protocol-neutral workflow persistence. It also hides missing-store tests because `WfMcpService(store=FileStore(...))` silently creates workflow stores.
`src/wf_mcp/broker/config.py::build_service_from_config` already does the right thing by passing all three stores explicitly. This slice preserves that behavior and removes the fallback.
## Target Ownership Rule
- `wf_artifacts` owns store protocols and file store implementations.
- `wf_api` may group protocol-neutral workflow stores into a small DTO.
- `wf_mcp` owns MCP config loading and calls the DTO factory for file-backed process-local stores.
- `WfMcpService` owns broker state, connections, adapters, source catalogs, events, and execution wiring.
- `WfMcpService` does not create workflow artifact/draft/run stores by guessing from the MCP catalog/auth store.
## Files
- Create: `src/wf_api/stores.py`
- Modify: `src/wf_api/__init__.py`
- Modify: `src/wf_mcp/broker/config.py`
- Modify: `src/wf_mcp/broker/service/core.py`
- Modify: direct `WfMcpService(...)` tests only where they rely on implicit workflow stores
- Test: `tests/wf_api/test_stores.py`
- Test: `tests/wf_mcp/service/test_catalog.py`
- Test: `tests/wf_mcp/test_broker_server.py`
---
### Task 1: Add Protocol-Neutral Store Bundle
**Files:**
- Create: `src/wf_api/stores.py`
- Modify: `src/wf_api/__init__.py`
- Test: `tests/wf_api/test_stores.py`
- [ ] **Step 1: Write failing store bundle tests**
Create `tests/wf_api/test_stores.py`:
```python
from __future__ import annotations
from wf_api.stores import WorkflowStores, file_workflow_stores
from wf_artifacts import (
FileDraftWorkspaceStore,
FileRunStore,
FileWorkflowArtifactStore,
)
from tests.wf_mcp.test_support import local_temp_root
def test_file_workflow_stores_constructs_all_three_file_stores() -> None:
root = local_temp_root() / "wf_api_file_workflow_stores"
stores = file_workflow_stores(root)
assert isinstance(stores, WorkflowStores)
assert isinstance(stores.artifact_store, FileWorkflowArtifactStore)
assert isinstance(stores.draft_workspace_store, FileDraftWorkspaceStore)
assert isinstance(stores.run_store, FileRunStore)
assert stores.artifact_store.root == root
assert stores.draft_workspace_store.root == root
assert stores.run_store.root == root
def test_wf_api_exports_workflow_stores() -> None:
from wf_api import WorkflowStores as ExportedWorkflowStores
from wf_api import file_workflow_stores as exported_file_workflow_stores
assert ExportedWorkflowStores is WorkflowStores
assert exported_file_workflow_stores is file_workflow_stores
```
- [ ] **Step 2: Run failing tests**
Run:
```bash
uv run pytest tests\wf_api\test_stores.py -q
```
Expected: import failure for `wf_api.stores`.
- [ ] **Step 3: Implement `wf_api.stores`**
Create `src/wf_api/stores.py`:
```python
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from wf_artifacts import (
DraftWorkspaceStore,
FileDraftWorkspaceStore,
FileRunStore,
FileWorkflowArtifactStore,
RunStore,
WorkflowArtifactStore,
)
@dataclass(frozen=True, slots=True)
class WorkflowStores:
"""Protocol-neutral persistence dependencies for workflow APIs."""
artifact_store: WorkflowArtifactStore
draft_workspace_store: DraftWorkspaceStore
run_store: RunStore
def file_workflow_stores(root: str | Path) -> WorkflowStores:
"""Create process-local file-backed workflow stores under one root."""
store_root = Path(root)
return WorkflowStores(
artifact_store=FileWorkflowArtifactStore(store_root),
draft_workspace_store=FileDraftWorkspaceStore(store_root),
run_store=FileRunStore(store_root),
)
__all__ = ["WorkflowStores", "file_workflow_stores"]
```
- [ ] **Step 4: Export from `wf_api`**
Update `src/wf_api/__init__.py`:
```python
from .stores import WorkflowStores, file_workflow_stores
```
Add both names to `__all__`.
- [ ] **Step 5: Verify Task 1**
Run:
```bash
uv run pytest tests\wf_api\test_stores.py -q
uv run ruff check src\wf_api\stores.py tests\wf_api\test_stores.py
uv run ruff format --check src\wf_api\stores.py tests\wf_api\test_stores.py
```
Expected: tests pass, lint pass, format pass.
---
### Task 2: Move Config Store Construction Through the Bundle
**Files:**
- Modify: `src/wf_mcp/broker/config.py`
- Test: `tests/wf_mcp/test_broker_server.py`
- [ ] **Step 1: Strengthen config construction test**
Find `tests/wf_mcp/test_broker_server.py::test_build_service_from_config_uses_store_root_for_artifacts`.
Update it to assert all three stores use the configured root:
```python
def test_build_service_from_config_uses_store_root_for_workflow_stores() -> None:
store_root = local_temp_root() / "broker_config_workflow_stores"
config = BrokerConfig(store_root=store_root, connections=[])
service = build_service_from_config(config)
assert isinstance(service.artifact_store, FileWorkflowArtifactStore)
assert isinstance(service.draft_workspace_store, FileDraftWorkspaceStore)
assert isinstance(service.run_store, FileRunStore)
assert service.artifact_store.root == store_root
assert service.draft_workspace_store.root == store_root
assert service.run_store.root == store_root
```
Ensure the test imports:
```python
from wf_artifacts import FileDraftWorkspaceStore, FileRunStore, FileWorkflowArtifactStore
```
- [ ] **Step 2: Run the focused test**
Run:
```bash
uv run pytest tests\wf_mcp\test_broker_server.py::test_build_service_from_config_uses_store_root_for_workflow_stores -q
```
Expected: pass before the config refactor, proving current behavior is covered.
- [ ] **Step 3: Update `build_service_from_config` to use `file_workflow_stores`**
In `src/wf_mcp/broker/config.py`, replace direct file store imports:
```python
from wf_api import file_workflow_stores
```
Remove:
```python
from wf_artifacts import (
FileDraftWorkspaceStore,
FileRunStore,
FileWorkflowArtifactStore,
)
```
Then update `build_service_from_config`:
```python
def build_service_from_config(config: BrokerConfig) -> WfMcpService:
"""Create a broker service with SDK adapters for configured connections."""
runtime_factory = PersistentSessionFactory()
workflow_stores = file_workflow_stores(config.store_root)
service = WfMcpService(
store=FileStore(config.store_root),
artifact_store=workflow_stores.artifact_store,
draft_workspace_store=workflow_stores.draft_workspace_store,
run_store=workflow_stores.run_store,
# Discovery can use short-lived SDK sessions. Workflow execution needs
# a persistent runtime so stateful MCP servers keep session/page state
# across sequential workflow nodes.
tool_executor=McpRuntimePool(runtime_factory.create),
)
```
- [ ] **Step 4: Verify Task 2**
Run:
```bash
uv run pytest tests\wf_mcp\test_broker_server.py::test_build_service_from_config_uses_store_root_for_workflow_stores -q
uv run ruff check src\wf_mcp\broker\config.py tests\wf_mcp\test_broker_server.py
uv run ruff format --check src\wf_mcp\broker\config.py tests\wf_mcp\test_broker_server.py
```
Expected: tests pass, lint pass, format pass.
---
### Task 3: Remove Implicit Workflow Store Creation from WfMcpService
**Files:**
- Modify: `src/wf_mcp/broker/service/core.py`
- Modify: `tests/wf_mcp/service/test_catalog.py`
- [ ] **Step 1: Replace the old default-store test**
Find `tests/wf_mcp/service/test_catalog.py::test_service_installs_default_draft_workspace_store`.
Replace it with:
```python
def test_service_does_not_install_workflow_stores_implicitly() -> None:
root = local_temp_root() / "service_no_implicit_workflow_stores"
service = WfMcpService(store=FileStore(root))
assert service.artifact_store is None
assert service.draft_workspace_store is None
assert service.run_store is None
```
- [ ] **Step 2: Run failing test**
Run:
```bash
uv run pytest tests\wf_mcp\service\test_catalog.py::test_service_does_not_install_workflow_stores_implicitly -q
```
Expected: fail because `WfMcpService.__post_init__` still creates stores.
- [ ] **Step 3: Remove implicit creation from `WfMcpService.__post_init__`**
In `src/wf_mcp/broker/service/core.py`, remove the imports:
```python
FileDraftWorkspaceStore,
FileRunStore,
FileWorkflowArtifactStore,
```
Remove the `_store_root` helper entirely:
```python
def _store_root(store: Store) -> Path:
"""Return the file root for stores that expose one, else use local default."""
root = getattr(store, "root", None)
return root if isinstance(root, Path) else Path(".wf_mcp_store")
```
Update `WfMcpService.__post_init__` to:
```python
def __post_init__(self) -> None:
"""Install broker-local system specs when enabled.
Workflow stores are injected by entrypoint/config construction. This service
must not guess workflow persistence from the MCP catalog/auth store because
CLI, MCP, and future HTTP frontends may share or swap those stores.
"""
if self.include_builtin_specs:
for source in builtin_sources().values():
self.register_capability_source(source)
self.register_capability_source(admin_source())
```
If `Path` becomes unused in `core.py`, remove `from pathlib import Path`.
- [ ] **Step 4: Verify Task 3**
Run:
```bash
uv run pytest tests\wf_mcp\service\test_catalog.py::test_service_does_not_install_workflow_stores_implicitly -q
uv run ruff check src\wf_mcp\broker\service\core.py tests\wf_mcp\service\test_catalog.py
uv run ruff format --check src\wf_mcp\broker\service\core.py tests\wf_mcp\service\test_catalog.py
```
Expected: tests pass, lint pass, format pass.
---
### Task 4: Fix Direct Service Tests That Need Workflow Stores
**Files:**
- Modify only tests that fail after Task 3.
- [ ] **Step 1: Run targeted workflow API/service tests**
Run:
```bash
uv run pytest tests\wf_api tests\wf_mcp\workflow_surface tests\wf_mcp\test_broker_server.py tests\wf_mcp\service -q
```
Expected: if failures appear, they should be tests that constructed `WfMcpService(store=...)` but then used workflow artifact/draft/run operations.
- [ ] **Step 2: Patch only failing tests by injecting stores explicitly**
For any failing direct `WfMcpService(...)` test that needs workflow stores, use this pattern:
```python
from wf_api import file_workflow_stores
root = local_temp_root() / "test_specific_name"
workflow_stores = file_workflow_stores(root)
service = WfMcpService(
store=FileStore(root / "mcp"),
artifact_store=workflow_stores.artifact_store,
draft_workspace_store=workflow_stores.draft_workspace_store,
run_store=workflow_stores.run_store,
)
```
Do not add stores to tests that only exercise broker catalog/admin/source behavior.
- [ ] **Step 3: Keep no-store behavior tests intact**
Tests like these should keep `artifact_store=None` through `WorkflowOperationContext` or direct service construction because they prove graceful no-store behavior:
```python
assert result["nodes"] == []
assert result["deployments"] == []
```
Do not “fix” these by injecting stores unless the test is explicitly about stored workflow data.
- [ ] **Step 4: Verify targeted tests**
Run:
```bash
uv run pytest tests\wf_api tests\wf_mcp\workflow_surface tests\wf_mcp\test_broker_server.py tests\wf_mcp\service -q
uv run ruff check tests\wf_api tests\wf_mcp\workflow_surface tests\wf_mcp\test_broker_server.py tests\wf_mcp\service
uv run ruff format --check tests\wf_api tests\wf_mcp\workflow_surface tests\wf_mcp\test_broker_server.py tests\wf_mcp\service
```
Expected: targeted tests pass and no broad fixture churn.
---
### Task 5: Document the Store Ownership Rule
**Files:**
- Modify: `docs/superpowers/research/2026-06-01-wf-api-extraction-map.md`
- Modify: `docs/current_roadmap.md` if it already has a `wf_api` section
- [ ] **Step 1: Update the extraction map**
In `docs/superpowers/research/2026-06-01-wf-api-extraction-map.md`, under `### Store Ownership Ambiguity`, replace the section with:
```markdown
### Store Ownership
- `wf_artifacts` owns workflow store protocols and file-backed implementations.
- `wf_api.stores.WorkflowStores` groups the artifact, draft workspace, and run stores as protocol-neutral API dependencies.
- MCP config construction creates file-backed workflow stores from `BrokerConfig.store_root` and injects them into `WfMcpService`.
- `WfMcpService.__post_init__` no longer creates workflow stores from its MCP `Store`; direct service tests must inject stores when they exercise workflow persistence.
- Future HTTP/API entrypoints should construct or receive the same `WorkflowStores` bundle instead of importing `wf_mcp`.
```
- [ ] **Step 2: Update roadmap only if there is a matching section**
If `docs/current_roadmap.md` has a `wf_api` or API extraction section, add:
```markdown
- Workflow store ownership is explicit: entrypoints construct/inject `WorkflowStores`; `WfMcpService` no longer guesses stores from the MCP store root.
```
If there is no matching section, skip this file.
- [ ] **Step 3: Verify docs are not stale**
Run:
```bash
rg -n "_store_root\\(|creates default `FileWorkflowArtifactStore`|installs default.*store" src docs tests
```
Expected: no current docs/tests claim `WfMcpService` installs default workflow stores. Historical plans may still mention old implementation; leave historical plans alone unless they are current roadmap/research docs.
---
### Task 6: Final Verification
**Files:**
- All touched files.
- [ ] **Step 1: Run focused suite**
Run:
```bash
uv run pytest tests\wf_api tests\wf_mcp\workflow_surface tests\wf_mcp\test_broker_server.py tests\wf_mcp\service -q
```
Expected: all selected tests pass.
- [ ] **Step 2: Run full suite**
Run:
```bash
uv run pytest -q
```
Expected: full suite passes with the repos known skip/xfail counts.
- [ ] **Step 3: Run lint and format checks**
Run:
```bash
uv run ruff check src\wf_api src\wf_mcp tests\wf_api tests\wf_mcp
uv run ruff format --check src\wf_api src\wf_mcp tests\wf_api tests\wf_mcp
```
Expected: all checks pass.
- [ ] **Step 4: Run typecheck**
Run:
```bash
uv run basedpyright --level error
```
Expected: `0 errors, 0 warnings, 0 notes`. If the command exits nonzero only because of the known workspace enumeration warning, report that exactly.
---
## Self-Review
- Spec coverage: This plan covers explicit store creation, `WfMcpService.__post_init__` cleanup, config behavior preservation, test migration, and docs.
- Placeholder scan: No `TODO`/`TBD` placeholders remain. Historical-plan references are explicitly scoped.
- Type consistency: `WorkflowStores` uses protocol types from `wf_artifacts`; `file_workflow_stores()` returns file-backed implementations; `WfMcpService` field types do not change.
- Scope check: This does not implement FastAPI, persisted-run conflict handling, or store locking. Those are separate slices.
@@ -404,12 +404,13 @@ src/wf_cli/
- `self.service._get_qualified_spec(qualified_name)` — used 3 times. This is a private method on `WfMcpService`. Must become a port method. - `self.service._get_qualified_spec(qualified_name)` — used 3 times. This is a private method on `WfMcpService`. Must become a port method.
- `self.service._record_event(event)` — used 7 times. This is a private method. Must become a port method (or the event bus itself becomes a port). - `self.service._record_event(event)` — used 7 times. This is a private method. Must become a port method (or the event bus itself becomes a port).
### Store Ownership Ambiguity ### Store Ownership
- `WfMcpService.__post_init__` creates default `FileWorkflowArtifactStore`, `FileDraftWorkspaceStore`, `FileRunStore` if not provided. These are protocol-neutral stores. - `wf_artifacts` owns workflow store protocols and file-backed implementations.
- The stores are created from `_store_root(self.store)` which uses the MCP `Store` root. - `wf_api.stores.WorkflowStores` groups the artifact, draft workspace, and run stores as protocol-neutral API dependencies.
- After extraction, store creation should be the caller's responsibility (config-driven), not `WfMcpService`'s. - MCP config construction creates file-backed workflow stores from `BrokerConfig.store_root` and injects them into `WfMcpService`.
- Next extraction slice must define the store initialization boundary: which caller constructs `FileWorkflowArtifactStore`, `FileDraftWorkspaceStore`, and `FileRunStore`; how config/API callers inject protocol-neutral store implementations; and which tests must explicitly construct stores instead of relying on `WfMcpService.__post_init__`. - `WfMcpService.__post_init__` no longer creates workflow stores from its MCP `Store`; direct service tests must inject stores when they exercise workflow persistence.
- Future HTTP/API entrypoints should construct or receive the same `WorkflowStores` bundle instead of importing `wf_mcp`.
### Naming Confusion ### Naming Confusion
+8 -1
View File
@@ -3,7 +3,12 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from wf_artifacts import FileWorkflowArtifactStore, WorkflowDeployment from wf_artifacts import (
FileDraftWorkspaceStore,
FileRunStore,
FileWorkflowArtifactStore,
WorkflowDeployment,
)
from wf_mcp.capabilities import DiscoveredPrompt, DiscoveredResource, DiscoveredTool from wf_mcp.capabilities import DiscoveredPrompt, DiscoveredResource, DiscoveredTool
from wf_mcp.models import AuthRecord, ConnectionConfig from wf_mcp.models import AuthRecord, ConnectionConfig
from wf_mcp.sdk import ToolCallResult from wf_mcp.sdk import ToolCallResult
@@ -136,6 +141,8 @@ async def prepare_demo_service(root: Path) -> WfMcpService:
service = WfMcpService( service = WfMcpService(
store=FileStore(root / "mcp_store"), store=FileStore(root / "mcp_store"),
artifact_store=FileWorkflowArtifactStore(root / "artifacts"), artifact_store=FileWorkflowArtifactStore(root / "artifacts"),
draft_workspace_store=FileDraftWorkspaceStore(root / "mcp_store"),
run_store=FileRunStore(root / "mcp_store"),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
+3
View File
@@ -39,6 +39,7 @@ from .operation_context import (
) )
from .runtime_dependencies import RuntimeDependencies, resolve_runtime_dependencies from .runtime_dependencies import RuntimeDependencies, resolve_runtime_dependencies
from .stores import WorkflowStores, file_workflow_stores
__all__ = [ __all__ = [
"DEFAULT_CALL_STEP_ID", "DEFAULT_CALL_STEP_ID",
@@ -78,4 +79,6 @@ __all__ = [
"workflow_output_schema_for_authoring", "workflow_output_schema_for_authoring",
"wrapper_hints_for_capability", "wrapper_hints_for_capability",
"resolve_runtime_dependencies", "resolve_runtime_dependencies",
"WorkflowStores",
"file_workflow_stores",
] ]
+35
View File
@@ -0,0 +1,35 @@
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from wf_artifacts import (
DraftWorkspaceStore,
FileDraftWorkspaceStore,
FileRunStore,
FileWorkflowArtifactStore,
RunStore,
WorkflowArtifactStore,
)
@dataclass(frozen=True, slots=True)
class WorkflowStores:
"""Protocol-neutral persistence dependencies for workflow APIs."""
artifact_store: WorkflowArtifactStore
draft_workspace_store: DraftWorkspaceStore
run_store: RunStore
def file_workflow_stores(root: str | Path) -> WorkflowStores:
"""Create process-local file-backed workflow stores under one root."""
store_root = Path(root)
return WorkflowStores(
artifact_store=FileWorkflowArtifactStore(store_root),
draft_workspace_store=FileDraftWorkspaceStore(store_root),
run_store=FileRunStore(store_root),
)
__all__ = ["WorkflowStores", "file_workflow_stores"]
+5 -8
View File
@@ -3,11 +3,7 @@ from __future__ import annotations
import json import json
from pathlib import Path from pathlib import Path
from wf_artifacts import ( from wf_api import file_workflow_stores
FileDraftWorkspaceStore,
FileRunStore,
FileWorkflowArtifactStore,
)
from ..control import BrokerConfigFile from ..control import BrokerConfigFile
from ..models import BrokerConfig from ..models import BrokerConfig
@@ -27,11 +23,12 @@ def load_broker_config(path: str | Path) -> BrokerConfig:
def build_service_from_config(config: BrokerConfig) -> WfMcpService: def build_service_from_config(config: BrokerConfig) -> WfMcpService:
"""Create a broker service with SDK adapters for configured connections.""" """Create a broker service with SDK adapters for configured connections."""
runtime_factory = PersistentSessionFactory() runtime_factory = PersistentSessionFactory()
workflow_stores = file_workflow_stores(config.store_root)
service = WfMcpService( service = WfMcpService(
store=FileStore(config.store_root), store=FileStore(config.store_root),
artifact_store=FileWorkflowArtifactStore(config.store_root), artifact_store=workflow_stores.artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(config.store_root), draft_workspace_store=workflow_stores.draft_workspace_store,
run_store=FileRunStore(config.store_root), run_store=workflow_stores.run_store,
# Discovery can use short-lived SDK sessions. Workflow execution needs # Discovery can use short-lived SDK sessions. Workflow execution needs
# a persistent runtime so stateful MCP servers keep session/page state # a persistent runtime so stateful MCP servers keep session/page state
# across sequential workflow nodes. # across sequential workflow nodes.
+6 -19
View File
@@ -2,16 +2,12 @@ from __future__ import annotations
import time import time
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path
from typing import Any from typing import Any
from pydantic import BaseModel from pydantic import BaseModel
from wf_artifacts import ( from wf_artifacts import (
DraftWorkspaceStore, DraftWorkspaceStore,
FileDraftWorkspaceStore,
FileRunStore,
FileWorkflowArtifactStore,
RunStore, RunStore,
WorkflowArtifact, WorkflowArtifact,
WorkflowArtifactCatalogEntry, WorkflowArtifactCatalogEntry,
@@ -68,12 +64,6 @@ from .builtins import builtin_sources
from .specs import get_qualified_spec, qualify_spec from .specs import get_qualified_spec, qualify_spec
def _store_root(store: Store) -> Path:
"""Return the file root for stores that expose one, else use local default."""
root = getattr(store, "root", None)
return root if isinstance(root, Path) else Path(".wf_mcp_store")
@dataclass(slots=True) @dataclass(slots=True)
class WfMcpService: class WfMcpService:
store: Store store: Store
@@ -89,15 +79,12 @@ class WfMcpService:
tool_executor: ToolExecutor | None = None tool_executor: ToolExecutor | None = None
def __post_init__(self) -> None: def __post_init__(self) -> None:
"""Install broker-local system specs when enabled.""" """Install broker-local system specs when enabled.
if self.artifact_store is None:
self.artifact_store = FileWorkflowArtifactStore(_store_root(self.store)) Workflow stores are injected by entrypoint/config construction. This service
if self.draft_workspace_store is None: must not guess workflow persistence from the MCP catalog/auth store because
self.draft_workspace_store = FileDraftWorkspaceStore( CLI, MCP, and future HTTP frontends may share or swap those stores.
_store_root(self.store) """
)
if self.run_store is None:
self.run_store = FileRunStore(_store_root(self.store))
if self.include_builtin_specs: if self.include_builtin_specs:
for source in builtin_sources().values(): for source in builtin_sources().values():
self.register_capability_source(source) self.register_capability_source(source)
+12 -5
View File
@@ -6,7 +6,12 @@ import asyncio
from dataclasses import replace from dataclasses import replace
from typing import Any from typing import Any
from wf_artifacts import FileWorkflowArtifactStore, RequiredCapability, WorkflowArtifact from wf_artifacts import (
FileWorkflowArtifactStore,
FileDraftWorkspaceStore,
RequiredCapability,
WorkflowArtifact,
)
from wf_api.artifacts import WorkflowArtifactApi from wf_api.artifacts import WorkflowArtifactApi
from wf_mcp.broker import WfMcpService from wf_mcp.broker import WfMcpService
from wf_mcp.models import ConnectionConfig from wf_mcp.models import ConnectionConfig
@@ -111,11 +116,11 @@ def _artifact_api(
*, *,
register_echo: bool = False, register_echo: bool = False,
) -> tuple[WorkflowArtifactApi, WfMcpService]: ) -> tuple[WorkflowArtifactApi, WfMcpService]:
mcp_root = artifact_store.root / "artifacts_mcp" / str(id(artifact_store))
service = WfMcpService( service = WfMcpService(
store=FileStore( store=FileStore(mcp_root),
artifact_store.root / "artifacts_mcp" / str(id(artifact_store))
),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
) )
if register_echo: if register_echo:
service.register_connection( service.register_connection(
@@ -255,9 +260,11 @@ def test_handler_delegation_for_inspect_artifact() -> None:
artifact_store = FileWorkflowArtifactStore( artifact_store = FileWorkflowArtifactStore(
local_temp_root() / "artifacts_delegation" local_temp_root() / "artifacts_delegation"
) )
mcp_root = artifact_store.root / "delegation_mcp"
service = WfMcpService( service = WfMcpService(
store=FileStore(artifact_store.root / "delegation_mcp"), store=FileStore(mcp_root),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
) )
artifact_store.save_artifact(_echo_artifact()) artifact_store.save_artifact(_echo_artifact())
+7 -3
View File
@@ -4,7 +4,7 @@ import asyncio
import pytest import pytest
from wf_artifacts import FileWorkflowArtifactStore from wf_artifacts import FileWorkflowArtifactStore, FileDraftWorkspaceStore
from wf_api.capabilities import WorkflowCapabilityApi from wf_api.capabilities import WorkflowCapabilityApi
from wf_mcp.broker import WfMcpService from wf_mcp.broker import WfMcpService
from wf_mcp.models import ConnectionConfig from wf_mcp.models import ConnectionConfig
@@ -22,9 +22,11 @@ def _capability_api(
register_echo: bool = False, register_echo: bool = False,
register_failing: bool = False, register_failing: bool = False,
) -> tuple[WorkflowCapabilityApi, WfMcpService]: ) -> tuple[WorkflowCapabilityApi, WfMcpService]:
mcp_root = artifact_store.root / "caps_mcp" / str(id(artifact_store))
service = WfMcpService( service = WfMcpService(
store=FileStore(artifact_store.root / "caps_mcp" / str(id(artifact_store))), store=FileStore(mcp_root),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
) )
if register_echo: if register_echo:
service.register_connection( service.register_connection(
@@ -241,9 +243,11 @@ def test_create_draft_workspace_from_capability() -> None:
def test_handler_delegates_to_capability_api() -> None: def test_handler_delegates_to_capability_api() -> None:
"""WorkflowSurfaceHandlers methods produce the same result as direct API.""" """WorkflowSurfaceHandlers methods produce the same result as direct API."""
artifact_store = FileWorkflowArtifactStore(local_temp_root() / "cap_api_delegation") artifact_store = FileWorkflowArtifactStore(local_temp_root() / "cap_api_delegation")
mcp_root = artifact_store.root / "delegation_mcp"
service = WfMcpService( service = WfMcpService(
store=FileStore(artifact_store.root / "delegation_mcp"), store=FileStore(mcp_root),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
+7 -3
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
import asyncio import asyncio
from typing import Any from typing import Any
from wf_artifacts import FileWorkflowArtifactStore from wf_artifacts import FileWorkflowArtifactStore, FileDraftWorkspaceStore
from wf_api.drafts import WorkflowDraftApi from wf_api.drafts import WorkflowDraftApi
from wf_mcp.broker import WfMcpService from wf_mcp.broker import WfMcpService
from wf_mcp.models import ConnectionConfig from wf_mcp.models import ConnectionConfig
@@ -55,9 +55,11 @@ def _draft_api(
*, *,
register_echo: bool = False, register_echo: bool = False,
) -> tuple[WorkflowDraftApi, WfMcpService]: ) -> tuple[WorkflowDraftApi, WfMcpService]:
mcp_root = artifact_store.root / "drafts_mcp" / str(id(artifact_store))
service = WfMcpService( service = WfMcpService(
store=FileStore(artifact_store.root / "drafts_mcp" / str(id(artifact_store))), store=FileStore(mcp_root),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
) )
if register_echo: if register_echo:
service.register_connection( service.register_connection(
@@ -326,9 +328,11 @@ def test_delegation_smoke_validate_draft_equivalence() -> None:
artifact_store = FileWorkflowArtifactStore( artifact_store = FileWorkflowArtifactStore(
local_temp_root() / "drafts_delegation_smoke" local_temp_root() / "drafts_delegation_smoke"
) )
mcp_root = artifact_store.root / "delegation_mcp"
service = WfMcpService( service = WfMcpService(
store=FileStore(artifact_store.root / "delegation_mcp"), store=FileStore(mcp_root),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
+3 -1
View File
@@ -5,7 +5,7 @@ from pathlib import Path
import pytest import pytest
from wf_artifacts import FileWorkflowArtifactStore, WorkflowDeployment from wf_artifacts import FileWorkflowArtifactStore, FileRunStore, WorkflowDeployment
from wf_api.runs import WorkflowRunApi from wf_api.runs import WorkflowRunApi
from wf_mcp.broker import WfMcpService from wf_mcp.broker import WfMcpService
from wf_mcp.broker.service.workflow_operation_context import context_from_service from wf_mcp.broker.service.workflow_operation_context import context_from_service
@@ -43,6 +43,7 @@ def _service_with_echo(
service = WfMcpService( service = WfMcpService(
store=FileStore(root / "mcp"), store=FileStore(root / "mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(root / "mcp"),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -67,6 +68,7 @@ def _service_with_failing(
service = WfMcpService( service = WfMcpService(
store=FileStore(root / "mcp"), store=FileStore(root / "mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(root / "mcp"),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
+32
View File
@@ -0,0 +1,32 @@
from __future__ import annotations
from wf_api.stores import WorkflowStores, file_workflow_stores
from wf_artifacts import (
FileDraftWorkspaceStore,
FileRunStore,
FileWorkflowArtifactStore,
)
from tests.wf_mcp.test_support import local_temp_root
def test_file_workflow_stores_constructs_all_three_file_stores() -> None:
root = local_temp_root() / "wf_api_file_workflow_stores"
stores = file_workflow_stores(root)
assert isinstance(stores, WorkflowStores)
assert isinstance(stores.artifact_store, FileWorkflowArtifactStore)
assert isinstance(stores.draft_workspace_store, FileDraftWorkspaceStore)
assert isinstance(stores.run_store, FileRunStore)
assert stores.artifact_store.root == root
assert stores.draft_workspace_store.root == root
assert stores.run_store.root == root
def test_wf_api_exports_workflow_stores() -> None:
from wf_api import WorkflowStores as ExportedWorkflowStores
from wf_api import file_workflow_stores as exported_file_workflow_stores
assert ExportedWorkflowStores is WorkflowStores
assert exported_file_workflow_stores is file_workflow_stores
+5 -5
View File
@@ -3,7 +3,6 @@ from __future__ import annotations
import asyncio import asyncio
import shutil import shutil
from wf_artifacts import FileDraftWorkspaceStore
from wf_authoring import NodeSpec from wf_authoring import NodeSpec
from wf_core import RunStatus from wf_core import RunStatus
from wf_mcp.broker import WfMcpService from wf_mcp.broker import WfMcpService
@@ -65,12 +64,13 @@ def test_service_installs_builtin_stdlib_specs_by_default() -> None:
assert "wf.mcp" not in service.capability_sources assert "wf.mcp" not in service.capability_sources
def test_service_installs_default_draft_workspace_store() -> None: def test_service_does_not_install_workflow_stores_implicitly() -> None:
root = local_temp_root() / "service_default_draft_workspace_store" root = local_temp_root() / "service_no_implicit_workflow_stores"
service = WfMcpService(store=FileStore(root)) service = WfMcpService(store=FileStore(root))
assert isinstance(service.draft_workspace_store, FileDraftWorkspaceStore) assert service.artifact_store is None
assert service.draft_workspace_store.root == root assert service.draft_workspace_store is None
assert service.run_store is None
def test_service_registers_empty_source_for_connection_without_catalog() -> None: def test_service_registers_empty_source_for_connection_without_catalog() -> None:
+13 -8
View File
@@ -5,6 +5,8 @@ import json
from typing import Any, cast from typing import Any, cast
from wf_artifacts import ( from wf_artifacts import (
FileDraftWorkspaceStore,
FileRunStore,
FileWorkflowArtifactStore, FileWorkflowArtifactStore,
RequiredCapability, RequiredCapability,
WorkflowArtifact, WorkflowArtifact,
@@ -352,6 +354,7 @@ def test_broker_runs_non_interrupting_workflow_deployment() -> None:
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "broker_run_mcp_store"), store=FileStore(local_temp_root() / "broker_run_mcp_store"),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(local_temp_root() / "broker_run_mcp_store"),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -431,6 +434,7 @@ def test_broker_run_deployment_pauses_and_resumes_interrupting_artifacts() -> No
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "broker_run_interrupt_mcp_store"), store=FileStore(local_temp_root() / "broker_run_interrupt_mcp_store"),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(local_temp_root() / "broker_run_interrupt_mcp_store"),
) )
server = create_broker_server(service) server = create_broker_server(service)
@@ -467,17 +471,18 @@ def test_broker_run_deployment_pauses_and_resumes_interrupting_artifacts() -> No
assert resumed["resume_readiness"] == "not_applicable" assert resumed["resume_readiness"] == "not_applicable"
def test_build_service_from_config_uses_store_root_for_artifacts() -> None: def test_build_service_from_config_uses_store_root_for_workflow_stores() -> None:
store_root = local_temp_root() / "broker_config_artifact_store" store_root = local_temp_root() / "broker_config_workflow_stores"
service = build_service_from_config( config = BrokerConfig(store_root=store_root, connections=[])
BrokerConfig(
store_root=store_root, service = build_service_from_config(config)
connections=[],
)
)
assert isinstance(service.artifact_store, FileWorkflowArtifactStore) assert isinstance(service.artifact_store, FileWorkflowArtifactStore)
assert isinstance(service.draft_workspace_store, FileDraftWorkspaceStore)
assert isinstance(service.run_store, FileRunStore)
assert service.artifact_store.root == store_root assert service.artifact_store.root == store_root
assert service.draft_workspace_store.root == store_root
assert service.run_store.root == store_root
def _artifact() -> WorkflowArtifact: def _artifact() -> WorkflowArtifact:
+4 -1
View File
@@ -4,6 +4,7 @@ import asyncio
from typing import Any from typing import Any
from wf_artifacts import ( from wf_artifacts import (
FileRunStore,
FileWorkflowArtifactStore, FileWorkflowArtifactStore,
RequiredCapability, RequiredCapability,
ResumeReadiness, ResumeReadiness,
@@ -381,9 +382,11 @@ def _deployment(*, bindings: dict[str, str] | None = None) -> WorkflowDeployment
def _handlers(artifact_store: FileWorkflowArtifactStore) -> WorkflowSurfaceHandlers: def _handlers(artifact_store: FileWorkflowArtifactStore) -> WorkflowSurfaceHandlers:
mcp_root = local_temp_root() / f"{artifact_store.root.name}_mcp"
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / f"{artifact_store.root.name}_mcp"), store=FileStore(mcp_root),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(mcp_root),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
+6 -1
View File
@@ -5,6 +5,8 @@ from typing import Any
from pydantic import BaseModel from pydantic import BaseModel
from wf_artifacts import ( from wf_artifacts import (
FileDraftWorkspaceStore,
FileRunStore,
FileWorkflowArtifactStore, FileWorkflowArtifactStore,
RequiredCapability, RequiredCapability,
WorkflowArtifact, WorkflowArtifact,
@@ -159,9 +161,12 @@ def multiply(current: int | None, incoming: int) -> int:
def handlers(artifact_store: FileWorkflowArtifactStore) -> WorkflowSurfaceHandlers: def handlers(artifact_store: FileWorkflowArtifactStore) -> WorkflowSurfaceHandlers:
mcp_root = artifact_store.root / "surface_mcp" / str(id(artifact_store))
service = WfMcpService( service = WfMcpService(
store=FileStore(artifact_store.root / "surface_mcp" / str(id(artifact_store))), store=FileStore(mcp_root),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
run_store=FileRunStore(mcp_root),
) )
return WorkflowSurfaceHandlers(service) return WorkflowSurfaceHandlers(service)
@@ -2,7 +2,7 @@ from __future__ import annotations
import asyncio import asyncio
from wf_artifacts import FileWorkflowArtifactStore from wf_artifacts import FileWorkflowArtifactStore, FileDraftWorkspaceStore
from wf_mcp.broker import WfMcpService from wf_mcp.broker import WfMcpService
from wf_mcp.models import ConnectionConfig from wf_mcp.models import ConnectionConfig
from wf_mcp.storage import FileStore from wf_mcp.storage import FileStore
@@ -102,6 +102,9 @@ def test_workflow_surface_does_not_auto_map_raw_mcp_content_blocks() -> None:
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_content_only_content_hint_mcp"), store=FileStore(local_temp_root() / "surface_content_only_content_hint_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(
local_temp_root() / "surface_content_only_content_hint_mcp"
),
) )
service.register_connection( service.register_connection(
ConnectionConfig( ConnectionConfig(
+32 -1
View File
@@ -2,7 +2,11 @@ from __future__ import annotations
import asyncio import asyncio
from wf_artifacts import FileWorkflowArtifactStore, WorkflowDeployment from wf_artifacts import (
FileWorkflowArtifactStore,
FileDraftWorkspaceStore,
WorkflowDeployment,
)
from wf_mcp.broker import WfMcpService from wf_mcp.broker import WfMcpService
from wf_mcp.models import ConnectionConfig from wf_mcp.models import ConnectionConfig
from wf_mcp.storage import FileStore from wf_mcp.storage import FileStore
@@ -122,6 +126,9 @@ def test_workflow_surface_validates_draft_workspace_with_live_outcomes() -> None
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_workspace_validate_mcp"), store=FileStore(local_temp_root() / "surface_workspace_validate_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(
local_temp_root() / "surface_workspace_validate_mcp"
),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -153,6 +160,9 @@ def test_workflow_surface_creates_minimal_draft_workspace_with_error_route() ->
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_minimal_workspace_mcp"), store=FileStore(local_temp_root() / "surface_minimal_workspace_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(
local_temp_root() / "surface_minimal_workspace_mcp"
),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -201,6 +211,9 @@ def test_workflow_surface_minimal_draft_honors_explicit_error_message_source() -
artifact_store=FileWorkflowArtifactStore( artifact_store=FileWorkflowArtifactStore(
local_temp_root() / "surface_minimal_explicit_error" local_temp_root() / "surface_minimal_explicit_error"
), ),
draft_workspace_store=FileDraftWorkspaceStore(
local_temp_root() / "surface_minimal_explicit_error_mcp"
),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -259,6 +272,9 @@ def test_workflow_surface_accepts_canonical_bindings_for_minimal_workspace() ->
artifact_store=FileWorkflowArtifactStore( artifact_store=FileWorkflowArtifactStore(
local_temp_root() / "surface_minimal_canonical" local_temp_root() / "surface_minimal_canonical"
), ),
draft_workspace_store=FileDraftWorkspaceStore(
local_temp_root() / "surface_minimal_canonical_mcp"
),
) )
h = WorkflowSurfaceHandlers(service) h = WorkflowSurfaceHandlers(service)
@@ -309,6 +325,9 @@ def test_workflow_surface_creates_draft_workspace_from_capability_hints() -> Non
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_workspace_from_capability_mcp"), store=FileStore(local_temp_root() / "surface_workspace_from_capability_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(
local_temp_root() / "surface_workspace_from_capability_mcp"
),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -362,6 +381,9 @@ def test_workflow_surface_creates_artifact_from_workspace() -> None:
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_workspace_artifact_mcp"), store=FileStore(local_temp_root() / "surface_workspace_artifact_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(
local_temp_root() / "surface_workspace_artifact_mcp"
),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -404,6 +426,9 @@ def test_workflow_surface_workspace_artifact_infers_raw_concrete_dependency() ->
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_workspace_artifact_raw_mcp"), store=FileStore(local_temp_root() / "surface_workspace_artifact_raw_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(
local_temp_root() / "surface_workspace_artifact_raw_mcp"
),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -441,6 +466,9 @@ def test_workflow_surface_creates_wrapper_from_workspace() -> None:
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_workspace_wrapper_mcp"), store=FileStore(local_temp_root() / "surface_workspace_wrapper_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(
local_temp_root() / "surface_workspace_wrapper_mcp"
),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -478,6 +506,9 @@ def test_workflow_surface_low_confidence_draft_returns_patch_guidance() -> None:
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_workspace_low_confidence_mcp"), store=FileStore(local_temp_root() / "surface_workspace_low_confidence_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
draft_workspace_store=FileDraftWorkspaceStore(
local_temp_root() / "surface_workspace_low_confidence_mcp"
),
) )
service.register_connection( service.register_connection(
ConnectionConfig( ConnectionConfig(
+8 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import asyncio import asyncio
from wf_artifacts import FileWorkflowArtifactStore, WorkflowDeployment from wf_artifacts import FileWorkflowArtifactStore, FileRunStore, WorkflowDeployment
from wf_mcp.broker import WfMcpService from wf_mcp.broker import WfMcpService
from wf_mcp.models import ConnectionConfig from wf_mcp.models import ConnectionConfig
from wf_mcp.storage import FileStore from wf_mcp.storage import FileStore
@@ -51,6 +51,7 @@ def test_workflow_surface_runs_non_interrupting_deployment() -> None:
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_run_mcp"), store=FileStore(local_temp_root() / "surface_run_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(local_temp_root() / "surface_run_mcp"),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -112,6 +113,7 @@ def test_workflow_surface_failed_deployment_exposes_error_on_run_and_inspect() -
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_failed_run_error_mcp"), store=FileStore(local_temp_root() / "surface_failed_run_error_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(local_temp_root() / "surface_failed_run_error_mcp"),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -153,6 +155,7 @@ def test_workflow_surface_run_deployment_can_include_trace_detail() -> None:
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_run_trace_detail_mcp"), store=FileStore(local_temp_root() / "surface_run_trace_detail_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(local_temp_root() / "surface_run_trace_detail_mcp"),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -201,6 +204,7 @@ def test_workflow_surface_run_deployment_can_read_empty_trace_range() -> None:
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_run_trace_empty_range_mcp"), store=FileStore(local_temp_root() / "surface_run_trace_empty_range_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(local_temp_root() / "surface_run_trace_empty_range_mcp"),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -237,6 +241,7 @@ def test_workflow_surface_runs_deployment_with_bound_node_spec_dependency() -> N
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_bound_node_mcp"), store=FileStore(local_temp_root() / "surface_bound_node_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(local_temp_root() / "surface_bound_node_mcp"),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -263,6 +268,7 @@ def test_workflow_surface_runs_artifact_created_from_concrete_node_ref() -> None
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_created_bound_node_mcp"), store=FileStore(local_temp_root() / "surface_created_bound_node_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(local_temp_root() / "surface_created_bound_node_mcp"),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")
@@ -390,6 +396,7 @@ def test_workflow_surface_runs_deployment_with_bound_reducer_dependency() -> Non
service = WfMcpService( service = WfMcpService(
store=FileStore(local_temp_root() / "surface_reducer_mcp"), store=FileStore(local_temp_root() / "surface_reducer_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
run_store=FileRunStore(local_temp_root() / "surface_reducer_mcp"),
) )
service.register_connection( service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal") ConnectionConfig(id="demo.personal", server="demo", account="personal")