feat: add semantic draft authoring operations

This commit is contained in:
lda
2026-06-27 17:14:17 +07:00 Verified
parent 5cfe513efc
commit 60a3815452
27 changed files with 1383 additions and 741 deletions
+38 -21
View File
@@ -187,6 +187,12 @@ class ValidateDraftWorkspaceRequest(BaseModel):
workspace_id: WorkspaceId
class CompileDraftWorkspaceRequest(BaseModel):
"""Typed MCP request for compiling one workspace draft without mutation."""
workspace_id: WorkspaceId
class SetDraftNameRequest(BaseModel):
"""Typed MCP request for changing the workflow draft name."""
@@ -237,20 +243,6 @@ class SetStepOutputMapRequest(BaseModel):
)
class AddStateFromOutputRequest(BaseModel):
"""Typed MCP request for declaring a state field from a step output schema."""
workspace_id: WorkspaceId
revision: int = Field(ge=1, description="Expected current workspace revision.")
step_id: str = Field(description="Draft step id whose capability output is used.")
output_field: str = Field(
description="Top-level output field to copy, for example after."
)
state_path: str = Field(
description="Root state path to declare, for example state.after."
)
class BindOutputToStateRequest(BaseModel):
"""Typed MCP request for binding one step output to one root state field."""
@@ -280,13 +272,13 @@ class AddStepFromCapabilityRequest(BaseModel):
default="ok",
description="Outcome on route_from_step that should route to the new step.",
)
route_outcome: str = Field(
default="ok",
description="Outcome emitted by the new step.",
)
route_to: str = Field(
default="__end__",
description="Target step id or __end__ for the new step outcome.",
routes: dict[str, str] | None = Field(
default=None,
description=(
"Outcome to target route map for the new step. If omitted, a single "
"declared outcome routes to __end__; multiple declared outcomes "
"require explicit routes."
),
)
input_map: dict[str, str] = Field(
default_factory=dict,
@@ -298,6 +290,31 @@ class AddStepFromCapabilityRequest(BaseModel):
)
class BranchDraftRequest(BaseModel):
"""Typed MCP request for branching routes on a draft step."""
workspace_id: WorkspaceId
revision: int = Field(ge=1, description="Expected workspace revision.")
step_id: str = Field(description="Draft step id whose routes should be branched.")
routes: dict[str, str] = Field(description="Outcome to target step map.")
class HandleDraftBranchItem(BaseModel):
"""Single branch item for handle_draft."""
step_id: str = Field(description="Draft step id.")
outcome: str = Field(description="Outcome label.")
class HandleDraftRequest(BaseModel):
"""Typed MCP request for handling draft branches."""
workspace_id: WorkspaceId
revision: int = Field(ge=1, description="Expected workspace revision.")
branches: list[HandleDraftBranchItem] = Field(description="Branch items to handle.")
target: str = Field(description="Target step id or __end__.")
class DeleteDraftWorkspaceRequest(BaseModel):
"""Typed MCP request payload for deleting one draft workspace."""
+56 -24
View File
@@ -12,10 +12,11 @@ from wf_mcp.broker.service import WfMcpService
from wf_mcp.broker.service.workflow_operation_context import context_from_service
from .models import (
AddStateFromOutputRequest,
AddStepFromCapabilityRequest,
BindOutputToStateRequest,
BranchDraftRequest,
CallCapabilityResult,
CompileDraftWorkspaceRequest,
CreateArtifactFromWorkspaceRequest,
CreateDraftWorkspaceFromCapabilityRequest,
CreateDraftWorkspaceFromCapabilityResult,
@@ -26,6 +27,7 @@ from .models import (
DeleteDraftWorkspaceResult,
DraftWorkspaceListResult,
DraftWorkspaceResult,
HandleDraftRequest,
PatchDraftWorkspaceRequest,
RunDeploymentResult,
SetDraftNameRequest,
@@ -368,6 +370,22 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
await handlers.validate_draft_workspace(workspace_id=request.workspace_id)
)
@server.tool(
name="wf.workflow.compile_draft_workspace",
title="Compile Draft Workspace",
description=(
"Compile a stored draft workspace without mutating it. Returns "
"compiled_plan and required_capabilities when valid, or diagnostics "
"when invalid."
),
)
async def compile_draft_workspace(
request: CompileDraftWorkspaceRequest,
) -> dict[str, Any]:
return await handlers.compile_draft_workspace(
workspace_id=request.workspace_id,
)
@server.tool(
name="wf.workflow.set_draft_name",
title="Set Draft Name",
@@ -442,27 +460,6 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
)
)
@server.tool(
name="wf.workflow.add_state_from_output",
title="Add State From Output",
description=(
"Declare one root state field by copying a draft step capability output "
"field schema, including local $defs/definitions when present."
),
)
async def add_state_from_output(
request: AddStateFromOutputRequest,
) -> DraftWorkspaceResult:
return DraftWorkspaceResult.model_validate(
await handlers.add_state_schema_from_output(
workspace_id=request.workspace_id,
revision=request.revision,
step_id=request.step_id,
output_field=request.output_field,
state_path=request.state_path,
)
)
@server.tool(
name="wf.workflow.bind_output_to_state",
title="Bind Output To State",
@@ -503,13 +500,48 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
capability_name=request.capability_name,
route_from_step=request.route_from_step,
route_from_outcome=request.route_from_outcome,
route_outcome=request.route_outcome,
route_to=request.route_to,
routes=request.routes,
input_map=request.input_map,
bind_outputs=request.bind_outputs,
)
)
@server.tool(
name="wf.workflow.branch_draft",
title="Branch Draft",
description=(
"Branch multiple outcome routes on a single draft step atomically. "
"Provided outcomes are updated while unspecified outcomes are preserved."
),
)
async def branch_draft(request: BranchDraftRequest) -> DraftWorkspaceResult:
return DraftWorkspaceResult.model_validate(
await handlers.branch_draft(
workspace_id=request.workspace_id,
revision=request.revision,
step_id=request.step_id,
routes=request.routes,
)
)
@server.tool(
name="wf.workflow.handle_draft",
title="Handle Draft",
description=("Set a common target for multiple step/outcome pairs atomically."),
)
async def handle_draft(request: HandleDraftRequest) -> DraftWorkspaceResult:
return DraftWorkspaceResult.model_validate(
await handlers.handle_draft(
workspace_id=request.workspace_id,
revision=request.revision,
branches=[
{"step_id": b.step_id, "outcome": b.outcome}
for b in request.branches
],
target=request.target,
)
)
@server.tool(
name="wf.workflow.create_minimal_draft_workspace",
title="Create Minimal Draft Workspace",