task_graph_mcp/tools/
claiming.rs1use super::{get_bool, get_string, make_tool_with_prompts};
8use crate::config::{AutoAdvanceConfig, DependenciesConfig, PhasesConfig, Prompts, StatesConfig};
9use crate::db::Database;
10use crate::error::ToolError;
11use crate::prompts::PromptContext;
12use anyhow::Result;
13use rmcp::model::Tool;
14use serde_json::{Value, json};
15
16pub fn get_tools(prompts: &Prompts, _states_config: &StatesConfig) -> Vec<Tool> {
17 vec![make_tool_with_prompts(
18 "claim",
19 "Commit to working on a task (like adding to a changelist). Fails if: already claimed, deps unsatisfied, or worker lacks required tags. Sets status to timed (working) status.",
20 json!({
21 "worker_id": {
22 "type": "string",
23 "description": "Worker ID claiming the task"
24 },
25 "task": {
26 "type": "string",
27 "description": "Task ID to claim"
28 },
29 "force": {
30 "type": "boolean",
31 "description": "Force claim even if owned by another agent (default: false)"
32 }
33 }),
34 vec!["worker_id", "task"],
35 prompts,
36 )]
37}
38
39pub fn claim(
40 db: &Database,
41 states_config: &StatesConfig,
42 phases_config: &PhasesConfig,
43 deps_config: &DependenciesConfig,
44 auto_advance: &AutoAdvanceConfig,
45 workflows: &crate::config::workflows::WorkflowsConfig,
46 args: Value,
47) -> Result<Value> {
48 let worker_id =
49 get_string(&args, "worker_id").ok_or_else(|| ToolError::missing_field("worker_id"))?;
50 let task_id = get_string(&args, "task").ok_or_else(|| ToolError::missing_field("task"))?;
51 let force = get_bool(&args, "force").unwrap_or(false);
52
53 let claim_status = states_config
55 .definitions
56 .iter()
57 .find(|(_, def)| def.timed)
58 .map(|(name, _)| name.clone())
59 .unwrap_or_else(|| "working".to_string());
60
61 let (task, _unblocked, _auto_advanced) = db.update_task_unified(
64 &task_id,
65 &worker_id,
66 None, None, None, Some(claim_status), None, None, None, None, None, None, None, None, force,
79 states_config,
80 deps_config,
81 auto_advance,
82 )?;
83
84 let transition_prompt_list: Vec<String> = {
86 match db.update_worker_state(&worker_id, Some(&task.status), task.phase.as_deref()) {
87 Ok((old_status, old_phase)) => {
88 let ctx = PromptContext::new(
90 &task.status,
91 task.phase.as_deref(),
92 states_config,
93 phases_config,
94 );
95 crate::prompts::get_transition_prompts_with_context(
96 old_status.as_deref().unwrap_or(""),
97 old_phase.as_deref(),
98 &task.status,
99 task.phase.as_deref(),
100 workflows,
101 &ctx,
102 )
103 }
104 Err(_) => vec![],
105 }
106 };
107
108 let mut response = json!({
109 "success": true,
110 "task": {
111 "id": &task.id,
112 "title": task.title,
113 "status": task.status,
114 "worker_id": task.worker_id,
115 "claimed_at": task.claimed_at
116 }
117 });
118
119 if !transition_prompt_list.is_empty() {
121 if let Value::Object(ref mut map) = response {
122 map.insert("prompts".to_string(), json!(transition_prompt_list));
123 }
124 }
125
126 Ok(response)
127}