handoff-mcp-server 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +31 -4
  2. package/bin/handoff-mcp.js +56 -13
  3. package/bin/resolve-binary.js +122 -0
  4. package/package.json +14 -9
  5. package/Cargo.lock +0 -686
  6. package/Cargo.toml +0 -30
  7. package/scripts/cargo-env.sh +0 -29
  8. package/scripts/handoff-memory-hook.py +0 -208
  9. package/scripts/install-local.sh +0 -109
  10. package/scripts/postinstall.js +0 -50
  11. package/scripts/sync-plugin-skills.sh +0 -35
  12. package/scripts/sync-plugin-version.sh +0 -85
  13. package/scripts/sync-workflow-inline.sh +0 -138
  14. package/src/cli.rs +0 -551
  15. package/src/context/injection.rs +0 -276
  16. package/src/context/mod.rs +0 -129
  17. package/src/lib.rs +0 -5
  18. package/src/main.rs +0 -157
  19. package/src/mcp/handlers/assignees.rs +0 -254
  20. package/src/mcp/handlers/auto_schedule.rs +0 -489
  21. package/src/mcp/handlers/bulk_update.rs +0 -155
  22. package/src/mcp/handlers/calendar.rs +0 -196
  23. package/src/mcp/handlers/capacity.rs +0 -318
  24. package/src/mcp/handlers/check_criterion.rs +0 -70
  25. package/src/mcp/handlers/config.rs +0 -402
  26. package/src/mcp/handlers/config_crud.rs +0 -183
  27. package/src/mcp/handlers/dashboard.rs +0 -214
  28. package/src/mcp/handlers/docs.rs +0 -2288
  29. package/src/mcp/handlers/docs_query.rs +0 -1335
  30. package/src/mcp/handlers/fork_session.rs +0 -91
  31. package/src/mcp/handlers/get_session.rs +0 -48
  32. package/src/mcp/handlers/get_task.rs +0 -53
  33. package/src/mcp/handlers/import_context.rs +0 -470
  34. package/src/mcp/handlers/init.rs +0 -28
  35. package/src/mcp/handlers/list_sessions.rs +0 -187
  36. package/src/mcp/handlers/list_tasks.rs +0 -308
  37. package/src/mcp/handlers/load_context.rs +0 -361
  38. package/src/mcp/handlers/log_time.rs +0 -67
  39. package/src/mcp/handlers/memory.rs +0 -961
  40. package/src/mcp/handlers/merge_sessions.rs +0 -103
  41. package/src/mcp/handlers/metrics.rs +0 -196
  42. package/src/mcp/handlers/milestones.rs +0 -102
  43. package/src/mcp/handlers/mod.rs +0 -140
  44. package/src/mcp/handlers/refer.rs +0 -307
  45. package/src/mcp/handlers/referrals.rs +0 -74
  46. package/src/mcp/handlers/save_context.rs +0 -354
  47. package/src/mcp/handlers/task_checklist.rs +0 -507
  48. package/src/mcp/handlers/timer.rs +0 -529
  49. package/src/mcp/handlers/update_session.rs +0 -197
  50. package/src/mcp/handlers/update_task.rs +0 -452
  51. package/src/mcp/mod.rs +0 -6
  52. package/src/mcp/protocol.rs +0 -41
  53. package/src/mcp/resources.rs +0 -57
  54. package/src/mcp/router.rs +0 -154
  55. package/src/mcp/tools.rs +0 -1522
  56. package/src/mcp/types.rs +0 -108
  57. package/src/setup.rs +0 -1212
  58. package/src/storage/config.rs +0 -578
  59. package/src/storage/docs/frontmatter.rs +0 -509
  60. package/src/storage/docs/mod.rs +0 -835
  61. package/src/storage/docs/model.rs +0 -708
  62. package/src/storage/docs/reassemble.rs +0 -167
  63. package/src/storage/docs/split.rs +0 -377
  64. package/src/storage/git.rs +0 -47
  65. package/src/storage/memory/injected.rs +0 -340
  66. package/src/storage/memory/mod.rs +0 -236
  67. package/src/storage/memory/model.rs +0 -127
  68. package/src/storage/mod.rs +0 -96
  69. package/src/storage/referrals.rs +0 -248
  70. package/src/storage/sessions.rs +0 -859
  71. package/src/storage/tasks.rs +0 -957
  72. package/templates/claude-md-section.md +0 -12
