agent-planner-mcp 0.5.0 → 0.5.1

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.
package/src/tools.js CHANGED
@@ -10,7 +10,11 @@
10
10
  */
11
11
 
12
12
  const { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');
13
- const apiClient = require('./api-client');
13
+ const defaultApiClient = require('./api-client');
14
+
15
+ const APP_URL = (process.env.APP_URL || 'https://agentplanner.io').replace(/\/$/, '');
16
+ function buildPlanUrl(planId) { return `${APP_URL}/app/plans/${planId}`; }
17
+ function buildTaskUrl(planId, nodeId) { return `${APP_URL}/app/plans/${planId}?node=${nodeId}`; }
14
18
 
15
19
  /**
16
20
  * Format JSON data as text for Claude Desktop
@@ -43,8 +47,10 @@ function formatResponse(data) {
43
47
  /**
44
48
  * Setup tools for the MCP server
45
49
  * @param {Server} server - MCP server instance
50
+ * @param {Object} [apiClientOverride] - Per-session API client (HTTP mode). Falls back to default (stdio mode).
46
51
  */
47
- function setupTools(server) {
52
+ function setupTools(server, apiClientOverride) {
53
+ const apiClient = apiClientOverride || defaultApiClient;
48
54
  // Suppress console logs when not in debug mode
49
55
  if (process.env.NODE_ENV !== 'development') {
50
56
  // Silent mode for production
@@ -62,7 +68,7 @@ function setupTools(server) {
62
68
  // ========================================
63
69
  {
64
70
  name: "quick_plan",
65
- description: "Create a plan quickly from a title and list of tasks. Perfect for getting started fast - just provide a title and task names. Returns plan URL and task IDs for immediate use.",
71
+ description: "Create a plan quickly from a title and list of tasks. Perfect for getting started fast - just provide a title and task names. Returns plan URL and task IDs for immediate use. Tip: provide a goal_id to automatically link this plan to a goal.",
66
72
  inputSchema: {
67
73
  type: "object",
68
74
  properties: {
@@ -73,7 +79,7 @@ function setupTools(server) {
73
79
  items: { type: "string" },
74
80
  description: "List of task titles (simple strings). A phase will be created automatically."
75
81
  },
76
- goal_id: { type: "string", description: "Optionally link to a goal" },
82
+ goal_id: { type: "string", description: "Optionally link this plan to a goal. Recommended: always link plans to goals for tracking." },
77
83
  organization_id: { type: "string", description: "Organization ID (uses default if not provided)" }
78
84
  },
79
85
  required: ["title", "tasks"]
@@ -104,7 +110,7 @@ function setupTools(server) {
104
110
  plan_id: { type: "string", description: "Plan ID (required for API)" },
105
111
  status: {
106
112
  type: "string",
107
- enum: ["not_started", "in_progress", "completed", "blocked"],
113
+ enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"],
108
114
  description: "New status"
109
115
  },
110
116
  note: { type: "string", description: "Optional note explaining the status change (especially useful for 'blocked')" }
@@ -132,6 +138,46 @@ function setupTools(server) {
132
138
  }
133
139
  },
134
140
 
141
+ {
142
+ name: "check_goals_health",
143
+ description: "Check the health of all your goals. Returns per-goal health status (on_track/at_risk/stale), bottleneck summaries, knowledge gaps, and pending decisions. Call this FIRST in the autonomous loop to identify which goals need attention.",
144
+ inputSchema: {
145
+ type: "object",
146
+ properties: {
147
+ status_filter: { type: "string", description: "Filter by health status (e.g. 'on_track', 'at_risk', 'stale')" }
148
+ }
149
+ }
150
+ },
151
+
152
+ // ========================================
153
+ // TASK CLAIMING - Prevent agent collisions
154
+ // ========================================
155
+ {
156
+ name: "claim_task",
157
+ description: "Claim exclusive ownership of a task before starting work. Prevents other agents from working on the same task. Claims expire after ttl_minutes (default 30). Always claim before starting work on a task.",
158
+ inputSchema: {
159
+ type: "object",
160
+ properties: {
161
+ task_id: { type: "string", description: "Task ID to claim" },
162
+ plan_id: { type: "string", description: "Plan ID" },
163
+ ttl_minutes: { type: "integer", description: "Claim duration in minutes (default 30)", default: 30 }
164
+ },
165
+ required: ["task_id", "plan_id"]
166
+ }
167
+ },
168
+ {
169
+ name: "release_task",
170
+ description: "Release a previously claimed task. Called automatically when you complete a task, but use this if you need to abandon work early.",
171
+ inputSchema: {
172
+ type: "object",
173
+ properties: {
174
+ task_id: { type: "string", description: "Task ID to release" },
175
+ plan_id: { type: "string", description: "Plan ID" }
176
+ },
177
+ required: ["task_id", "plan_id"]
178
+ }
179
+ },
180
+
135
181
  // ========================================
136
182
  // CONTEXT LOADING - Get everything you need
137
183
  // Use before starting work on a plan/goal
@@ -150,7 +196,7 @@ function setupTools(server) {
150
196
  },
151
197
  {
152
198
  name: "get_my_tasks",
153
- description: "Get tasks that need attention - blocked tasks, in-progress tasks, and next tasks to start. Perfect for heartbeat check-ins.",
199
+ description: "Get tasks that need attention - blocked tasks, in-progress tasks, and next tasks to start. Perfect for status check-ins.",
154
200
  inputSchema: {
155
201
  type: "object",
156
202
  properties: {
@@ -165,31 +211,6 @@ function setupTools(server) {
165
211
  }
166
212
  },
167
213
 
168
- // ========================================
169
- // KNOWLEDGE - Build persistent memory
170
- // ========================================
171
- {
172
- name: "add_learning",
173
- description: "Capture something you learned for future reference. This persists beyond the current session - other agents and future-you will benefit.",
174
- inputSchema: {
175
- type: "object",
176
- properties: {
177
- title: { type: "string", description: "Brief title summarizing the learning" },
178
- content: { type: "string", description: "What you learned - be specific and include context" },
179
- scope: { type: "string", enum: ["organization", "goal", "plan"], description: "Where to store this (defaults to organization)" },
180
- scope_id: { type: "string", description: "ID of the organization/goal/plan" },
181
- tags: { type: "array", items: { type: "string" }, description: "Tags for easier retrieval" },
182
- entry_type: {
183
- type: "string",
184
- enum: ["learning", "decision", "context", "constraint"],
185
- default: "learning",
186
- description: "Type of knowledge"
187
- }
188
- },
189
- required: ["title", "content"]
190
- }
191
- },
192
-
193
214
  // ========================================
194
215
  // MARKDOWN EXPORT/IMPORT - Filesystem pattern
195
216
  // ========================================
@@ -380,14 +401,20 @@ function setupTools(server) {
380
401
  status: {
381
402
  type: "string",
382
403
  description: "Node status",
383
- enum: ["not_started", "in_progress", "completed", "blocked"],
404
+ enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"],
384
405
  default: "not_started"
385
406
  },
386
407
  context: { type: "string", description: "Additional context for the node" },
387
408
  agent_instructions: { type: "string", description: "Instructions for AI agents working on this node" },
388
409
  acceptance_criteria: { type: "string", description: "Criteria for node completion" },
389
410
  due_date: { type: "string", description: "Due date (ISO format)" },
390
- metadata: { type: "object", description: "Additional metadata" }
411
+ metadata: { type: "object", description: "Additional metadata" },
412
+ task_mode: {
413
+ type: "string",
414
+ description: "RPI workflow mode for the node",
415
+ enum: ["research", "plan", "implement", "free"],
416
+ default: "free"
417
+ }
391
418
  },
392
419
  required: ["plan_id", "node_type", "title"]
393
420
  }
@@ -405,13 +432,18 @@ function setupTools(server) {
405
432
  status: {
406
433
  type: "string",
407
434
  description: "New node status",
408
- enum: ["not_started", "in_progress", "completed", "blocked"]
435
+ enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"]
409
436
  },
410
437
  context: { type: "string", description: "New context" },
411
438
  agent_instructions: { type: "string", description: "New agent instructions" },
412
439
  acceptance_criteria: { type: "string", description: "New acceptance criteria" },
413
440
  due_date: { type: "string", description: "New due date (ISO format)" },
414
- metadata: { type: "object", description: "New metadata" }
441
+ metadata: { type: "object", description: "New metadata" },
442
+ task_mode: {
443
+ type: "string",
444
+ description: "RPI workflow mode for the node",
445
+ enum: ["research", "plan", "implement", "free"]
446
+ }
415
447
  },
416
448
  required: ["plan_id", "node_id"]
417
449
  }
@@ -467,6 +499,168 @@ function setupTools(server) {
467
499
  }
468
500
  },
469
501
 
502
+ // ===== DEPENDENCY TOOLS =====
503
+ {
504
+ name: "create_dependency",
505
+ description: "Create a dependency edge between two nodes in a plan. Source 'blocks' target by default.",
506
+ inputSchema: {
507
+ type: "object",
508
+ properties: {
509
+ plan_id: { type: "string", description: "Plan ID" },
510
+ source_node_id: { type: "string", description: "Source node ID (the blocker)" },
511
+ target_node_id: { type: "string", description: "Target node ID (the blocked)" },
512
+ dependency_type: {
513
+ type: "string",
514
+ description: "Type of dependency",
515
+ enum: ["blocks", "requires", "relates_to"],
516
+ default: "blocks"
517
+ },
518
+ weight: { type: "integer", description: "Edge weight (default 1)", default: 1 },
519
+ metadata: { type: "object", description: "Additional metadata" }
520
+ },
521
+ required: ["plan_id", "source_node_id", "target_node_id"]
522
+ }
523
+ },
524
+ {
525
+ name: "delete_dependency",
526
+ description: "Delete a dependency edge",
527
+ inputSchema: {
528
+ type: "object",
529
+ properties: {
530
+ plan_id: { type: "string", description: "Plan ID" },
531
+ dependency_id: { type: "string", description: "Dependency edge ID" }
532
+ },
533
+ required: ["plan_id", "dependency_id"]
534
+ }
535
+ },
536
+ {
537
+ name: "list_dependencies",
538
+ description: "List all dependency edges in a plan",
539
+ inputSchema: {
540
+ type: "object",
541
+ properties: {
542
+ plan_id: { type: "string", description: "Plan ID" }
543
+ },
544
+ required: ["plan_id"]
545
+ }
546
+ },
547
+ {
548
+ name: "get_node_dependencies",
549
+ description: "Get upstream and downstream dependencies for a node",
550
+ inputSchema: {
551
+ type: "object",
552
+ properties: {
553
+ plan_id: { type: "string", description: "Plan ID" },
554
+ node_id: { type: "string", description: "Node ID" },
555
+ direction: {
556
+ type: "string",
557
+ description: "Direction to query",
558
+ enum: ["upstream", "downstream", "both"],
559
+ default: "both"
560
+ }
561
+ },
562
+ required: ["plan_id", "node_id"]
563
+ }
564
+ },
565
+
566
+ // ===== RPI WORKFLOW =====
567
+ {
568
+ name: "create_rpi_chain",
569
+ description: "Create a Research→Plan→Implement task chain with automatic dependency edges. The three tasks are linked: Research blocks Plan, Plan blocks Implement.",
570
+ inputSchema: {
571
+ type: "object",
572
+ properties: {
573
+ plan_id: { type: "string", description: "Plan ID" },
574
+ title: { type: "string", description: "Base title for the chain (e.g. 'Auth refactor')" },
575
+ description: { type: "string", description: "Description for the research task" },
576
+ parent_id: { type: "string", description: "Parent node ID (optional, defaults to root)" }
577
+ },
578
+ required: ["plan_id", "title"]
579
+ }
580
+ },
581
+
582
+ // ===== ANALYSIS TOOLS =====
583
+ {
584
+ name: "analyze_impact",
585
+ description: "Analyze what happens if a node is delayed, blocked, or removed. Shows directly and transitively affected nodes.",
586
+ inputSchema: {
587
+ type: "object",
588
+ properties: {
589
+ plan_id: { type: "string", description: "Plan ID" },
590
+ node_id: { type: "string", description: "Node ID to analyze" },
591
+ scenario: {
592
+ type: "string",
593
+ description: "Impact scenario",
594
+ enum: ["delay", "block", "remove"],
595
+ default: "block"
596
+ }
597
+ },
598
+ required: ["plan_id", "node_id"]
599
+ }
600
+ },
601
+ {
602
+ name: "get_critical_path",
603
+ description: "Find the critical path (longest dependency chain) through incomplete tasks in a plan",
604
+ inputSchema: {
605
+ type: "object",
606
+ properties: {
607
+ plan_id: { type: "string", description: "Plan ID" }
608
+ },
609
+ required: ["plan_id"]
610
+ }
611
+ },
612
+
613
+ // ===== PROGRESSIVE CONTEXT TOOLS =====
614
+ {
615
+ name: "get_task_context",
616
+ description: "Get progressive context for a task at adjustable depth. This is the PRIMARY way to load context before starting work on a task.\n\nDepth levels:\n- 1: Task focus — node details + recent logs\n- 2: Local neighborhood — adds parent, siblings, direct dependencies\n- 3: Knowledge — adds plan-scoped knowledge entries\n- 4: Extended — adds plan overview, ancestry, goals, transitive dependencies\n\nFor RPI implement tasks, automatically includes research/plan outputs from the chain.",
617
+ inputSchema: {
618
+ type: "object",
619
+ properties: {
620
+ node_id: { type: "string", description: "Task/node ID to get context for" },
621
+ depth: {
622
+ type: "integer",
623
+ description: "Context depth 1-4 (default 2). Start with 2, go deeper if needed.",
624
+ minimum: 1,
625
+ maximum: 4,
626
+ default: 2
627
+ },
628
+ token_budget: {
629
+ type: "integer",
630
+ description: "Max estimated tokens (0 = unlimited). Use to stay within context window limits.",
631
+ default: 0
632
+ },
633
+ log_limit: {
634
+ type: "integer",
635
+ description: "Max recent logs to include per node",
636
+ default: 10
637
+ },
638
+ include_research: {
639
+ type: "boolean",
640
+ description: "Include research outputs from RPI chain siblings (for implement tasks)",
641
+ default: true
642
+ }
643
+ },
644
+ required: ["node_id"]
645
+ }
646
+ },
647
+ {
648
+ name: "suggest_next_tasks",
649
+ description: "Suggest the next actionable tasks for a plan based on dependency analysis. Returns tasks where all upstream blockers are completed, prioritized by: RPI research tasks first, then by how many downstream tasks each unblocks.",
650
+ inputSchema: {
651
+ type: "object",
652
+ properties: {
653
+ plan_id: { type: "string", description: "Plan ID" },
654
+ limit: {
655
+ type: "integer",
656
+ description: "Maximum suggestions to return",
657
+ default: 5
658
+ }
659
+ },
660
+ required: ["plan_id"]
661
+ }
662
+ },
663
+
470
664
  // ===== LOGGING TOOLS (Replaces Comments) =====
471
665
  {
472
666
  name: "add_log",
@@ -532,7 +726,7 @@ function setupTools(server) {
532
726
  node_id: { type: "string", description: "Node ID" },
533
727
  status: {
534
728
  type: "string",
535
- enum: ["not_started", "in_progress", "completed", "blocked"]
729
+ enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"]
536
730
  },
537
731
  title: { type: "string" },
538
732
  description: { type: "string" }
@@ -766,102 +960,174 @@ function setupTools(server) {
766
960
  }
767
961
  },
768
962
 
769
- // ===== KNOWLEDGE TOOLS =====
963
+ // ===== CROSS-PLAN & EXTERNAL DEPENDENCY TOOLS =====
770
964
  {
771
- name: "add_knowledge_entry",
772
- description: "Add a knowledge entry (decision, context, constraint, learning, reference, note) to a scope",
965
+ name: "create_cross_plan_dependency",
966
+ description: "Create a dependency edge between nodes in different plans. Use when a task in one plan blocks or requires a task in another plan.",
773
967
  inputSchema: {
774
968
  type: "object",
775
969
  properties: {
776
- scope: {
777
- type: "string",
778
- description: "Scope type",
779
- enum: ["organization", "goal", "plan"]
780
- },
781
- scope_id: { type: "string", description: "ID of the org, goal, or plan" },
782
- entry_type: {
783
- type: "string",
784
- description: "Type of knowledge entry",
785
- enum: ["decision", "context", "constraint", "learning", "reference", "note"]
786
- },
787
- title: { type: "string", description: "Entry title" },
788
- content: { type: "string", description: "Entry content" },
789
- source_url: { type: "string", description: "Source URL (optional)" },
790
- tags: { type: "array", description: "Tags for categorization", items: { type: "string" } }
970
+ source_node_id: { type: "string", description: "Source node ID (the blocker/prerequisite)" },
971
+ target_node_id: { type: "string", description: "Target node ID (the blocked/dependent task)" },
972
+ dependency_type: { type: "string", enum: ["blocks", "requires", "relates_to"], default: "blocks", description: "Edge type (default: blocks)" },
973
+ weight: { type: "number", description: "Edge weight (default 1)" }
791
974
  },
792
- required: ["scope", "scope_id", "entry_type", "title", "content"]
975
+ required: ["source_node_id", "target_node_id"]
793
976
  }
794
977
  },
795
978
  {
796
- name: "list_knowledge_entries",
797
- description: "List knowledge entries for a scope",
979
+ name: "list_cross_plan_dependencies",
980
+ description: "List all dependency edges that cross plan boundaries between specified plans.",
798
981
  inputSchema: {
799
982
  type: "object",
800
983
  properties: {
801
- scope: {
802
- type: "string",
803
- description: "Scope type",
804
- enum: ["organization", "goal", "plan"]
805
- },
806
- scope_id: { type: "string", description: "ID of the org, goal, or plan" },
807
- entry_type: {
808
- type: "string",
809
- description: "Filter by entry type",
810
- enum: ["decision", "context", "constraint", "learning", "reference", "note"]
811
- },
812
- tags: { type: "string", description: "Filter by tags (comma-separated)" },
813
- limit: { type: "integer", description: "Max entries to return", default: 50 }
984
+ plan_ids: {
985
+ type: "array",
986
+ items: { type: "string" },
987
+ description: "Plan IDs to check for cross-plan edges (at least 2)"
988
+ }
814
989
  },
815
- required: ["scope", "scope_id"]
990
+ required: ["plan_ids"]
816
991
  }
817
992
  },
818
993
  {
819
- name: "search_knowledge",
820
- description: "Search knowledge entries across scopes using text search",
994
+ name: "create_external_dependency",
995
+ description: "Create an external dependency node representing a blocker outside the system (vendor API, legal approval, etc.). Optionally blocks a target task.",
821
996
  inputSchema: {
822
997
  type: "object",
823
998
  properties: {
824
- query: { type: "string", description: "Search query" },
825
- scope: {
826
- type: "string",
827
- description: "Limit to scope type",
828
- enum: ["organization", "goal", "plan"]
829
- },
830
- scope_id: { type: "string", description: "Limit to specific scope" },
831
- entry_types: {
832
- type: "array",
833
- description: "Filter by entry types",
834
- items: { type: "string" }
835
- },
836
- limit: { type: "integer", description: "Max results", default: 10 }
999
+ plan_id: { type: "string", description: "Plan to add the external dependency to" },
1000
+ title: { type: "string", description: "External dependency title (e.g., 'Waiting for vendor API access')" },
1001
+ description: { type: "string", description: "Details about the external dependency" },
1002
+ url: { type: "string", description: "URL reference (ticket, docs, etc.)" },
1003
+ blocks_node_id: { type: "string", description: "Node ID that this external dep blocks" }
1004
+ },
1005
+ required: ["plan_id", "title"]
1006
+ }
1007
+ },
1008
+
1009
+ // ===== GOAL-DEPENDENCY TOOLS =====
1010
+ {
1011
+ name: "goal_path",
1012
+ description: "Get the full dependency path to a goal — all tasks that contribute to achieving it (direct achievers + their upstream blockers). Shows completion stats and which tasks are blocking progress.",
1013
+ inputSchema: {
1014
+ type: "object",
1015
+ properties: {
1016
+ goal_id: { type: "string", description: "Goal ID" },
1017
+ max_depth: { type: "number", description: "Max traversal depth (default 20)" }
1018
+ },
1019
+ required: ["goal_id"]
1020
+ }
1021
+ },
1022
+ {
1023
+ name: "goal_progress",
1024
+ description: "Get goal progress calculated from its dependency graph. Returns overall completion percentage and direct achiever progress.",
1025
+ inputSchema: {
1026
+ type: "object",
1027
+ properties: {
1028
+ goal_id: { type: "string", description: "Goal ID" }
1029
+ },
1030
+ required: ["goal_id"]
1031
+ }
1032
+ },
1033
+ {
1034
+ name: "add_achiever",
1035
+ description: "Link a task to a goal via an 'achieves' dependency edge. This declares that completing this task contributes to achieving the goal.",
1036
+ inputSchema: {
1037
+ type: "object",
1038
+ properties: {
1039
+ goal_id: { type: "string", description: "Goal ID" },
1040
+ node_id: { type: "string", description: "Task/node ID that achieves this goal" },
1041
+ weight: { type: "number", description: "Edge weight for critical path (default 1)" }
1042
+ },
1043
+ required: ["goal_id", "node_id"]
1044
+ }
1045
+ },
1046
+ {
1047
+ name: "remove_achiever",
1048
+ description: "Remove an achieves edge between a task and a goal",
1049
+ inputSchema: {
1050
+ type: "object",
1051
+ properties: {
1052
+ goal_id: { type: "string", description: "Goal ID" },
1053
+ dependency_id: { type: "string", description: "Dependency edge ID to remove" }
1054
+ },
1055
+ required: ["goal_id", "dependency_id"]
1056
+ }
1057
+ },
1058
+ {
1059
+ name: "goal_knowledge_gaps",
1060
+ description: "Detect knowledge gaps for a goal — checks which tasks on the goal's dependency path lack relevant knowledge in the temporal knowledge graph. Useful for identifying where research is needed before implementation.",
1061
+ inputSchema: {
1062
+ type: "object",
1063
+ properties: {
1064
+ goal_id: { type: "string", description: "Goal ID" }
1065
+ },
1066
+ required: ["goal_id"]
1067
+ }
1068
+ },
1069
+
1070
+ // ===== GRAPHITI KNOWLEDGE GRAPH TOOLS =====
1071
+ {
1072
+ name: "add_learning",
1073
+ description: "Record a knowledge episode to the temporal knowledge graph. Use this after research, when making decisions, or discovering important context. Graphiti automatically extracts entities and relationships. The knowledge persists across plans and sessions.",
1074
+ inputSchema: {
1075
+ type: "object",
1076
+ properties: {
1077
+ content: { type: "string", description: "The knowledge content — be detailed. Include context, reasoning, and conclusions." },
1078
+ title: { type: "string", description: "Short title/name for the episode" },
1079
+ entry_type: { type: "string", enum: ["decision", "learning", "context", "constraint"], description: "Type of knowledge" },
1080
+ plan_id: { type: "string", description: "Plan ID this knowledge relates to (optional)" },
1081
+ node_id: { type: "string", description: "Node/task ID this knowledge relates to (optional)" }
1082
+ },
1083
+ required: ["content"]
1084
+ }
1085
+ },
1086
+ {
1087
+ name: "recall_knowledge",
1088
+ description: "Search the temporal knowledge graph for relevant facts, decisions, and learnings. Searches across ALL plans in the organization. Use before starting work or making decisions.",
1089
+ inputSchema: {
1090
+ type: "object",
1091
+ properties: {
1092
+ query: { type: "string", description: "What to search for — be specific" },
1093
+ max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
837
1094
  },
838
1095
  required: ["query"]
839
1096
  }
840
1097
  },
841
1098
  {
842
- name: "update_knowledge_entry",
843
- description: "Update a knowledge entry",
1099
+ name: "find_entities",
1100
+ description: "Search for entities (technologies, people, patterns, constraints) in the knowledge graph. Returns entity nodes with their relationships.",
844
1101
  inputSchema: {
845
1102
  type: "object",
846
1103
  properties: {
847
- entry_id: { type: "string", description: "Entry ID" },
848
- title: { type: "string", description: "New title" },
849
- content: { type: "string", description: "New content" },
850
- entry_type: { type: "string", description: "New type" },
851
- tags: { type: "array", description: "New tags", items: { type: "string" } }
1104
+ query: { type: "string", description: "Entity search query" },
1105
+ max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
852
1106
  },
853
- required: ["entry_id"]
1107
+ required: ["query"]
854
1108
  }
855
1109
  },
1110
+
856
1111
  {
857
- name: "delete_knowledge_entry",
858
- description: "Delete a knowledge entry",
1112
+ name: "check_contradictions",
1113
+ description: "Check if knowledge about a topic has changed over time. Returns current facts and any superseded (outdated) facts. Useful before making decisions based on past knowledge — ensures you're working with the latest information.",
859
1114
  inputSchema: {
860
1115
  type: "object",
861
1116
  properties: {
862
- entry_id: { type: "string", description: "Entry ID to delete" }
1117
+ query: { type: "string", description: "Topic to check for contradictions" },
1118
+ max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
863
1119
  },
864
- required: ["entry_id"]
1120
+ required: ["query"]
1121
+ }
1122
+ },
1123
+ {
1124
+ name: "get_recent_episodes",
1125
+ description: "Get recent knowledge episodes from the temporal graph. Returns the latest episodes (learnings, decisions, context) across all plans. Useful to understand what has been learned recently or to review your own work session history.",
1126
+ inputSchema: {
1127
+ type: "object",
1128
+ properties: {
1129
+ max_episodes: { type: "number", description: "Maximum episodes to return (default 20)", default: 20 }
1130
+ }
865
1131
  }
866
1132
  },
867
1133
 
@@ -967,7 +1233,7 @@ function setupTools(server) {
967
1233
  success: true,
968
1234
  message: `Plan "${title}" created with ${tasks.length} tasks`,
969
1235
  plan_id: plan.id,
970
- plan_url: `https://www.agentplanner.io/app/plans/${plan.id}`,
1236
+ plan_url: buildPlanUrl(plan.id),
971
1237
  phase_id: phase.id,
972
1238
  task_ids: createdTasks.map(t => t.id),
973
1239
  tasks: createdTasks,
@@ -1029,7 +1295,7 @@ function setupTools(server) {
1029
1295
  task_id: task.id,
1030
1296
  plan_id: plan_id,
1031
1297
  phase_id: targetPhaseId,
1032
- task_url: `https://www.agentplanner.io/app/plans/${plan_id}?node=${task.id}`,
1298
+ task_url: buildTaskUrl(plan_id, task.id),
1033
1299
  next_steps: [
1034
1300
  "Use quick_status to mark as in_progress when you start",
1035
1301
  "Use quick_log to document progress"
@@ -1132,12 +1398,8 @@ function setupTools(server) {
1132
1398
  context.goal = await apiClient.goals.get(goal_id);
1133
1399
  if (include_knowledge) {
1134
1400
  try {
1135
- const knowledge = await apiClient.knowledge.getEntries({
1136
- scope: 'goal',
1137
- scope_id: goal_id,
1138
- limit: 10
1139
- });
1140
- context.goal_knowledge = knowledge.entries || [];
1401
+ const graphResult = await apiClient.graphiti.graphSearch({ query: context.goal?.title || '', max_results: 10 });
1402
+ context.goal_knowledge = graphResult?.results?.facts || [];
1141
1403
  } catch (e) {}
1142
1404
  }
1143
1405
  } catch (e) {
@@ -1149,7 +1411,7 @@ function setupTools(server) {
1149
1411
  if (plan_id) {
1150
1412
  try {
1151
1413
  context.plan = await apiClient.plans.getPlan(plan_id);
1152
- context.plan_url = `https://www.agentplanner.io/app/plans/${plan_id}`;
1414
+ context.plan_url = buildPlanUrl(plan_id);
1153
1415
 
1154
1416
  const nodes = await apiClient.nodes.getNodes(plan_id);
1155
1417
  context.statistics = calculatePlanStatistics(nodes);
@@ -1178,12 +1440,8 @@ function setupTools(server) {
1178
1440
 
1179
1441
  if (include_knowledge) {
1180
1442
  try {
1181
- const knowledge = await apiClient.knowledge.getEntries({
1182
- scope: 'plan',
1183
- scope_id: plan_id,
1184
- limit: 10
1185
- });
1186
- context.plan_knowledge = knowledge.entries || [];
1443
+ const graphResult = await apiClient.graphiti.graphSearch({ query: context.plan?.title || '', max_results: 10 });
1444
+ context.plan_knowledge = graphResult?.results?.facts || [];
1187
1445
  } catch (e) {}
1188
1446
  }
1189
1447
  } catch (e) {
@@ -1260,34 +1518,8 @@ function setupTools(server) {
1260
1518
  return formatResponse(tasks);
1261
1519
  }
1262
1520
 
1263
- // ========================================
1264
- // KNOWLEDGE SHORTCUTS
1265
- // ========================================
1266
-
1267
- if (name === "add_learning") {
1268
- const { title, content, scope = 'organization', scope_id, tags, entry_type = 'learning' } = args;
1269
-
1270
- const entryData = {
1271
- entry_type,
1272
- title,
1273
- content
1274
- };
1275
- if (scope) entryData.scope = scope;
1276
- if (scope_id) entryData.scope_id = scope_id;
1277
- if (tags) entryData.tags = tags;
1278
-
1279
- const entry = await apiClient.knowledge.createEntry(entryData);
1280
-
1281
- return formatResponse({
1282
- success: true,
1283
- message: "Knowledge captured for future reference",
1284
- entry_id: entry.id,
1285
- entry_type,
1286
- title,
1287
- tip: "This will be searchable via search_knowledge. Good practice!"
1288
- });
1289
- }
1290
-
1521
+ // add_learning handled in GRAPHITI KNOWLEDGE GRAPH HANDLERS section below
1522
+
1291
1523
  // ========================================
1292
1524
  // MARKDOWN EXPORT
1293
1525
  // ========================================
@@ -1475,7 +1707,7 @@ function setupTools(server) {
1475
1707
  success: true,
1476
1708
  message: `Plan "${planTitle}" created from markdown with ${phases.length} phases and ${createdTasks.length} tasks`,
1477
1709
  plan_id: plan.id,
1478
- plan_url: `https://www.agentplanner.io/app/plans/${plan.id}`,
1710
+ plan_url: buildPlanUrl(plan.id),
1479
1711
  phases: createdPhases,
1480
1712
  tasks: createdTasks,
1481
1713
  next_steps: [
@@ -1600,7 +1832,7 @@ function setupTools(server) {
1600
1832
  const result = await apiClient.plans.updateVisibility(plan_id, visibilityData);
1601
1833
 
1602
1834
  const shareUrl = visibility === "public"
1603
- ? `https://www.agentplanner.io/app/plans/${plan_id}`
1835
+ ? buildPlanUrl(plan_id)
1604
1836
  : null;
1605
1837
 
1606
1838
  return formatResponse({
@@ -1641,27 +1873,29 @@ function setupTools(server) {
1641
1873
 
1642
1874
  if (name === "move_node") {
1643
1875
  const { plan_id, node_id, parent_id, order_index } = args;
1644
-
1876
+
1645
1877
  try {
1878
+ // Build request body with only provided fields - don't send nulls
1879
+ const body = {};
1880
+ if (parent_id) body.parent_id = parent_id;
1881
+ if (order_index !== undefined) body.order_index = order_index;
1882
+
1646
1883
  // Call the move endpoint - using POST as per API definition
1647
1884
  const response = await apiClient.axiosInstance.post(
1648
1885
  `/plans/${plan_id}/nodes/${node_id}/move`,
1649
- {
1650
- parent_id: parent_id || null,
1651
- order_index: order_index !== undefined ? order_index : null
1652
- }
1886
+ body
1653
1887
  );
1654
-
1888
+
1655
1889
  return formatResponse(response.data);
1656
1890
  } catch (error) {
1657
1891
  // If endpoint still doesn't work, try updating the node directly
1658
1892
  if (error.response && error.response.status === 404) {
1659
1893
  console.error('Move endpoint not found, trying direct update');
1660
1894
  // Fallback to updating the node's parent_id via regular update
1661
- const updateResponse = await apiClient.nodes.updateNode(plan_id, node_id, {
1662
- parent_id: parent_id || null,
1663
- order_index: order_index !== undefined ? order_index : null
1664
- });
1895
+ const updateData = {};
1896
+ if (parent_id) updateData.parent_id = parent_id;
1897
+ if (order_index !== undefined) updateData.order_index = order_index;
1898
+ const updateResponse = await apiClient.nodes.updateNode(plan_id, node_id, updateData);
1665
1899
  return formatResponse(updateResponse);
1666
1900
  }
1667
1901
  throw error;
@@ -1690,6 +1924,90 @@ function setupTools(server) {
1690
1924
  return formatResponse(response.data);
1691
1925
  }
1692
1926
 
1927
+ // ===== DEPENDENCIES =====
1928
+ if (name === "create_dependency") {
1929
+ const { plan_id, source_node_id, target_node_id, dependency_type, weight, metadata } = args;
1930
+ const response = await apiClient.axiosInstance.post(
1931
+ `/plans/${plan_id}/dependencies`,
1932
+ { source_node_id, target_node_id, dependency_type, weight, metadata }
1933
+ );
1934
+ return formatResponse(response.data);
1935
+ }
1936
+
1937
+ if (name === "delete_dependency") {
1938
+ const { plan_id, dependency_id } = args;
1939
+ const response = await apiClient.axiosInstance.delete(
1940
+ `/plans/${plan_id}/dependencies/${dependency_id}`
1941
+ );
1942
+ return formatResponse(response.data);
1943
+ }
1944
+
1945
+ if (name === "list_dependencies") {
1946
+ const { plan_id } = args;
1947
+ const response = await apiClient.axiosInstance.get(
1948
+ `/plans/${plan_id}/dependencies`
1949
+ );
1950
+ return formatResponse(response.data);
1951
+ }
1952
+
1953
+ if (name === "get_node_dependencies") {
1954
+ const { plan_id, node_id, direction = 'both' } = args;
1955
+ const response = await apiClient.axiosInstance.get(
1956
+ `/plans/${plan_id}/nodes/${node_id}/dependencies`,
1957
+ { params: { direction } }
1958
+ );
1959
+ return formatResponse(response.data);
1960
+ }
1961
+
1962
+ // ===== RPI WORKFLOW =====
1963
+ if (name === "create_rpi_chain") {
1964
+ const { plan_id, title, description, parent_id } = args;
1965
+ const response = await apiClient.axiosInstance.post(
1966
+ `/plans/${plan_id}/nodes/rpi-chain`,
1967
+ { title, description, parent_id }
1968
+ );
1969
+ return formatResponse(response.data);
1970
+ }
1971
+
1972
+ // ===== ANALYSIS =====
1973
+ if (name === "analyze_impact") {
1974
+ const { plan_id, node_id, scenario = 'block' } = args;
1975
+ const response = await apiClient.axiosInstance.get(
1976
+ `/plans/${plan_id}/nodes/${node_id}/impact`,
1977
+ { params: { scenario } }
1978
+ );
1979
+ return formatResponse(response.data);
1980
+ }
1981
+
1982
+ if (name === "get_critical_path") {
1983
+ const { plan_id } = args;
1984
+ const response = await apiClient.axiosInstance.get(
1985
+ `/plans/${plan_id}/critical-path`
1986
+ );
1987
+ return formatResponse(response.data);
1988
+ }
1989
+
1990
+ // ===== PROGRESSIVE CONTEXT =====
1991
+ if (name === "get_task_context") {
1992
+ const { node_id, depth = 2, token_budget = 0, log_limit = 10, include_research = true } = args;
1993
+ const params = new URLSearchParams({
1994
+ node_id,
1995
+ depth: String(depth),
1996
+ token_budget: String(token_budget),
1997
+ log_limit: String(log_limit),
1998
+ include_research: String(include_research),
1999
+ });
2000
+ const response = await apiClient.axiosInstance.get(`/context/progressive?${params.toString()}`);
2001
+ return formatResponse(response.data);
2002
+ }
2003
+
2004
+ if (name === "suggest_next_tasks") {
2005
+ const { plan_id, limit = 5 } = args;
2006
+ const params = new URLSearchParams({ plan_id, limit: String(limit) });
2007
+ const response = await apiClient.axiosInstance.get(`/context/suggest?${params.toString()}`);
2008
+ return formatResponse(response.data);
2009
+ }
2010
+
1693
2011
  // ===== LOGGING =====
1694
2012
  if (name === "add_log") {
1695
2013
  const { plan_id, node_id, content, log_type = "comment", tags } = args;
@@ -1901,60 +2219,154 @@ function setupTools(server) {
1901
2219
  });
1902
2220
  }
1903
2221
 
1904
- // ===== KNOWLEDGE TOOLS =====
1905
- if (name === "add_knowledge_entry") {
1906
- const { scope, scope_id, entry_type, title, content, source_url, tags } = args;
1907
- const result = await apiClient.knowledge.createEntry({
1908
- scope,
1909
- scope_id,
1910
- entry_type,
1911
- title,
1912
- content,
1913
- source_url,
1914
- tags
2222
+ // ===== CROSS-PLAN & EXTERNAL DEPENDENCY HANDLERS =====
2223
+ if (name === "create_cross_plan_dependency") {
2224
+ const { source_node_id, target_node_id, dependency_type, weight } = args;
2225
+ const result = await apiClient.dependencies.createCrossPlan({
2226
+ source_node_id, target_node_id, dependency_type, weight
1915
2227
  });
1916
2228
  return formatResponse(result);
1917
2229
  }
1918
-
1919
- if (name === "list_knowledge_entries") {
1920
- const { scope, scope_id, entry_type, tags, limit = 50 } = args;
1921
2230
 
1922
- const result = await apiClient.knowledge.listEntries({ scope, scope_id, entry_type, tags, limit });
2231
+ if (name === "list_cross_plan_dependencies") {
2232
+ const { plan_ids } = args;
2233
+ const result = await apiClient.dependencies.listCrossPlan(plan_ids);
1923
2234
  return formatResponse(result);
1924
2235
  }
1925
-
1926
- if (name === "search_knowledge") {
1927
- const { query, scope, scope_id, entry_types, limit = 10 } = args;
1928
-
1929
- // Build search request
1930
- const searchData = { query, limit };
1931
- if (scope && scope_id) {
1932
- searchData.scope = scope;
1933
- searchData.scope_id = scope_id;
1934
- }
1935
- if (entry_types) {
1936
- searchData.entry_types = entry_types;
1937
- }
1938
-
1939
- const result = await apiClient.knowledge.search(searchData);
2236
+
2237
+ if (name === "create_external_dependency") {
2238
+ const { plan_id, title, description, url, blocks_node_id } = args;
2239
+ const result = await apiClient.dependencies.createExternal({
2240
+ plan_id, title, description, url, blocks_node_id
2241
+ });
1940
2242
  return formatResponse(result);
1941
2243
  }
1942
-
1943
- if (name === "update_knowledge_entry") {
1944
- const { entry_id, ...updateData } = args;
1945
- const result = await apiClient.knowledge.updateEntry(entry_id, updateData);
2244
+
2245
+ // ===== GOAL-DEPENDENCY HANDLERS =====
2246
+ if (name === "goal_path") {
2247
+ const { goal_id, max_depth } = args;
2248
+ const result = await apiClient.goals.getPath(goal_id, max_depth);
1946
2249
  return formatResponse(result);
1947
2250
  }
1948
-
1949
- if (name === "delete_knowledge_entry") {
1950
- const { entry_id } = args;
1951
- await apiClient.knowledge.deleteEntry(entry_id);
2251
+
2252
+ if (name === "goal_progress") {
2253
+ const { goal_id } = args;
2254
+ const result = await apiClient.goals.getProgress(goal_id);
2255
+ return formatResponse(result);
2256
+ }
2257
+
2258
+ if (name === "add_achiever") {
2259
+ const { goal_id, node_id, weight } = args;
2260
+ const result = await apiClient.goals.addAchiever(goal_id, node_id, weight);
1952
2261
  return formatResponse({
1953
- success: true,
1954
- message: `Knowledge entry ${entry_id} deleted`
2262
+ ...result,
2263
+ message: `Task ${node_id} now achieves goal ${goal_id}`,
2264
+ });
2265
+ }
2266
+
2267
+ if (name === "remove_achiever") {
2268
+ const { goal_id, dependency_id } = args;
2269
+ const result = await apiClient.goals.removeAchiever(goal_id, dependency_id);
2270
+ return formatResponse(result);
2271
+ }
2272
+
2273
+ if (name === "goal_knowledge_gaps") {
2274
+ const { goal_id } = args;
2275
+ const result = await apiClient.goals.getKnowledgeGaps(goal_id);
2276
+ return formatResponse(result);
2277
+ }
2278
+
2279
+ // ===== GRAPHITI KNOWLEDGE GRAPH HANDLERS =====
2280
+ if (name === "add_learning") {
2281
+ const { content, title, entry_type, plan_id, node_id } = args;
2282
+
2283
+ // Add to Graphiti temporal knowledge graph
2284
+ const result = await apiClient.graphiti.addEpisode({
2285
+ content,
2286
+ name: title,
2287
+ plan_id,
2288
+ node_id,
2289
+ metadata: { entry_type: entry_type || 'learning' },
2290
+ });
2291
+ return formatResponse({
2292
+ ...result,
2293
+ message: 'Knowledge recorded in temporal graph',
2294
+ tip: 'This is now searchable via recall_knowledge across all plans'
1955
2295
  });
1956
2296
  }
1957
2297
 
2298
+ if (name === "recall_knowledge") {
2299
+ const { query, max_results = 10 } = args;
2300
+
2301
+ // Try Graphiti first (temporal, cross-plan)
2302
+ try {
2303
+ const graphResult = await apiClient.graphiti.graphSearch({ query, max_results });
2304
+ if (graphResult?.results) {
2305
+ return formatResponse({
2306
+ ...graphResult,
2307
+ source: 'graphiti_temporal_graph'
2308
+ });
2309
+ }
2310
+ } catch (err) {
2311
+ return formatResponse({
2312
+ results: [],
2313
+ source: 'graphiti_temporal_graph',
2314
+ error: 'Knowledge graph not available: ' + err.message,
2315
+ });
2316
+ }
2317
+ }
2318
+
2319
+ if (name === "find_entities") {
2320
+ const { query, max_results = 10 } = args;
2321
+
2322
+ try {
2323
+ const result = await apiClient.graphiti.searchEntities({ query, max_results });
2324
+ return formatResponse(result);
2325
+ } catch (err) {
2326
+ return formatResponse({
2327
+ error: 'Entity search requires the temporal knowledge graph (Graphiti)',
2328
+ detail: err.message
2329
+ });
2330
+ }
2331
+ }
2332
+
2333
+ if (name === "check_contradictions") {
2334
+ const { query, max_results = 10 } = args;
2335
+
2336
+ try {
2337
+ const result = await apiClient.graphiti.detectContradictions({ query, max_results });
2338
+ if (result.contradictions_found) {
2339
+ return formatResponse({
2340
+ ...result,
2341
+ warning: 'Some knowledge has been superseded. Review the "superseded" facts before proceeding.',
2342
+ });
2343
+ }
2344
+ return formatResponse({
2345
+ ...result,
2346
+ message: 'No contradictions found — all facts are current.',
2347
+ });
2348
+ } catch (err) {
2349
+ return formatResponse({
2350
+ error: 'Contradiction detection requires the temporal knowledge graph (Graphiti)',
2351
+ detail: err.message,
2352
+ });
2353
+ }
2354
+ }
2355
+
2356
+ if (name === "get_recent_episodes") {
2357
+ const { max_episodes = 20 } = args || {};
2358
+
2359
+ try {
2360
+ const result = await apiClient.graphiti.getEpisodes({ max_episodes });
2361
+ return formatResponse(result);
2362
+ } catch (err) {
2363
+ return formatResponse({
2364
+ error: 'Episodic memory requires the temporal knowledge graph (Graphiti)',
2365
+ detail: err.message
2366
+ });
2367
+ }
2368
+ }
2369
+
1958
2370
  // ===== HELPER TOOLS =====
1959
2371
  if (name === "get_started") {
1960
2372
  const { topic = "overview" } = args || {};
@@ -1975,8 +2387,8 @@ function setupTools(server) {
1975
2387
  "2. Use list_plans to see existing plans",
1976
2388
  "3. Before working on a plan, use understand_context to get the full picture",
1977
2389
  "4. Update task statuses as you work (update_node with status)",
1978
- "5. Store important decisions and learnings using add_knowledge_entry",
1979
- "6. Check search_knowledge before making decisions to see past context"
2390
+ "5. Store important decisions and learnings using add_learning",
2391
+ "6. Check recall_knowledge before making decisions to see past context"
1980
2392
  ],
1981
2393
  quick_tips: [
1982
2394
  "Always capture WHY decisions were made, not just WHAT",
@@ -2008,7 +2420,7 @@ function setupTools(server) {
2008
2420
  workflow: [
2009
2421
  "1. Use get_plan_structure to see the full plan",
2010
2422
  "2. Find tasks with status 'not_started' or 'in_progress'",
2011
- "3. Before starting a task, check search_knowledge for relevant context",
2423
+ "3. Before starting a task, check recall_knowledge for relevant context",
2012
2424
  "4. Update task status to 'in_progress' when you begin",
2013
2425
  "5. Add logs to document what you're doing",
2014
2426
  "6. Mark 'completed' when done, or 'blocked' if stuck"
@@ -2025,7 +2437,7 @@ function setupTools(server) {
2025
2437
  "When blocked, clearly document what's blocking you",
2026
2438
  "Store learnings as you go - don't wait until the end"
2027
2439
  ],
2028
- tools_to_use: ["get_plan_structure", "update_node", "add_log", "search_knowledge"]
2440
+ tools_to_use: ["get_plan_structure", "update_node", "add_log", "recall_knowledge"]
2029
2441
  },
2030
2442
  knowledge: {
2031
2443
  title: "Knowledge Management",
@@ -2051,7 +2463,7 @@ function setupTools(server) {
2051
2463
  "When you discover a constraint or rule",
2052
2464
  "When you find a useful resource or reference"
2053
2465
  ],
2054
- tools_to_use: ["add_knowledge_entry", "search_knowledge", "list_knowledge_entries"]
2466
+ tools_to_use: ["add_learning", "recall_knowledge", "find_entities", "check_contradictions"]
2055
2467
  },
2056
2468
  collaboration: {
2057
2469
  title: "Collaboration",
@@ -2095,12 +2507,8 @@ function setupTools(server) {
2095
2507
  // Get related knowledge if available
2096
2508
  if (include_knowledge) {
2097
2509
  try {
2098
- const knowledge = await apiClient.knowledge.getEntries({
2099
- scope: 'goal',
2100
- scope_id: goal_id,
2101
- limit: 10
2102
- });
2103
- context.goal_knowledge = knowledge.entries || [];
2510
+ const graphResult = await apiClient.graphiti.graphSearch({ query: context.goal?.title || '', max_results: 10 });
2511
+ context.goal_knowledge = graphResult?.results?.facts || [];
2104
2512
  } catch (e) {
2105
2513
  // Knowledge fetch failed, continue without it
2106
2514
  }
@@ -2147,12 +2555,8 @@ function setupTools(server) {
2147
2555
  // Get related knowledge if available
2148
2556
  if (include_knowledge) {
2149
2557
  try {
2150
- const knowledge = await apiClient.knowledge.getEntries({
2151
- scope: 'plan',
2152
- scope_id: plan_id,
2153
- limit: 10
2154
- });
2155
- context.plan_knowledge = knowledge.entries || [];
2558
+ const graphResult = await apiClient.graphiti.graphSearch({ query: context.plan?.title || '', max_results: 10 });
2559
+ context.plan_knowledge = graphResult?.results?.facts || [];
2156
2560
  } catch (e) {
2157
2561
  // Knowledge fetch failed, continue without it
2158
2562
  }
@@ -2166,6 +2570,45 @@ function setupTools(server) {
2166
2570
  return formatResponse(context);
2167
2571
  }
2168
2572
 
2573
+ // ===== GOALS HEALTH DASHBOARD =====
2574
+ if (name === "check_goals_health") {
2575
+ const { status_filter } = args || {};
2576
+ const result = await apiClient.goals.getDashboard();
2577
+
2578
+ let goals = result.goals || result;
2579
+ if (status_filter && Array.isArray(goals)) {
2580
+ goals = goals.filter(g => g.health_status === status_filter || g.status === status_filter);
2581
+ }
2582
+
2583
+ return formatResponse({
2584
+ ...result,
2585
+ goals,
2586
+ tip: "Prioritize: stale goals first, then at_risk, then on_track."
2587
+ });
2588
+ }
2589
+
2590
+ // ===== TASK CLAIMING =====
2591
+ if (name === "claim_task") {
2592
+ const { task_id, plan_id, ttl_minutes = 30 } = args;
2593
+ const result = await apiClient.nodes.claimTask(plan_id, task_id, 'mcp-agent', ttl_minutes);
2594
+ return formatResponse({
2595
+ success: true,
2596
+ message: `Task ${task_id} claimed for ${ttl_minutes} minutes`,
2597
+ ...result,
2598
+ tip: "Remember to release the task when done, or it will auto-expire."
2599
+ });
2600
+ }
2601
+
2602
+ if (name === "release_task") {
2603
+ const { task_id, plan_id } = args;
2604
+ const result = await apiClient.nodes.releaseTask(plan_id, task_id, 'mcp-agent');
2605
+ return formatResponse({
2606
+ success: true,
2607
+ message: `Task ${task_id} released`,
2608
+ ...result
2609
+ });
2610
+ }
2611
+
2169
2612
  // Tool not found
2170
2613
  throw new Error(`Unknown tool: ${name}`);
2171
2614
  } catch (error) {