agent-planner-mcp 0.5.0 → 0.6.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.
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,61 +138,64 @@ 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
+
135
152
  // ========================================
136
- // CONTEXT LOADING - Get everything you need
137
- // Use before starting work on a plan/goal
153
+ // TASK CLAIMING - Prevent agent collisions
138
154
  // ========================================
139
155
  {
140
- name: "get_context",
141
- description: "Load EVERYTHING you need to work on a plan or goal: structure, status, progress, blocked tasks, recent activity, and relevant knowledge. Call this FIRST before starting work.",
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.",
142
158
  inputSchema: {
143
159
  type: "object",
144
160
  properties: {
145
- plan_id: { type: "string", description: "Plan to get context for" },
146
- goal_id: { type: "string", description: "Goal to get context for" },
147
- include_knowledge: { type: "boolean", default: true, description: "Include relevant knowledge entries" }
148
- }
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"]
149
166
  }
150
167
  },
151
168
  {
152
- 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.",
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.",
154
171
  inputSchema: {
155
172
  type: "object",
156
173
  properties: {
157
- plan_id: { type: "string", description: "Specific plan to check (optional - checks all if not provided)" },
158
- status: {
159
- type: "array",
160
- items: { type: "string" },
161
- default: ["blocked", "in_progress"],
162
- description: "Task statuses to include"
163
- }
164
- }
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"]
165
178
  }
166
179
  },
167
180
 
168
181
  // ========================================
169
- // KNOWLEDGE - Build persistent memory
182
+ // CONTEXT LOADING - Get everything you need
183
+ // Use before starting work on a plan/goal
170
184
  // ========================================
171
185
  {
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.",
186
+ name: "get_my_tasks",
187
+ description: "Get tasks that need attention - blocked tasks, in-progress tasks, and next tasks to start. Perfect for status check-ins.",
174
188
  inputSchema: {
175
189
  type: "object",
176
190
  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"
191
+ plan_id: { type: "string", description: "Specific plan to check (optional - checks all if not provided)" },
192
+ status: {
193
+ type: "array",
194
+ items: { type: "string" },
195
+ default: ["blocked", "in_progress"],
196
+ description: "Task statuses to include"
187
197
  }
188
- },
189
- required: ["title", "content"]
198
+ }
190
199
  }
191
200
  },
192
201
 
@@ -380,14 +389,20 @@ function setupTools(server) {
380
389
  status: {
381
390
  type: "string",
382
391
  description: "Node status",
383
- enum: ["not_started", "in_progress", "completed", "blocked"],
392
+ enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"],
384
393
  default: "not_started"
385
394
  },
386
395
  context: { type: "string", description: "Additional context for the node" },
387
396
  agent_instructions: { type: "string", description: "Instructions for AI agents working on this node" },
388
397
  acceptance_criteria: { type: "string", description: "Criteria for node completion" },
389
398
  due_date: { type: "string", description: "Due date (ISO format)" },
390
- metadata: { type: "object", description: "Additional metadata" }
399
+ metadata: { type: "object", description: "Additional metadata" },
400
+ task_mode: {
401
+ type: "string",
402
+ description: "RPI workflow mode for the node",
403
+ enum: ["research", "plan", "implement", "free"],
404
+ default: "free"
405
+ }
391
406
  },
392
407
  required: ["plan_id", "node_type", "title"]
393
408
  }
@@ -405,13 +420,18 @@ function setupTools(server) {
405
420
  status: {
406
421
  type: "string",
407
422
  description: "New node status",
408
- enum: ["not_started", "in_progress", "completed", "blocked"]
423
+ enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"]
409
424
  },
410
425
  context: { type: "string", description: "New context" },
411
426
  agent_instructions: { type: "string", description: "New agent instructions" },
412
427
  acceptance_criteria: { type: "string", description: "New acceptance criteria" },
413
428
  due_date: { type: "string", description: "New due date (ISO format)" },
414
- metadata: { type: "object", description: "New metadata" }
429
+ metadata: { type: "object", description: "New metadata" },
430
+ task_mode: {
431
+ type: "string",
432
+ description: "RPI workflow mode for the node",
433
+ enum: ["research", "plan", "implement", "free"]
434
+ }
415
435
  },
416
436
  required: ["plan_id", "node_id"]
417
437
  }
@@ -443,8 +463,8 @@ function setupTools(server) {
443
463
  }
444
464
  },
