list draft workspaces: one tool

This commit is contained in:
lda
2026-05-19 05:46:20 +07:00 Verified
parent 1d6a70431b
commit 8921fffc55
8 changed files with 69 additions and 4 deletions
+6 -4
View File
@@ -503,11 +503,13 @@ Concrete MCP sequence:
capability behaves as expected. capability behaves as expected.
3. `wf.workflow.create_minimal_draft_workspace` with a `request` object that 3. `wf.workflow.create_minimal_draft_workspace` with a `request` object that
contains schemas, `input_map`, and `output_map`. contains schemas, `input_map`, and `output_map`.
4. `wf.workflow.get_draft_workspace` with `include_draft=true` if the client 4. `wf.workflow.list_draft_workspaces` if the client needs to rediscover
existing workspace ids.
5. `wf.workflow.get_draft_workspace` with `include_draft=true` if the client
needs to inspect the full current draft. needs to inspect the full current draft.
5. `wf.workflow.patch_draft_workspace` with the current `revision`. 6. `wf.workflow.patch_draft_workspace` with the current `revision`.
6. `wf.workflow.create_artifact_from_workspace` after validation is clean. 7. `wf.workflow.create_artifact_from_workspace` after validation is clean.
7. `wf.workflow.save_deployment`, then `validate_deployment`, then 8. `wf.workflow.save_deployment`, then `validate_deployment`, then
`run_deployment`. `run_deployment`.
`create_artifact_from_workspace` also uses a `request` object: `create_artifact_from_workspace` also uses a `request` object:
+1
View File
@@ -349,6 +349,7 @@ resending the full draft each turn.
| Need | Tool | | Need | Tool |
| --- | --- | | --- | --- |
| Start a patchable authoring session | `wf.workflow.create_minimal_draft_workspace` | | Start a patchable authoring session | `wf.workflow.create_minimal_draft_workspace` |
| List existing draft sessions | `wf.workflow.list_draft_workspaces` |
| Fetch current draft workspace | `wf.workflow.get_draft_workspace` | | Fetch current draft workspace | `wf.workflow.get_draft_workspace` |
| Patch current draft workspace | `wf.workflow.patch_draft_workspace` | | Patch current draft workspace | `wf.workflow.patch_draft_workspace` |
| Save final workspace as artifact | `wf.workflow.create_artifact_from_workspace` | | Save final workspace as artifact | `wf.workflow.create_artifact_from_workspace` |
+1
View File
@@ -41,6 +41,7 @@ _SEARCH_ALWAYS_VISIBLE_TOOL_NAMES = [
"wf.workflow.create_artifact_from_plan", "wf.workflow.create_artifact_from_plan",
"wf.workflow.create_artifact_from_draft", "wf.workflow.create_artifact_from_draft",
"wf.workflow.patch_draft", "wf.workflow.patch_draft",
"wf.workflow.list_draft_workspaces",
"wf.workflow.create_draft_workspace", "wf.workflow.create_draft_workspace",
"wf.workflow.get_draft_workspace", "wf.workflow.get_draft_workspace",
"wf.workflow.patch_draft_workspace", "wf.workflow.patch_draft_workspace",
+10
View File
@@ -375,6 +375,16 @@ class WorkflowSurfaceHandlers:
raise KeyError("draft workspace store is not configured") raise KeyError("draft workspace store is not configured")
return self.service.draft_workspace_store return self.service.draft_workspace_store
async def list_draft_workspaces(self) -> dict[str, Any]:
"""Return compact summaries for stored draft workspaces."""
store = self._draft_store()
return {
"workspaces": [
get_draft_workspace_record(store, workspace_id=workspace.id)
for workspace in store.list_workspaces()
]
}
async def create_draft_workspace( async def create_draft_workspace(
self, self,
*, *,
+8
View File
@@ -91,6 +91,14 @@ class DraftWorkspaceResult(BaseModel):
) )
class DraftWorkspaceListResult(BaseModel):
"""Inspector-visible response for listing stored draft workspaces."""
workspaces: list[dict[str, Any]] = Field(
description="Compact draft workspace summaries. Full drafts are omitted."
)
class CreateDraftWorkspaceRequest(BaseModel): class CreateDraftWorkspaceRequest(BaseModel):
"""Typed MCP request payload for creating a stored draft workspace.""" """Typed MCP request payload for creating a stored draft workspace."""
+11
View File
@@ -14,6 +14,7 @@ from .models import (
CreateArtifactFromWorkspaceRequest, CreateArtifactFromWorkspaceRequest,
CreateDraftWorkspaceRequest, CreateDraftWorkspaceRequest,
CreateMinimalDraftWorkspaceRequest, CreateMinimalDraftWorkspaceRequest,
DraftWorkspaceListResult,
DraftWorkspaceResult, DraftWorkspaceResult,
PatchDraftWorkspaceRequest, PatchDraftWorkspaceRequest,
) )
@@ -197,6 +198,16 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
) -> dict[str, Any]: ) -> dict[str, Any]:
return await handlers.patch_draft(draft=draft, patch=patch) return await handlers.patch_draft(draft=draft, patch=patch)
@server.tool(
name="wf.workflow.list_draft_workspaces",
title="List Draft Workspaces",
description="List compact summaries for mutable workflow draft workspaces.",
)
async def list_draft_workspaces() -> DraftWorkspaceListResult:
return DraftWorkspaceListResult.model_validate(
await handlers.list_draft_workspaces()
)
@server.tool( @server.tool(
name="wf.workflow.create_draft_workspace", name="wf.workflow.create_draft_workspace",
title="Create Draft Workspace", title="Create Draft Workspace",
+2
View File
@@ -64,6 +64,7 @@ def test_server_exposes_upstream_admin_and_workflow_tools() -> None:
assert "wf.workflow.compile_draft" in names assert "wf.workflow.compile_draft" in names
assert "wf.workflow.create_artifact_from_draft" in names assert "wf.workflow.create_artifact_from_draft" in names
assert "wf.workflow.patch_draft" in names assert "wf.workflow.patch_draft" in names
assert "wf.workflow.list_draft_workspaces" in names
assert "wf.workflow.create_draft_workspace" in names assert "wf.workflow.create_draft_workspace" in names
assert "wf.workflow.get_draft_workspace" in names assert "wf.workflow.get_draft_workspace" in names
assert "wf.workflow.patch_draft_workspace" in names assert "wf.workflow.patch_draft_workspace" in names
@@ -187,6 +188,7 @@ def test_server_search_mode_pins_stable_control_and_workflow_tools() -> None:
assert "wf.workflow.list_artifacts" in names assert "wf.workflow.list_artifacts" in names
assert "wf.workflow.validate_draft" in names assert "wf.workflow.validate_draft" in names
assert "wf.workflow.create_artifact_from_draft" in names assert "wf.workflow.create_artifact_from_draft" in names
assert "wf.workflow.list_draft_workspaces" in names
assert "wf.workflow.create_draft_workspace" in names assert "wf.workflow.create_draft_workspace" in names
assert "wf.workflow.get_draft_workspace" in names assert "wf.workflow.get_draft_workspace" in names
assert "wf.workflow.patch_draft_workspace" in names assert "wf.workflow.patch_draft_workspace" in names
+30
View File
@@ -377,6 +377,36 @@ def test_workflow_surface_creates_and_gets_draft_workspace() -> None:
assert fetched["draft"]["steps"]["echo"]["use"] == "demo.personal.echo_tool" assert fetched["draft"]["steps"]["echo"]["use"] == "demo.personal.echo_tool"
def test_workflow_surface_lists_draft_workspaces() -> None:
artifact_store = FileWorkflowArtifactStore(
local_temp_root() / "surface_workspace_list"
)
handlers = _handlers(artifact_store)
asyncio.run(
handlers.create_draft_workspace(
workspace_id="b_draft",
draft=_echo_draft(),
title="B Draft",
)
)
asyncio.run(
handlers.create_draft_workspace(
workspace_id="a_draft",
draft=_echo_draft(),
title="A Draft",
)
)
payload = asyncio.run(handlers.list_draft_workspaces())
assert [workspace["workspace_id"] for workspace in payload["workspaces"]] == [
"a_draft",
"b_draft",
]
assert payload["workspaces"][0]["title"] == "A Draft"
assert "draft" not in payload["workspaces"][0]
def test_workflow_surface_patches_draft_workspace_by_revision() -> None: def test_workflow_surface_patches_draft_workspace_by_revision() -> None:
artifact_store = FileWorkflowArtifactStore( artifact_store = FileWorkflowArtifactStore(
local_temp_root() / "surface_workspace_patch" local_temp_root() / "surface_workspace_patch"