Skip to main content

lean_ctx/tools/registered/
ctx_workflow.rs

1use 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}