445
465
  {
446
- name: "get_node_context",
447
- description: "Get comprehensive context for a node including children and logs",
466
+ name: "get_node_ancestry",
467
+ description: "Get the path from root to a specific node",
448
468
  inputSchema: {
449
469
  type: "object",
450
470
  properties: {
@@ -454,19 +474,169 @@ function setupTools(server) {
454
474
  required: ["plan_id", "node_id"]
455
475
  }
456
476
  },
477
+
478
+ // ===== DEPENDENCY TOOLS =====
479
+ {
480
+ name: "create_dependency",
481
+ description: "Create a dependency edge between two nodes in a plan. Source 'blocks' target by default.",
482
+ inputSchema: {
483
+ type: "object",
484
+ properties: {
485
+ plan_id: { type: "string", description: "Plan ID" },
486
+ source_node_id: { type: "string", description: "Source node ID (the blocker)" },
487
+ target_node_id: { type: "string", description: "Target node ID (the blocked)" },
488
+ dependency_type: {
489
+ type: "string",
490
+ description: "Type of dependency",
491
+ enum: ["blocks", "requires", "relates_to"],
492
+ default: "blocks"
493
+ },
494
+ weight: { type: "integer", description: "Edge weight (default 1)", default: 1 },
495
+ metadata: { type: "object", description: "Additional metadata" }
496
+ },
497
+ required: ["plan_id", "source_node_id", "target_node_id"]
498
+ }
499
+ },
457
500
  {
458
- name: "get_node_ancestry",
459
- description: "Get the path from root to a specific node",
501
+ name: "delete_dependency",
502
+ description: "Delete a dependency edge",
460
503
  inputSchema: {
461
504
  type: "object",
462
505
  properties: {
463
506
  plan_id: { type: "string", description: "Plan ID" },
464
- node_id: { type: "string", description: "Node ID" }
507
+ dependency_id: { type: "string", description: "Dependency edge ID" }
508
+ },
509
+ required: ["plan_id", "dependency_id"]
510
+ }
511
+ },
512
+ {
513
+ name: "list_dependencies",
514
+ description: "List all dependency edges in a plan",
515
+ inputSchema: {
516
+ type: "object",
517
+ properties: {
518
+ plan_id: { type: "string", description: "Plan ID" }
519
+ },
520
+ required: ["plan_id"]
521
+ }
522
+ },
523
+ {
524
+ name: "get_node_dependencies",
525
+ description: "Get upstream and downstream dependencies for a node",
526
+ inputSchema: {
527
+ type: "object",
528
+ properties: {
529
+ plan_id: { type: "string", description: "Plan ID" },
530
+ node_id: { type: "string", description: "Node ID" },
531
+ direction: {
532
+ type: "string",
533
+ description: "Direction to query",
534
+ enum: ["upstream", "downstream", "both"],
535
+ default: "both"
536
+ }
465
537
  },
466
538
  required: ["plan_id", "node_id"]
467
539
  }
468
540
  },
469
-
541
+
542
+ // ===== RPI WORKFLOW =====
543
+ {
544
+ name: "create_rpi_chain",
545
+ description: "Create a Research→Plan→Implement task chain with automatic dependency edges. The three tasks are linked: Research blocks Plan, Plan blocks Implement.",
546
+ inputSchema: {
547
+ type: "object",
548
+ properties: {
549
+ plan_id: { type: "string", description: "Plan ID" },
550
+ title: { type: "string", description: "Base title for the chain (e.g. 'Auth refactor')" },
551
+ description: { type: "string", description: "Description for the research task" },
552
+ parent_id: { type: "string", description: "Parent node ID (optional, defaults to root)" }
553
+ },
554
+ required: ["plan_id", "title"]
555
+ }
556
+ },
557
+
558
+ // ===== ANALYSIS TOOLS =====
559
+ {
560
+ name: "analyze_impact",
561
+ description: "Analyze what happens if a node is delayed, blocked, or removed. Shows directly and transitively affected nodes.",
562
+ inputSchema: {
563
+ type: "object",
564
+ properties: {
565
+ plan_id: { type: "string", description: "Plan ID" },
566
+ node_id: { type: "string", description: "Node ID to analyze" },
567
+ scenario: {
568
+ type: "string",
569
+ description: "Impact scenario",
570
+ enum: ["delay", "block", "remove"],
571
+ default: "block"
572
+ }
573
+ },
574
+ required: ["plan_id", "node_id"]
575
+ }
576
+ },
577
+ {
578
+ name: "get_critical_path",
579
+ description: "Find the critical path (longest dependency chain) through incomplete tasks in a plan",
580
+ inputSchema: {
581
+ type: "object",
582
+ properties: {
583
+ plan_id: { type: "string", description: "Plan ID" }
584
+ },
585
+ required: ["plan_id"]
586
+ }
587
+ },
588
+
589
+ // ===== PROGRESSIVE CONTEXT TOOLS =====
590
+ {
591
+ name: "get_task_context",
592
+ 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.",
593
+ inputSchema: {
594
+ type: "object",
595
+ properties: {
596
+ node_id: { type: "string", description: "Task/node ID to get context for" },
597
+ depth: {
598
+ type: "integer",
599
+ description: "Context depth 1-4 (default 2). Start with 2, go deeper if needed.",
600
+ minimum: 1,
601
+ maximum: 4,
602
+ default: 2
603
+ },
604
+ token_budget: {
605
+ type: "integer",
606
+ description: "Max estimated tokens (0 = unlimited). Use to stay within context window limits.",
607
+ default: 0
608
+ },
609
+ log_limit: {
610
+ type: "integer",
611
+ description: "Max recent logs to include per node",
612
+ default: 10
613
+ },
614
+ include_research: {
615
+ type: "boolean",
616
+ description: "Include research outputs from RPI chain siblings (for implement tasks)",
617
+ default: true
618
+ }
619
+ },
620
+ required: ["node_id"]
621
+ }
622
+ },
623
+ {
624
+ name: "suggest_next_tasks",
625
+ 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.",
626
+ inputSchema: {
627
+ type: "object",
628
+ properties: {
629
+ plan_id: { type: "string", description: "Plan ID" },
630
+ limit: {
631
+ type: "integer",
632
+ description: "Maximum suggestions to return",
633
+ default: 5
634
+ }
635
+ },
636
+ required: ["plan_id"]
637
+ }
638
+ },
639
+
470
640
  // ===== LOGGING TOOLS (Replaces Comments) =====
471
641
  {
472
642
  name: "add_log",
@@ -532,7 +702,7 @@ function setupTools(server) {
532
702
  node_id: { type: "string", description: "Node ID" },
533
703
  status: {
534
704
  type: "string",
535
- enum: ["not_started", "in_progress", "completed", "blocked"]
705
+ enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"]
536
706
  },
537
707
  title: { type: "string" },
538
708
  description: { type: "string" }
@@ -548,7 +718,7 @@ function setupTools(server) {
548
718
  // ===== PLAN STRUCTURE & SUMMARY =====
549
719
  {
550
720
  name: "get_plan_structure",
551
- description: "Get the hierarchical structure of a plan with minimal fields (id, parent_id, node_type, title, status, order_index). Use get_node_context for detailed information about specific nodes.",
721
+ description: "Get the hierarchical structure of a plan with minimal fields (id, parent_id, node_type, title, status, order_index). Use get_task_context for detailed information about specific nodes.",
552
722
  inputSchema: {
553
723
  type: "object",
554
724
  properties: {
@@ -575,33 +745,9 @@ function setupTools(server) {
575
745
  },
576
746
 
577
747
  // ===== AGENT CONTEXT TOOLS (Leaf-up context loading) =====
578
- {
579
- name: "get_agent_context",
580
- description: "Get focused context for a specific task/node. Uses leaf-up traversal - returns only the relevant path from the node to root, not the entire plan tree. Best for agents starting work on a specific task.",
581
- inputSchema: {
582
- type: "object",
583
- properties: {
584
- node_id: {
585
- type: "string",
586
- description: "Task or phase node ID to get context for"
587
- },
588
- include_knowledge: {
589
- type: "boolean",
590
- description: "Include knowledge entries from relevant scopes (plan, goals, org)",
591
- default: true
592
- },
593
- include_siblings: {
594
- type: "boolean",
595
- description: "Include sibling tasks in the same phase",
596
- default: false
597
- }
598
- },
599
- required: ["node_id"]
600
- }
601
- },
602
748
  {
603
749
  name: "get_plan_context",
604
- description: "Get plan-level context overview. Returns plan details, phase summaries (not full tree), linked goals, and organization. Use get_agent_context for task-focused work.",
750
+ description: "Get plan-level context overview. Returns plan details, phase summaries (not full tree), linked goals, and organization. Use get_task_context for task-focused work.",
605
751
  inputSchema: {
606
752
  type: "object",
607
753
  properties: {
@@ -766,102 +912,174 @@ function setupTools(server) {
766
912
  }
767
913
  },
768
914
 
769
- // ===== KNOWLEDGE TOOLS =====
915
+ // ===== CROSS-PLAN & EXTERNAL DEPENDENCY TOOLS =====
770
916
  {
771
- name: "add_knowledge_entry",
772
- description: "Add a knowledge entry (decision, context, constraint, learning, reference, note) to a scope",
917
+ name: "create_cross_plan_dependency",
918
+ 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
919
  inputSchema: {
774
920
  type: "object",
775
921
  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" } }
922
+ source_node_id: { type: "string", description: "Source node ID (the blocker/prerequisite)" },
923
+ target_node_id: { type: "string", description: "Target node ID (the blocked/dependent task)" },
924
+ dependency_type: { type: "string", enum: ["blocks", "requires", "relates_to"], default: "blocks", description: "Edge type (default: blocks)" },
925
+ weight: { type: "number", description: "Edge weight (default 1)" }
791
926
  },
792
- required: ["scope", "scope_id", "entry_type", "title", "content"]
927
+ required: ["source_node_id", "target_node_id"]
793
928
  }
794
929
  },
795
930
  {
796
- name: "list_knowledge_entries",
797
- description: "List knowledge entries for a scope",
931
+ name: "list_cross_plan_dependencies",
932
+ description: "List all dependency edges that cross plan boundaries between specified plans.",
798
933
  inputSchema: {
799
934
  type: "object",
800
935
  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 }
936
+ plan_ids: {
937
+ type: "array",
938
+ items: { type: "string" },
939
+ description: "Plan IDs to check for cross-plan edges (at least 2)"
940
+ }
814
941
  },
815
- required: ["scope", "scope_id"]
942
+ required: ["plan_ids"]
816
943
  }
817
944
  },
818
945
  {
819
- name: "search_knowledge",
820
- description: "Search knowledge entries across scopes using text search",
946
+ name: "create_external_dependency",
947
+ description: "Create an external dependency node representing a blocker outside the system (vendor API, legal approval, etc.). Optionally blocks a target task.",
821
948
  inputSchema: {
822
949
  type: "object",
823
950
  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 }
951
+ plan_id: { type: "string", description: "Plan to add the external dependency to" },
952
+ title: { type: "string", description: "External dependency title (e.g., 'Waiting for vendor API access')" },
953
+ description: { type: "string", description: "Details about the external dependency" },
954
+ url: { type: "string", description: "URL reference (ticket, docs, etc.)" },
955
+ blocks_node_id: { type: "string", description: "Node ID that this external dep blocks" }
956
+ },
957
+ required: ["plan_id", "title"]
958
+ }
959
+ },
960
+
961
+ // ===== GOAL-DEPENDENCY TOOLS =====
962
+ {
963
+ name: "goal_path",
964
+ 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.",
965
+ inputSchema: {
966
+ type: "object",
967
+ properties: {
968
+ goal_id: { type: "string", description: "Goal ID" },
969
+ max_depth: { type: "number", description: "Max traversal depth (default 20)" }
970
+ },
971
+ required: ["goal_id"]
972
+ }
973
+ },
974
+ {
975
+ name: "goal_progress",
976
+ description: "Get goal progress calculated from its dependency graph. Returns overall completion percentage and direct achiever progress.",
977
+ inputSchema: {
978
+ type: "object",
979
+ properties: {
980
+ goal_id: { type: "string", description: "Goal ID" }
981
+ },
982
+ required: ["goal_id"]
983
+ }
984
+ },
985
+ {
986
+ name: "add_achiever",
987
+ description: "Link a task to a goal via an 'achieves' dependency edge. This declares that completing this task contributes to achieving the goal.",
988
+ inputSchema: {
989
+ type: "object",
990
+ properties: {
991
+ goal_id: { type: "string", description: "Goal ID" },
992
+ node_id: { type: "string", description: "Task/node ID that achieves this goal" },
993
+ weight: { type: "number", description: "Edge weight for critical path (default 1)" }
994
+ },
995
+ required: ["goal_id", "node_id"]
996
+ }
997
+ },
998
+ {
999
+ name: "remove_achiever",
1000
+ description: "Remove an achieves edge between a task and a goal",
1001
+ inputSchema: {
1002
+ type: "object",
1003
+ properties: {
1004
+ goal_id: { type: "string", description: "Goal ID" },
1005
+ dependency_id: { type: "string", description: "Dependency edge ID to remove" }
1006
+ },
1007
+ required: ["goal_id", "dependency_id"]
1008
+ }
1009
+ },
1010
+ {
1011
+ name: "goal_knowledge_gaps",
1012
+ 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.",
1013
+ inputSchema: {
1014
+ type: "object",
1015
+ properties: {
1016
+ goal_id: { type: "string", description: "Goal ID" }
1017
+ },
1018
+ required: ["goal_id"]
1019
+ }
1020
+ },
1021
+
1022
+ // ===== GRAPHITI KNOWLEDGE GRAPH TOOLS =====
1023
+ {
1024
+ name: "add_learning",
1025
+ 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.",
1026
+ inputSchema: {
1027
+ type: "object",
1028
+ properties: {
1029
+ content: { type: "string", description: "The knowledge content — be detailed. Include context, reasoning, and conclusions." },
1030
+ title: { type: "string", description: "Short title/name for the episode" },
1031
+ entry_type: { type: "string", enum: ["decision", "learning", "context", "constraint"], description: "Type of knowledge" },
1032
+ plan_id: { type: "string", description: "Plan ID this knowledge relates to (optional)" },
1033
+ node_id: { type: "string", description: "Node/task ID this knowledge relates to (optional)" }
1034
+ },
1035
+ required: ["content"]
1036
+ }
1037
+ },
1038
+ {
1039
+ name: "recall_knowledge",
1040
+ 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.",
1041
+ inputSchema: {
1042
+ type: "object",
1043
+ properties: {
1044
+ query: { type: "string", description: "What to search for — be specific" },
1045
+ max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
837
1046
  },
838
1047
  required: ["query"]
839
1048
  }
840
1049
  },
841
1050
  {
842
- name: "update_knowledge_entry",
843
- description: "Update a knowledge entry",
1051
+ name: "find_entities",
1052
+ description: "Search for entities (technologies, people, patterns, constraints) in the knowledge graph. Returns entity nodes with their relationships.",
844
1053
  inputSchema: {
845
1054
  type: "object",
846
1055
  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" } }
1056
+ query: { type: "string", description: "Entity search query" },
1057
+ max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
852
1058
  },
853
- required: ["entry_id"]
1059
+ required: ["query"]
854
1060
  }
855
1061
  },
1062
+
856
1063
  {
857
- name: "delete_knowledge_entry",
858
- description: "Delete a knowledge entry",
1064
+ name: "check_contradictions",
1065
+ 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
1066
  inputSchema: {
860
1067
  type: "object",
861
1068
  properties: {
862
- entry_id: { type: "string", description: "Entry ID to delete" }
1069
+ query: { type: "string", description: "Topic to check for contradictions" },
1070
+ max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
863
1071
  },
864
- required: ["entry_id"]
1072
+ required: ["query"]
1073
+ }
1074
+ },
1075
+ {
1076
+ name: "get_recent_episodes",
1077
+ 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.",
1078
+ inputSchema: {
1079
+ type: "object",
1080
+ properties: {
1081
+ max_episodes: { type: "number", description: "Maximum episodes to return (default 20)", default: 20 }
1082
+ }
865
1083
  }
866
1084
  },
867
1085
 
@@ -881,18 +1099,6 @@ function setupTools(server) {
881
1099
  }
882
1100
  }
883
1101
  },
884
- {
885
- name: "understand_context",
886
- description: "Get comprehensive context about a plan or goal - its purpose, current state, recent activity, blocked tasks, and relevant knowledge. Use this BEFORE starting work to understand the full situation.",
887
- inputSchema: {
888
- type: "object",
889
- properties: {
890
- plan_id: { type: "string", description: "Plan ID to understand" },
891
- goal_id: { type: "string", description: "Goal ID to understand" },
892
- include_knowledge: { type: "boolean", description: "Include relevant knowledge entries", default: true }
893
- }
894
- }
895
- }
896
1102
  ]
897
1103
  };
