lean_ctx/tools/registered/
ctx_workflow.rs1use rmcp::ErrorData;
2use rmcp::model::Tool;
3use serde_json::{Map, Value, json};
4
5use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_str};
6use crate::tool_defs::tool_def;
7
8pub struct CtxWorkflowTool;
9
10impl McpTool for CtxWorkflowTool {
11 fn name(&self) -> &'static str {
12 "ctx_workflow"
13 }
14
15 fn tool_def(&self) -> Tool {
16 tool_def(
17 "ctx_workflow",
18 "Workflow rails — state machine with evidence tracking.\n\
19 WORKFLOW: start → transition (multiple) → complete. evidence_add before\n\
20 transition to attach proof. Built-in plan_code_test when spec omitted.\n\
21 Actions: start|status|transition|complete|evidence_add|evidence_list|stop.\n\
22 spec=WorkflowSpec JSON for custom states/transitions.\n\
23 ANTIPATTERN: NOT for one-shot tasks — use direct tool calls instead.",
24 json!({
25 "type": "object",
26 "properties": {
27 "action": {
28 "type": "string",
29 "enum": ["start", "status", "transition", "complete", "evidence_add", "evidence_list", "stop"],
30 "description": "start|status|transition|complete|evidence_add|evidence_list|stop"
31 },
32 "name": { "type": "string", "description": "Workflow name (for start)" },
33 "spec": { "type": "string", "description": "WorkflowSpec JSON (for start; omit for builtin)" },
34 "to": { "type": "string", "description": "Target state (for transition)" },
35 "key": { "type": "string", "description": "Evidence key (for evidence_add)" },
36 "value": { "type": "string", "description": "Evidence value or transition note" }
37 }
38 }),
39 )
40 }
41
42 fn handle(
43 &self,
44 args: &Map<String, Value>,
45 ctx: &ToolContext,
46 ) -> Result<ToolOutput, ErrorData> {
47 let action = get_str(args, "action").unwrap_or_else(|| "status".to_string());
48
49 let agent_id_str = ctx
50 .agent_id
51 .as_ref()
52 .and_then(|h| h.blocking_read().clone());
53
54 let result = {
55 let session_handle = ctx
56 .session
57 .as_ref()
58 .ok_or_else(|| ErrorData::internal_error("session not available", None))?;
59 let mut session = session_handle.blocking_write();
60 crate::tools::ctx_workflow::handle_with_session_agent(
61 Some(args),
62 &mut session,
63 agent_id_str.as_deref(),
64 )
65 };
66
67 if let Some(workflow_handle) = ctx.workflow.as_ref() {
68 let mut wf = workflow_handle.blocking_write();
69 *wf = crate::core::workflow::load_active_for_agent(agent_id_str.as_deref())
70 .ok()
71 .flatten();
72 }
73
74 Ok(ToolOutput {
75 text: result,
76 original_tokens: 0,
77 saved_tokens: 0,
78 mode: Some(action),
79 path: None,
80 changed: false,
81 shell_outcome: None,
82 })
83 }
84}