list draft workspaces: one tool
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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` |
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
*,
|
*,
|
||||||
|
|||||||
@@ -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."""
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user