898
1104
  });
@@ -967,14 +1173,14 @@ function setupTools(server) {
967
1173
  success: true,
968
1174
  message: `Plan "${title}" created with ${tasks.length} tasks`,
969
1175
  plan_id: plan.id,
970
- plan_url: `https://www.agentplanner.io/app/plans/${plan.id}`,
1176
+ plan_url: buildPlanUrl(plan.id),
971
1177
  phase_id: phase.id,
972
1178
  task_ids: createdTasks.map(t => t.id),
973
1179
  tasks: createdTasks,
974
1180
  next_steps: [
975
1181
  "Use quick_status to update task progress",
976
1182
  "Use quick_log to document your work",
977
- "Use get_context to load full plan details"
1183
+ "Use get_plan_context to load full plan details"
978
1184
  ]
979
1185
  });
980
1186
  }
@@ -1029,7 +1235,7 @@ function setupTools(server) {
1029
1235
  task_id: task.id,
1030
1236
  plan_id: plan_id,
1031
1237
  phase_id: targetPhaseId,
1032
- task_url: `https://www.agentplanner.io/app/plans/${plan_id}?node=${task.id}`,
1238
+ task_url: buildTaskUrl(plan_id, task.id),
1033
1239
  next_steps: [
1034
1240
  "Use quick_status to mark as in_progress when you start",
1035
1241
  "Use quick_log to document progress"
@@ -1110,96 +1316,7 @@ function setupTools(server) {
1110
1316
  // ========================================
1111
1317
  // CONTEXT LOADING IMPLEMENTATIONS
1112
1318
  // ========================================
1113
-
1114
- if (name === "get_context") {
1115
- // Redirect to understand_context implementation (same functionality)
1116
- const { plan_id, goal_id, include_knowledge = true } = args;
1117
-
1118
- if (!plan_id && !goal_id) {
1119
- return formatResponse({
1120
- error: "Provide either plan_id or goal_id to get context",
1121
- suggestion: "Use list_plans or list_goals to find IDs"
1122
- });
1123
- }
1124
-
1125
- const context = {
1126
- retrieved_at: new Date().toISOString()
1127
- };
1128
-
1129
- // Get goal context
1130
- if (goal_id) {
1131
- try {
1132
- context.goal = await apiClient.goals.get(goal_id);
1133
- if (include_knowledge) {
1134
- 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 || [];
1141
- } catch (e) {}
1142
- }
1143
- } catch (e) {
1144
- context.goal_error = e.message;
1145
- }
1146
- }
1147
-
1148
- // Get plan context
1149
- if (plan_id) {
1150
- try {
1151
- context.plan = await apiClient.plans.getPlan(plan_id);
1152
- context.plan_url = `https://www.agentplanner.io/app/plans/${plan_id}`;
1153
-
1154
- const nodes = await apiClient.nodes.getNodes(plan_id);
1155
- context.statistics = calculatePlanStatistics(nodes);
1156
- context.progress_percentage = context.statistics.total > 0
1157
- ? ((context.statistics.status_counts.completed / context.statistics.total) * 100).toFixed(1) + '%'
1158
- : '0%';
1159
-
1160
- const flatNodes = flattenNodes(nodes);
1161
- context.needs_attention = {
1162
- blocked: flatNodes.filter(n => n.status === 'blocked').map(n => ({
1163
- id: n.id, title: n.title, type: n.node_type
1164
- })),
1165
- in_progress: flatNodes.filter(n => n.status === 'in_progress').map(n => ({
1166
- id: n.id, title: n.title, type: n.node_type
1167
- })),
1168
- ready_to_start: flatNodes
1169
- .filter(n => n.node_type === 'task' && n.status === 'not_started')
1170
- .slice(0, 5)
1171
- .map(n => ({ id: n.id, title: n.title }))
1172
- };
1173
-
1174
- try {
1175
- const activity = await apiClient.activity.getPlanActivity(plan_id);
1176
- context.recent_activity = (activity || []).slice(0, 5);
1177
- } catch (e) {}
1178
-
1179
- if (include_knowledge) {
1180
- 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 || [];
1187
- } catch (e) {}
1188
- }
1189
- } catch (e) {
1190
- context.plan_error = e.message;
1191
- }
1192
- }
1193
-
1194
- context.recommendation = context.needs_attention?.blocked?.length > 0
1195
- ? "⚠️ There are blocked tasks that need attention first"
1196
- : context.needs_attention?.in_progress?.length > 0
1197
- ? "Continue working on the in-progress tasks"
1198
- : "Pick a task from ready_to_start to begin";
1199
-
1200
- return formatResponse(context);
1201
- }
1202
-
1319
+
1203
1320
  if (name === "get_my_tasks") {
1204
1321
  const { plan_id, status = ["blocked", "in_progress"] } = args;
1205
1322
 
@@ -1260,34 +1377,8 @@ function setupTools(server) {
1260
1377
  return formatResponse(tasks);
1261
1378
  }
1262
1379
 
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
-
1380
+ // add_learning handled in GRAPHITI KNOWLEDGE GRAPH HANDLERS section below
1381
+
1291
1382
  // ========================================
1292
1383
  // MARKDOWN EXPORT
1293
1384
  // ========================================
@@ -1475,11 +1566,11 @@ function setupTools(server) {
1475
1566
  success: true,
1476
1567
  message: `Plan "${planTitle}" created from markdown with ${phases.length} phases and ${createdTasks.length} tasks`,
1477
1568
  plan_id: plan.id,
1478
- plan_url: `https://www.agentplanner.io/app/plans/${plan.id}`,
1569
+ plan_url: buildPlanUrl(plan.id),
1479
1570
  phases: createdPhases,
1480
1571
  tasks: createdTasks,
1481
1572
  next_steps: [
1482
- "Use get_context to review the imported plan",
1573
+ "Use get_plan_context to review the imported plan",
1483
1574
  "Use quick_status to update task progress"
1484
1575
  ]
1485
1576
  });
@@ -1600,7 +1691,7 @@ function setupTools(server) {
1600
1691
  const result = await apiClient.plans.updateVisibility(plan_id, visibilityData);
1601
1692
 
1602
1693
  const shareUrl = visibility === "public"
1603
- ? `https://www.agentplanner.io/app/plans/${plan_id}`
1694
+ ? buildPlanUrl(plan_id)
1604
1695
  : null;
1605
1696
 
1606
1697
  return formatResponse({
@@ -1641,55 +1732,130 @@ function setupTools(server) {
1641
1732
 
1642
1733
  if (name === "move_node") {
1643
1734
  const { plan_id, node_id, parent_id, order_index } = args;
1644
-
1735
+
1645
1736
  try {
1737
+ // Build request body with only provided fields - don't send nulls
1738
+ const body = {};
1739
+ if (parent_id) body.parent_id = parent_id;
1740
+ if (order_index !== undefined) body.order_index = order_index;
1741
+
1646
1742
  // Call the move endpoint - using POST as per API definition
1647
1743
  const response = await apiClient.axiosInstance.post(
1648
1744
  `/plans/${plan_id}/nodes/${node_id}/move`,
1649
- {
1650
- parent_id: parent_id || null,
1651
- order_index: order_index !== undefined ? order_index : null
1652
- }
1745
+ body
1653
1746
  );
1654
-
1747
+
1655
1748
  return formatResponse(response.data);
1656
1749
  } catch (error) {
1657
1750
  // If endpoint still doesn't work, try updating the node directly
1658
1751
  if (error.response && error.response.status === 404) {
1659
1752
  console.error('Move endpoint not found, trying direct update');
1660
1753
  // 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
- });
1754
+ const updateData = {};
1755
+ if (parent_id) updateData.parent_id = parent_id;
1756
+ if (order_index !== undefined) updateData.order_index = order_index;
1757
+ const updateResponse = await apiClient.nodes.updateNode(plan_id, node_id, updateData);
1665
1758
  return formatResponse(updateResponse);
1666
1759
  }
1667
1760
  throw error;
1668
1761
  }
1669
1762
  }
1670
1763
 
1671
- if (name === "get_node_context") {
1764
+ if (name === "get_node_ancestry") {
1672
1765
  const { plan_id, node_id } = args;
1673
1766
 
1674
- // Get node with context
1767
+ // Get node ancestry
1675
1768
  const response = await apiClient.axiosInstance.get(
1676
- `/plans/${plan_id}/nodes/${node_id}/context`
1769
+ `/plans/${plan_id}/nodes/${node_id}/ancestry`
1677
1770
  );
1678
1771
 
1679
1772
  return formatResponse(response.data);
1680
1773
  }
1681
1774
 
1682
- if (name === "get_node_ancestry") {
1683
- const { plan_id, node_id } = args;
1684
-
1685
- // Get node ancestry
1775
+ // ===== DEPENDENCIES =====
1776
+ if (name === "create_dependency") {
1777
+ const { plan_id, source_node_id, target_node_id, dependency_type, weight, metadata } = args;
1778
+ const response = await apiClient.axiosInstance.post(
1779
+ `/plans/${plan_id}/dependencies`,
1780
+ { source_node_id, target_node_id, dependency_type, weight, metadata }
1781
+ );
1782
+ return formatResponse(response.data);
1783
+ }
1784
+
1785
+ if (name === "delete_dependency") {
1786
+ const { plan_id, dependency_id } = args;
1787
+ const response = await apiClient.axiosInstance.delete(
1788
+ `/plans/${plan_id}/dependencies/${dependency_id}`
1789
+ );
1790
+ return formatResponse(response.data);
1791
+ }
1792
+
1793
+ if (name === "list_dependencies") {
1794
+ const { plan_id } = args;
1686
1795
  const response = await apiClient.axiosInstance.get(
1687
- `/plans/${plan_id}/nodes/${node_id}/ancestry`
1796
+ `/plans/${plan_id}/dependencies`
1688
1797
  );
1689
-
1690
1798
  return formatResponse(response.data);
1691
1799
  }
1692
-
1800
+
1801
+ if (name === "get_node_dependencies") {
1802
+ const { plan_id, node_id, direction = 'both' } = args;
1803
+ const response = await apiClient.axiosInstance.get(
1804
+ `/plans/${plan_id}/nodes/${node_id}/dependencies`,
1805
+ { params: { direction } }
1806
+ );
1807
+ return formatResponse(response.data);
1808
+ }
1809
+
1810
+ // ===== RPI WORKFLOW =====
1811
+ if (name === "create_rpi_chain") {
1812
+ const { plan_id, title, description, parent_id } = args;
1813
+ const response = await apiClient.axiosInstance.post(
1814
+ `/plans/${plan_id}/nodes/rpi-chain`,
1815
+ { title, description, parent_id }
1816
+ );
1817
+ return formatResponse(response.data);
1818
+ }
1819
+
1820
+ // ===== ANALYSIS =====
1821
+ if (name === "analyze_impact") {
1822
+ const { plan_id, node_id, scenario = 'block' } = args;
1823
+ const response = await apiClient.axiosInstance.get(
1824
+ `/plans/${plan_id}/nodes/${node_id}/impact`,
1825
+ { params: { scenario } }
1826
+ );
1827
+ return formatResponse(response.data);
1828
+ }
1829
+
1830
+ if (name === "get_critical_path") {
1831
+ const { plan_id } = args;
1832
+ const response = await apiClient.axiosInstance.get(
1833
+ `/plans/${plan_id}/critical-path`
1834
+ );
1835
+ return formatResponse(response.data);
1836
+ }
1837
+
1838
+ // ===== PROGRESSIVE CONTEXT =====
1839
+ if (name === "get_task_context") {
1840
+ const { node_id, depth = 2, token_budget = 0, log_limit = 10, include_research = true } = args;
1841
+ const params = new URLSearchParams({
1842
+ node_id,
1843
+ depth: String(depth),
1844
+ token_budget: String(token_budget),
1845
+ log_limit: String(log_limit),
1846
+ include_research: String(include_research),
1847
+ });
1848
+ const response = await apiClient.axiosInstance.get(`/context/progressive?${params.toString()}`);
1849
+ return formatResponse(response.data);
1850
+ }
1851
+
1852
+ if (name === "suggest_next_tasks") {
1853
+ const { plan_id, limit = 5 } = args;
1854
+ const params = new URLSearchParams({ plan_id, limit: String(limit) });
1855
+ const response = await apiClient.axiosInstance.get(`/context/suggest?${params.toString()}`);
1856
+ return formatResponse(response.data);
1857
+ }
1858
+
1693
1859
  // ===== LOGGING =====
1694
1860
  if (name === "add_log") {
1695
1861
  const { plan_id, node_id, content, log_type = "comment", tags } = args;
@@ -1804,17 +1970,6 @@ function setupTools(server) {
1804
1970
  }
1805
1971
 
1806
1972
  // ===== AGENT CONTEXT TOOLS =====
1807
- if (name === "get_agent_context") {
1808
- const { node_id, include_knowledge = true, include_siblings = false } = args;
1809
-
1810
- const result = await apiClient.context.getNodeContext(node_id, {
1811
- include_knowledge,
1812
- include_siblings
1813
- });
1814
-
1815
- return formatResponse(result);
1816
- }
1817
-
1818
1973
  if (name === "get_plan_context") {
1819
1974
  const { plan_id, include_knowledge = true } = args;
1820
1975
 
@@ -1901,60 +2056,154 @@ function setupTools(server) {
1901
2056
  });
1902
2057
  }
1903
2058
 
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
2059
+ // ===== CROSS-PLAN & EXTERNAL DEPENDENCY HANDLERS =====
2060
+ if (name === "create_cross_plan_dependency") {
2061
+ const { source_node_id, target_node_id, dependency_type, weight } = args;
2062
+ const result = await apiClient.dependencies.createCrossPlan({
2063
+ source_node_id, target_node_id, dependency_type, weight
1915
2064
  });
1916
2065
  return formatResponse(result);
1917
2066
  }
1918
-
1919
- if (name === "list_knowledge_entries") {
1920
- const { scope, scope_id, entry_type, tags, limit = 50 } = args;
1921
2067
 
1922
- const result = await apiClient.knowledge.listEntries({ scope, scope_id, entry_type, tags, limit });
2068
+ if (name === "list_cross_plan_dependencies") {
2069
+ const { plan_ids } = args;
2070
+ const result = await apiClient.dependencies.listCrossPlan(plan_ids);
1923
2071
  return formatResponse(result);
1924
2072
  }
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);
2073
+
2074
+ if (name === "create_external_dependency") {
2075
+ const { plan_id, title, description, url, blocks_node_id } = args;
2076
+ const result = await apiClient.dependencies.createExternal({
2077
+ plan_id, title, description, url, blocks_node_id
2078
+ });
1940
2079
  return formatResponse(result);
1941
2080
  }
1942
-
1943
- if (name === "update_knowledge_entry") {
1944
- const { entry_id, ...updateData } = args;
1945
- const result = await apiClient.knowledge.updateEntry(entry_id, updateData);
2081
+
2082
+ // ===== GOAL-DEPENDENCY HANDLERS =====
2083
+ if (name === "goal_path") {
2084
+ const { goal_id, max_depth } = args;
2085
+ const result = await apiClient.goals.getPath(goal_id, max_depth);
1946
2086
  return formatResponse(result);
1947
2087
  }
1948
-
1949
- if (name === "delete_knowledge_entry") {
1950
- const { entry_id } = args;
1951
- await apiClient.knowledge.deleteEntry(entry_id);
2088
+
2089
+ if (name === "goal_progress") {
2090
+ const { goal_id } = args;
2091
+ const result = await apiClient.goals.getProgress(goal_id);
2092
+ return formatResponse(result);
2093
+ }
2094
+
2095
+ if (name === "add_achiever") {
2096
+ const { goal_id, node_id, weight } = args;
2097
+ const result = await apiClient.goals.addAchiever(goal_id, node_id, weight);
1952
2098
  return formatResponse({
1953
- success: true,
1954
- message: `Knowledge entry ${entry_id} deleted`
2099
+ ...result,
2100
+ message: `Task ${node_id} now achieves goal ${goal_id}`,
1955
2101
  });
1956
2102
  }
1957
2103
 
2104
+ if (name === "remove_achiever") {
2105
+ const { goal_id, dependency_id } = args;
2106
+ const result = await apiClient.goals.removeAchiever(goal_id, dependency_id);
2107
+ return formatResponse(result);
2108
+ }
2109
+
2110
+ if (name === "goal_knowledge_gaps") {
2111
+ const { goal_id } = args;
2112
+ const result = await apiClient.goals.getKnowledgeGaps(goal_id);
2113
+ return formatResponse(result);
2114
+ }
2115
+
2116
+ // ===== GRAPHITI KNOWLEDGE GRAPH HANDLERS =====
2117
+ if (name === "add_learning") {
2118
+ const { content, title, entry_type, plan_id, node_id } = args;
2119
+
2120
+ // Add to Graphiti temporal knowledge graph
2121
+ const result = await apiClient.graphiti.addEpisode({
2122
+ content,
2123
+ name: title,
2124
+ plan_id,
2125
+ node_id,
2126
+ metadata: { entry_type: entry_type || 'learning' },
2127
+ });
2128
+ return formatResponse({
2129
+ ...result,
2130
+ message: 'Knowledge recorded in temporal graph',
2131
+ tip: 'This is now searchable via recall_knowledge across all plans'
2132
+ });
2133
+ }
2134
+
2135
+ if (name === "recall_knowledge") {
2136
+ const { query, max_results = 10 } = args;
2137
+
2138
+ // Try Graphiti first (temporal, cross-plan)
2139
+ try {
2140
+ const graphResult = await apiClient.graphiti.graphSearch({ query, max_results });
2141
+ if (graphResult?.results) {
2142
+ return formatResponse({
2143
+ ...graphResult,
2144
+ source: 'graphiti_temporal_graph'
2145
+ });
2146
+ }
2147
+ } catch (err) {
2148
+ return formatResponse({
2149
+ results: [],
2150
+ source: 'graphiti_temporal_graph',
2151
+ error: 'Knowledge graph not available: ' + err.message,
2152
+ });
2153
+ }
2154
+ }
2155
+
2156
+ if (name === "find_entities") {
2157
+ const { query, max_results = 10 } = args;
2158
+
2159
+ try {
2160
+ const result = await apiClient.graphiti.searchEntities({ query, max_results });
2161
+ return formatResponse(result);
2162
+ } catch (err) {
2163
+ return formatResponse({
2164
+ error: 'Entity search requires the temporal knowledge graph (Graphiti)',
2165
+ detail: err.message
2166
+ });
2167
+ }
2168
+ }
2169
+
2170
+ if (name === "check_contradictions") {
2171
+ const { query, max_results = 10 } = args;
2172
+
2173
+ try {
2174
+ const result = await apiClient.graphiti.detectContradictions({ query, max_results });
2175
+ if (result.contradictions_found) {
2176
+ return formatResponse({
2177
+ ...result,
2178
+ warning: 'Some knowledge has been superseded. Review the "superseded" facts before proceeding.',
2179
+ });
2180
+ }
2181
+ return formatResponse({
2182
+ ...result,
2183
+ message: 'No contradictions found — all facts are current.',
2184
+ });
2185
+ } catch (err) {
2186
+ return formatResponse({
2187
+ error: 'Contradiction detection requires the temporal knowledge graph (Graphiti)',
2188
+ detail: err.message,
2189
+ });
2190
+ }
2191
+ }
2192
+
2193
+ if (name === "get_recent_episodes") {
2194
+ const { max_episodes = 20 } = args || {};
2195
+
2196
+ try {
2197
+ const result = await apiClient.graphiti.getEpisodes({ max_episodes });
2198
+ return formatResponse(result);
2199
+ } catch (err) {
2200
+ return formatResponse({
2201
+ error: 'Episodic memory requires the temporal knowledge graph (Graphiti)',
2202
+ detail: err.message
2203
+ });
2204
+ }
2205
+ }
2206
+
1958
2207
  // ===== HELPER TOOLS =====
1959
2208
  if (name === "get_started") {
1960
2209
  const { topic = "overview" } = args || {};
@@ -1973,10 +2222,10 @@ function setupTools(server) {
1973
2222
  recommended_workflow: [
1974
2223
  "1. Check list_goals to understand current objectives",
1975
2224
  "2. Use list_plans to see existing plans",
1976
- "3. Before working on a plan, use understand_context to get the full picture",
2225
+ "3. Before working on a plan, use get_plan_context to get the full picture",
1977
2226
  "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"
2227
+ "5. Store important decisions and learnings using add_learning",
2228
+ "6. Check recall_knowledge before making decisions to see past context"
1980
2229
  ],
1981
2230
  quick_tips: [
1982
2231
  "Always capture WHY decisions were made, not just WHAT",
@@ -2008,7 +2257,7 @@ function setupTools(server) {
2008
2257
  workflow: [
2009
2258
  "1. Use get_plan_structure to see the full plan",
2010
2259
  "2. Find tasks with status 'not_started' or 'in_progress'",
2011
- "3. Before starting a task, check search_knowledge for relevant context",
2260
+ "3. Before starting a task, check recall_knowledge for relevant context",
2012
2261
  "4. Update task status to 'in_progress' when you begin",
2013
2262
  "5. Add logs to document what you're doing",
2014
2263
  "6. Mark 'completed' when done, or 'blocked' if stuck"
@@ -2025,7 +2274,7 @@ function setupTools(server) {
2025
2274
  "When blocked, clearly document what's blocking you",
2026
2275
  "Store learnings as you go - don't wait until the end"
2027
2276
  ],
2028
- tools_to_use: ["get_plan_structure", "update_node", "add_log", "search_knowledge"]
2277
+ tools_to_use: ["get_plan_structure", "update_node", "add_log", "recall_knowledge"]
2029
2278
  },
2030
2279
  knowledge: {
2031
2280
  title: "Knowledge Management",
@@ -2051,7 +2300,7 @@ function setupTools(server) {
2051
2300
  "When you discover a constraint or rule",
2052
2301
  "When you find a useful resource or reference"
2053
2302
  ],
2054
- tools_to_use: ["add_knowledge_entry", "search_knowledge", "list_knowledge_entries"]
2303
+ tools_to_use: ["add_learning", "recall_knowledge", "find_entities", "check_contradictions"]
2055
2304
  },
2056
2305
  collaboration: {
2057
2306
  title: "Collaboration",
@@ -2074,98 +2323,45 @@ function setupTools(server) {
2074
2323
  return formatResponse(guides[topic] || guides.overview);
2075
2324
  }
2076
2325
 
2077
- if (name === "understand_context") {
2078
- const { plan_id, goal_id, include_knowledge = true } = args;
2079
-
2080
- if (!plan_id && !goal_id) {
2081
- return formatResponse({
2082
- error: "Provide either plan_id or goal_id to get context"
2083
- });
2084
- }
2085
-
2086
- const context = {
2087
- retrieved_at: new Date().toISOString()
2088
- };
2089
-
2090
- // Get goal context
2091
- if (goal_id) {
2092
- try {
2093
- context.goal = await apiClient.goals.get(goal_id);
2094
-
2095
- // Get related knowledge if available
2096
- if (include_knowledge) {
2097
- 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 || [];
2104
- } catch (e) {
2105
- // Knowledge fetch failed, continue without it
2106
- }
2107
- }
2108
- } catch (e) {
2109
- context.goal_error = e.message;
2110
- }
2111
- }
2112
-
2113
- // Get plan context
2114
- if (plan_id) {
2115
- try {
2116
- context.plan = await apiClient.plans.getPlan(plan_id);
2117
-
2118
- const nodes = await apiClient.nodes.getNodes(plan_id);
2119
- context.statistics = calculatePlanStatistics(nodes);
2120
- context.progress_percentage = context.statistics.total > 0
2121
- ? ((context.statistics.status_counts.completed / context.statistics.total) * 100).toFixed(1) + '%'
2122
- : '0%';
2123
-
2124
- // Get blocked and in-progress tasks for attention
2125
- const flatNodes = flattenNodes(nodes);
2126
- context.needs_attention = {
2127
- blocked: flatNodes.filter(n => n.status === 'blocked').map(n => ({
2128
- id: n.id,
2129
- title: n.title,
2130
- type: n.node_type
2131
- })),
2132
- in_progress: flatNodes.filter(n => n.status === 'in_progress').map(n => ({
2133
- id: n.id,
2134
- title: n.title,
2135
- type: n.node_type
2136
- }))
2137
- };
2138
-
2139
- // Get recent activity
2140
- try {
2141
- const activity = await apiClient.activity.getPlanActivity(plan_id);
2142
- context.recent_activity = (activity || []).slice(0, 5);
2143
- } catch (e) {
2144
- // Activity fetch failed, continue without it
2145
- }
2146
-
2147
- // Get related knowledge if available
2148
- if (include_knowledge) {
2149
- 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 || [];
2156
- } catch (e) {
2157
- // Knowledge fetch failed, continue without it
2158
- }
2159
- }
2160
- } catch (e) {
2161
- context.plan_error = e.message;
2162
- }
2326
+ // ===== GOALS HEALTH DASHBOARD =====
2327
+ if (name === "check_goals_health") {
2328
+ const { status_filter } = args || {};
2329
+ const result = await apiClient.goals.getDashboard();
2330
+
2331
+ let goals = result.goals || result;
2332
+ if (status_filter && Array.isArray(goals)) {
2333
+ goals = goals.filter(g => g.health_status === status_filter || g.status === status_filter);
2163
2334
  }
2164
-
2165
- context.recommendation = "Review the statistics, needs_attention, and knowledge entries before starting work.";
2166
- return formatResponse(context);
2335
+
2336
+ return formatResponse({
2337
+ ...result,
2338
+ goals,
2339
+ tip: "Prioritize: stale goals first, then at_risk, then on_track."
2340
+ });
2167
2341
  }
2168
-
2342
+
2343
+ // ===== TASK CLAIMING =====
2344
+ if (name === "claim_task") {
2345
+ const { task_id, plan_id, ttl_minutes = 30 } = args;
2346
+ const result = await apiClient.nodes.claimTask(plan_id, task_id, 'mcp-agent', ttl_minutes);
2347
+ return formatResponse({
2348
+ success: true,
2349
+ message: `Task ${task_id} claimed for ${ttl_minutes} minutes`,
2350
+ ...result,
2351
+ tip: "Remember to release the task when done, or it will auto-expire."
2352
+ });
2353
+ }
2354
+
2355
+ if (name === "release_task") {
2356
+ const { task_id, plan_id } = args;
2357
+ const result = await apiClient.nodes.releaseTask(plan_id, task_id, 'mcp-agent');
2358
+ return formatResponse({
2359
+ success: true,
2360
+ message: `Task ${task_id} released`,
2361
+ ...result
2362
+ });
2363
+ }
2364
+
2169
2365
  // Tool not found
2170
2366
  throw new Error(`Unknown tool: ${name}`);
2171
2367
  } catch (error) {