@@ -1,103 +0,0 @@
1
- use anyhow::Result;
2
- use serde_json::{json, Value};
3
-
4
- use super::resolve_project_dir;
5
- use crate::storage::ensure_handoff_exists;
6
- use crate::storage::sessions::merge_sessions;
7
-
8
- pub fn handle(arguments: &Value) -> Result<String> {
9
- let project_dir = resolve_project_dir(arguments)?;
10
- let handoff = ensure_handoff_exists(&project_dir)?;
11
- let sessions_dir = handoff.join("sessions");
12
-
13
- let source_session_ids: Vec<&str> = arguments
14
- .get("source_session_ids")
15
- .and_then(|v| v.as_array())
16
- .ok_or_else(|| anyhow::anyhow!("'source_session_ids' parameter is required"))?
17
- .iter()
18
- .filter_map(|v| v.as_str())
19
- .collect();
20
-
21
- if source_session_ids.len() < 2 {
22
- anyhow::bail!("'source_session_ids' must contain at least 2 session IDs");
23
- }
24
-
25
- let target_session_id = arguments
26
- .get("target_session_id")
27
- .and_then(|v| v.as_str())
28
- .ok_or_else(|| anyhow::anyhow!("'target_session_id' parameter is required"))?;
29
-
30
- if !source_session_ids.contains(&target_session_id) {
31
- anyhow::bail!(
32
- "target_session_id '{}' must be one of the source_session_ids",
33
- target_session_id
34
- );
35
- }
36
-
37
- let close_sources = arguments
38
- .get("close_sources")
39
- .and_then(|v| v.as_bool())
40
- .unwrap_or(true);
41
-
42
- let result = merge_sessions(
43
- &sessions_dir,
44
- &source_session_ids,
45
- target_session_id,
46
- close_sources,
47
- )?;
48
-
49
- let conflicts_json: Vec<Value> = result
50
- .conflicts
51
- .iter()
52
- .map(|c| {
53
- json!({
54
- "type": c.conflict_type,
55
- "description": c.description,
56
- "session_a": c.session_a,
57
- "session_b": c.session_b,
58
- })
59
- })
60
- .collect();
61
-
62
- let output_json = json!({
63
- "merged_session_id": result.merged_session_id,
64
- "merged_decisions": result.merged_decisions,
65
- "merged_notes": result.merged_notes,
66
- "merged_references": result.merged_references,
67
- "merged_context_pointers": result.merged_context_pointers,
68
- "conflicts": conflicts_json,
69
- "closed_sessions": result.closed_sessions,
70
- });
71
-
72
- let mut output = format!(
73
- "Merged {} sessions into {}\n\
74
- Merged: {} decisions, {} notes, {} references, {} context_pointers",
75
- source_session_ids.len(),
76
- result.merged_session_id,
77
- result.merged_decisions,
78
- result.merged_notes,
79
- result.merged_references,
80
- result.merged_context_pointers,
81
- );
82
-
83
- if !result.conflicts.is_empty() {
84
- output.push_str(&format!("\nConflicts: {}", result.conflicts.len()));
85
- for c in &result.conflicts {
86
- output.push_str(&format!("\n - {}: {}", c.conflict_type, c.description));
87
- }
88
- }
89
-
90
- if !result.closed_sessions.is_empty() {
91
- output.push_str(&format!(
92
- "\nClosed source sessions: {}",
93
- result.closed_sessions.join(", ")
94
- ));
95
- }
96
-
97
- output.push_str(&format!(
98
- "\n\n{}",
99
- serde_json::to_string_pretty(&output_json)?
100
- ));
101
-
102
- Ok(output)
103
- }
@@ -1,196 +0,0 @@
1
- use std::collections::HashMap;
2
-
3
- use anyhow::Result;
4
- use chrono::Utc;
5
- use serde_json::{json, Value};
6
-
7
- use super::resolve_project_dir;
8
- use crate::storage::config::read_config;
9
- use crate::storage::ensure_handoff_exists;
10
- use crate::storage::tasks::{build_task_index, is_terminal_status, TaskIndex};
11
-
12
- pub fn handle(arguments: &Value) -> Result<String> {
13
- let project_dir = resolve_project_dir(arguments)?;
14
- let handoff = ensure_handoff_exists(&project_dir)?;
15
- let tasks_dir = handoff.join("tasks");
16
-
17
- let assignee_filter = arguments.get("assignee").and_then(|v| v.as_str());
18
-
19
- let (tree, _) = build_task_index(&tasks_dir, u32::MAX)?;
20
-
21
- let today = Utc::now().format("%Y-%m-%d").to_string();
22
-
23
- let mut total: u32 = 0;
24
- let mut by_status: HashMap<String, u32> = HashMap::new();
25
- let mut estimate_sum: f64 = 0.0;
26
- let mut actual_sum: f64 = 0.0;
27
- let mut remaining_sum: f64 = 0.0;
28
- let mut overdue_tasks: Vec<Value> = Vec::new();
29
- let mut milestones: HashMap<String, (u32, u32, f64, f64)> = HashMap::new();
30
-
31
- collect_metrics(
32
- &tree,
33
- assignee_filter,
34
- &today,
35
- &mut total,
36
- &mut by_status,
37
- &mut estimate_sum,
38
- &mut actual_sum,
39
- &mut remaining_sum,
40
- &mut overdue_tasks,
41
- &mut milestones,
42
- );
43
-
44
- let done_count = *by_status.get("done").unwrap_or(&0);
45
- let skipped_count = *by_status.get("skipped").unwrap_or(&0);
46
- let completion_percent = if total > 0 {
47
- ((done_count + skipped_count) as f64 / total as f64) * 100.0
48
- } else {
49
- 0.0
50
- };
51
-
52
- let budget = read_budget(&handoff);
53
-
54
- // AI-effort multiplier: the raw estimate is the human-effort estimate;
55
- // multiplying by ai_estimate_multiplier yields the expected AI-effort hours.
56
- // Raw estimates are preserved; only the adjusted view is derived here.
57
- let multiplier = read_config(&handoff.join("config.toml"))
58
- .map(|c| c.settings.ai_estimate_multiplier)
59
- .unwrap_or(0.2);
60
-
61
- let milestone_list: Vec<Value> = milestones
62
- .into_iter()
63
- .map(|(name, (done, ms_total, est, act))| {
64
- json!({
65
- "name": name,
66
- "done": done,
67
- "total": ms_total,
68
- "estimate_hours": est,
69
- "adjusted_estimate_hours": est * multiplier,
70
- "actual_hours": act,
71
- })
72
- })
73
- .collect();
74
-
75
- let result = json!({
76
- "total": total,
77
- "by_status": by_status,
78
- "completion_percent": (completion_percent * 10.0).round() / 10.0,
79
- "total_estimate_hours": estimate_sum,
80
- "ai_estimate_multiplier": multiplier,
81
- "total_adjusted_estimate_hours": estimate_sum * multiplier,
82
- "total_actual_hours": actual_sum,
83
- "total_remaining_hours": remaining_sum,
84
- "overdue_count": overdue_tasks.len(),
85
- "overdue_tasks": overdue_tasks,
86
- "budget": budget,
87
- "milestones": milestone_list,
88
- });
89
-
90
- serde_json::to_string_pretty(&result).map_err(Into::into)
91
- }
92
-
93
- #[allow(clippy::too_many_arguments)]
94
- fn collect_metrics(
95
- tree: &[TaskIndex],
96
- assignee_filter: Option<&str>,
97
- today: &str,
98
- total: &mut u32,
99
- by_status: &mut HashMap<String, u32>,
100
- estimate_sum: &mut f64,
101
- actual_sum: &mut f64,
102
- remaining_sum: &mut f64,
103
- overdue_tasks: &mut Vec<Value>,
104
- milestones: &mut HashMap<String, (u32, u32, f64, f64)>,
105
- ) {
106
- for node in tree {
107
- let task_assignee = node.assignee.as_deref();
108
- let include = match assignee_filter {
109
- Some(f) => task_assignee == Some(f),
110
- None => true,
111
- };
112
-
113
- if include {
114
- *total += 1;
115
- *by_status.entry(node.status.clone()).or_insert(0) += 1;
116
-
117
- if let Some(ref sched) = node.schedule {
118
- if let Some(est) = sched.estimate_hours {
119
- *estimate_sum += est;
120
- }
121
- if let Some(act) = sched.actual_hours {
122
- *actual_sum += act;
123
- }
124
- if let Some(rem) = sched.remaining_hours {
125
- *remaining_sum += rem;
126
- }
127
-
128
- if let Some(ref due) = sched.due_date {
129
- if !is_terminal_status(&node.status) && due.as_str() < today {
130
- let days_overdue = days_between(due, today).unwrap_or(0);
131
- overdue_tasks.push(json!({
132
- "id": node.id,
133
- "title": node.title,
134
- "due_date": due,
135
- "days_overdue": days_overdue,
136
- }));
137
- }
138
- }
139
-
140
- if let Some(ref ms) = sched.milestone {
141
- let entry = milestones.entry(ms.clone()).or_insert((0, 0, 0.0, 0.0));
142
- entry.1 += 1;
143
- if is_terminal_status(&node.status) {
144
- entry.0 += 1;
145
- }
146
- if let Some(est) = sched.estimate_hours {
147
- entry.2 += est;
148
- }
149
- if let Some(act) = sched.actual_hours {
150
- entry.3 += act;
151
- }
152
- }
153
- }
154
- }
155
-
156
- collect_metrics(
157
- &node.children,
158
- assignee_filter,
159
- today,
160
- total,
161
- by_status,
162
- estimate_sum,
163
- actual_sum,
164
- remaining_sum,
165
- overdue_tasks,
166
- milestones,
167
- );
168
- }
169
- }
170
-
171
- fn days_between(from: &str, to: &str) -> Option<i64> {
172
- let from_date = chrono::NaiveDate::parse_from_str(from, "%Y-%m-%d").ok()?;
173
- let to_date = chrono::NaiveDate::parse_from_str(to, "%Y-%m-%d").ok()?;
174
- Some((to_date - from_date).num_days())
175
- }
176
-
177
- fn read_budget(handoff: &std::path::Path) -> Value {
178
- let config_path = handoff.join("config.toml");
179
- let content = match std::fs::read_to_string(&config_path) {
180
- Ok(c) => c,
181
- Err(_) => return Value::Null,
182
- };
183
-
184
- for line in content.lines() {
185
- let trimmed = line.trim();
186
- if trimmed.starts_with("total_hours") {
187
- if let Some(val_str) = trimmed.split('=').nth(1) {
188
- if let Ok(total) = val_str.trim().parse::<f64>() {
189
- return json!({ "total_hours": total });
190
- }
191
- }
192
- }
193
- }
194
-
195
- Value::Null
196
- }
@@ -1,102 +0,0 @@
1
- //! Milestone CRUD over the `[milestones.<name>]` config.toml section.
2
- //! Mirrors the VSCode extension's addMilestone/updateMilestone/removeMilestone.
3
-
4
- use anyhow::Result;
5
- use serde_json::{json, Value};
6
- use toml_edit::{Item, Table};
7
-
8
- use super::config_crud::{
9
- config_path, ensure_subtable, ensure_table, load_doc, require_str, save_doc, set_opt_str,
10
- };
11
-
12
- /// handoff_list_milestones — return every `[milestones.*]` entry.
13
- pub fn handle_list(arguments: &Value) -> Result<String> {
14
- let path = config_path(arguments)?;
15
- let doc = load_doc(&path)?;
16
-
17
- let mut out = serde_json::Map::new();
18
- if let Some(table) = doc.get("milestones").and_then(|v| v.as_table()) {
19
- for (name, item) in table.iter() {
20
- let sub = match item.as_table() {
21
- Some(t) => t,
22
- None => continue,
23
- };
24
- out.insert(
25
- name.to_string(),
26
- json!({
27
- "date": sub.get("date").and_then(|v| v.as_str()),
28
- "color": sub.get("color").and_then(|v| v.as_str()),
29
- "description": sub.get("description").and_then(|v| v.as_str()),
30
- }),
31
- );
32
- }
33
- }
34
- serde_json::to_string_pretty(&json!({ "milestones": out })).map_err(Into::into)
35
- }
36
-
37
- /// handoff_add_milestone — create a `[milestones.<name>]` entry. Fails if it exists.
38
- pub fn handle_add(arguments: &Value) -> Result<String> {
39
- let path = config_path(arguments)?;
40
- let name = require_str(arguments, "name")?;
41
- let mut doc = load_doc(&path)?;
42
-
43
- let table = ensure_table(&mut doc, "milestones")?;
44
- table.set_implicit(true);
45
- if table.contains_key(name) {
46
- anyhow::bail!(
47
- "Milestone '{name}' already exists. Use handoff_update_milestone to modify it."
48
- );
49
- }
50
- let mut sub = Table::new();
51
- apply_fields(&mut sub, arguments);
52
- doc["milestones"][name] = Item::Table(sub);
53
-
54
- save_doc(&path, &doc)?;
55
- Ok(format!("Added milestone '{name}'"))
56
- }
57
-
58
- /// handoff_update_milestone — patch an existing `[milestones.<name>]` entry.
59
- pub fn handle_update(arguments: &Value) -> Result<String> {
60
- let path = config_path(arguments)?;
61
- let name = require_str(arguments, "name")?;
62
- let mut doc = load_doc(&path)?;
63
-
64
- let exists = doc
65
- .get("milestones")
66
- .and_then(|v| v.as_table())
67
- .map(|t| t.contains_key(name))
68
- .unwrap_or(false);
69
- if !exists {
70
- anyhow::bail!("Milestone '{name}' not found. Use handoff_add_milestone to create it.");
71
- }
72
- let sub = ensure_subtable(&mut doc, "milestones", name)?;
73
- apply_fields(sub, arguments);
74
-
75
- save_doc(&path, &doc)?;
76
- Ok(format!("Updated milestone '{name}'"))
77
- }
78
-
79
- /// handoff_remove_milestone — delete a `[milestones.<name>]` entry.
80
- pub fn handle_remove(arguments: &Value) -> Result<String> {
81
- let path = config_path(arguments)?;
82
- let name = require_str(arguments, "name")?;
83
- let mut doc = load_doc(&path)?;
84
-
85
- let removed = doc
86
- .get_mut("milestones")
87
- .and_then(|v| v.as_table_mut())
88
- .map(|t| t.remove(name).is_some())
89
- .unwrap_or(false);
90
- if !removed {
91
- anyhow::bail!("Milestone '{name}' not found.");
92
- }
93
- save_doc(&path, &doc)?;
94
- Ok(format!("Removed milestone '{name}'"))
95
- }
96
-
97
- fn apply_fields(table: &mut Table, arguments: &Value) {
98
- table.set_implicit(false);
99
- set_opt_str(table, "date", arguments.get("date"));
100
- set_opt_str(table, "color", arguments.get("color"));
101
- set_opt_str(table, "description", arguments.get("description"));
102
- }
@@ -1,140 +0,0 @@
1
- pub mod assignees;
2
- pub mod auto_schedule;
3
- pub mod bulk_update;
4
- pub mod calendar;
5
- pub mod capacity;
6
- pub mod check_criterion;
7
- pub mod config;
8
- pub mod config_crud;
9
- pub mod dashboard;
10
- pub mod docs;
11
- pub mod docs_query;
12
- pub mod fork_session;
13
- pub mod get_session;
14
- pub mod get_task;
15
- pub mod import_context;
16
- pub mod init;
17
- pub mod list_sessions;
18
- pub mod list_tasks;
19
- pub mod load_context;
20
- pub mod log_time;
21
- pub mod memory;
22
- pub mod merge_sessions;
23
- pub mod metrics;
24
- pub mod milestones;
25
- pub mod refer;
26
- pub mod referrals;
27
- pub mod save_context;
28
- pub mod task_checklist;
29
- pub mod timer;
30
- pub mod update_session;
31
- pub mod update_task;
32
-
33
- use std::path::PathBuf;
34
-
35
- use anyhow::{Context, Result};
36
- use serde_json::Value;
37
-
38
- use crate::mcp::types::JsonRpcResponse;
39
-
40
- pub fn resolve_project_dir(arguments: &Value) -> Result<PathBuf> {
41
- let raw = match arguments
42
- .get("project_dir")
43
- .and_then(|v| v.as_str())
44
- .filter(|s| !s.is_empty() && !s.starts_with("${"))
45
- {
46
- Some(dir) => PathBuf::from(dir),
47
- None => match std::env::var("CLAUDE_PROJECT_DIR") {
48
- Ok(dir) if !dir.is_empty() => PathBuf::from(dir),
49
- _ => std::env::current_dir().context("Failed to get current directory")?,
50
- },
51
- };
52
- std::fs::canonicalize(&raw)
53
- .with_context(|| format!("Invalid project path: {raw}", raw = raw.display()))
54
- }
55
-
56
- pub fn handle_tool_call(name: &str, arguments: &Value) -> JsonRpcResponse {
57
- let result = match name {
58
- "handoff_init" => init::handle(arguments),
59
- "handoff_update_task" => update_task::handle(arguments),
60
- "handoff_list_tasks" => list_tasks::handle(arguments),
61
- "handoff_save_context" => save_context::handle(arguments),
62
- "handoff_load_context" => load_context::handle(arguments),
63
- "handoff_dashboard" => dashboard::handle(arguments),
64
- "handoff_get_config" => config::handle_get(arguments),
65
- "handoff_update_config" => config::handle_update(arguments),
66
- "handoff_get_task" => get_task::handle(arguments),
67
- "handoff_check_criterion" => check_criterion::handle(arguments),
68
- "handoff_import_context" => import_context::handle(arguments),
69
- "handoff_refer" => refer::handle(arguments),
70
- "handoff_list_referrals" => referrals::handle_list(arguments),
71
- "handoff_get_referral" => referrals::handle_get(arguments),
72
- "handoff_update_referral" => referrals::handle_update(arguments),
73
- "handoff_update_session" => update_session::handle(arguments),
74
- "handoff_log_time" => log_time::handle(arguments),
75
- "handoff_get_metrics" => metrics::handle(arguments),
76
- "handoff_list_sessions" => list_sessions::handle(arguments),
77
- "handoff_list_assignees" => assignees::handle(arguments),
78
- "handoff_bulk_update_tasks" => bulk_update::handle(arguments),
79
- "handoff_get_session" => get_session::handle(arguments),
80
- "handoff_get_capacity" => capacity::handle(arguments),
81
- "handoff_auto_schedule" => auto_schedule::handle(arguments),
82
- "handoff_add_assignee" => assignees::handle_add(arguments),
83
- "handoff_update_assignee" => assignees::handle_update(arguments),
84
- "handoff_remove_assignee" => assignees::handle_remove(arguments),
85
- "handoff_list_milestones" => milestones::handle_list(arguments),
86
- "handoff_add_milestone" => milestones::handle_add(arguments),
87
- "handoff_update_milestone" => milestones::handle_update(arguments),
88
- "handoff_remove_milestone" => milestones::handle_remove(arguments),
89
- "handoff_update_calendar" => calendar::handle_update_calendar(arguments),
90
- "handoff_update_labels" => calendar::handle_update_labels(arguments),
91
- "handoff_start_project" => calendar::handle_start_project(arguments),
92
- "handoff_memory_save" => memory::handle_save(arguments),
93
- "handoff_memory_query" => memory::handle_query(arguments),
94
- "handoff_memory_delete" => memory::handle_delete(arguments),
95
- "handoff_memory_cleanup" => memory::handle_cleanup(arguments),
96
- "handoff_fork_session" => fork_session::handle(arguments),
97
- "handoff_merge_sessions" => merge_sessions::handle(arguments),
98
- "handoff_timer_start" => timer::handle_start(arguments),
99
- "handoff_timer_stop" => timer::handle_stop(arguments),
100
- "handoff_timer_get_time" => timer::handle_get_time(arguments),
101
- "handoff_doc_save" => docs::handle_doc_save(arguments),
102
- "handoff_doc_update_section" => docs::handle_doc_update_section(arguments),
103
- "handoff_doc_get" => docs::handle_doc_get(arguments),
104
- "handoff_doc_list" => docs::handle_doc_list(arguments),
105
- "handoff_doc_delete" => docs::handle_doc_delete(arguments),
106
- "handoff_doc_reassemble" => docs::handle_doc_reassemble(arguments),
107
- "handoff_doc_tree" => docs::handle_doc_tree(arguments),
108
- "handoff_doc_graph" => docs::handle_doc_graph(arguments),
109
- "handoff_doc_trace" => docs::handle_doc_trace(arguments),
110
- "handoff_doc_verify" => docs::handle_doc_verify(arguments),
111
- "handoff_doc_verify_status" => docs::handle_doc_verify_status(arguments),
112
- "handoff_doc_query" => docs_query::handle_doc_query(arguments),
113
- "handoff_doc_analyze" => docs_query::handle_doc_analyze(arguments),
114
- "handoff_doc_import" => docs_query::handle_doc_import(arguments),
115
- "handoff_task_checklist" => task_checklist::handle(arguments),
116
- _ => Err(anyhow::anyhow!("Tool not implemented: {name}")),
117
- };
118
-
119
- match result {
120
- Ok(content) => {
121
- let tool_result = serde_json::json!({
122
- "content": [{
123
- "type": "text",
124
- "text": content
125
- }]
126
- });
127
- JsonRpcResponse::success(None, tool_result)
128
- }
129
- Err(e) => {
130
- let tool_result = serde_json::json!({
131
- "isError": true,
132
- "content": [{
133
- "type": "text",
134
- "text": format!("Error: {e}")
135
- }]
136
- });
137
- JsonRpcResponse::success(None, tool_result)
138
- }
139
- }
140
- }