agent-planner-mcp 0.3.1 → 0.5.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
@@ -56,6 +56,176 @@ function setupTools(server) {
56
56
  server.setRequestHandler(ListToolsRequestSchema, async () => {
57
57
  return {
58
58
  tools: [
59
+ // ========================================
60
+ // QUICK ACTIONS - Low friction entry points
61
+ // Use these for common operations
62
+ // ========================================
63
+ {
64
+ 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.",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ title: { type: "string", description: "Plan title" },
70
+ description: { type: "string", description: "Optional plan description" },
71
+ tasks: {
72
+ type: "array",
73
+ items: { type: "string" },
74
+ description: "List of task titles (simple strings). A phase will be created automatically."
75
+ },
76
+ goal_id: { type: "string", description: "Optionally link to a goal" },
77
+ organization_id: { type: "string", description: "Organization ID (uses default if not provided)" }
78
+ },
79
+ required: ["title", "tasks"]
80
+ }
81
+ },
82
+ {
83
+ name: "quick_task",
84
+ description: "Add a single task to an existing plan. Minimal parameters - just plan_id and title. Automatically adds to the first phase or creates one.",
85
+ inputSchema: {
86
+ type: "object",
87
+ properties: {
88
+ plan_id: { type: "string", description: "Plan to add task to" },
89
+ title: { type: "string", description: "Task title" },
90
+ description: { type: "string", description: "Optional task description" },
91
+ phase_id: { type: "string", description: "Specific phase to add to (optional - uses first phase if not provided)" },
92
+ agent_instructions: { type: "string", description: "Instructions for AI agents working on this task" }
93
+ },
94
+ required: ["plan_id", "title"]
95
+ }
96
+ },
97
+ {
98
+ name: "quick_status",
99
+ description: "Update a task's status. The most common operation - made simple. Returns what to do next.",
100
+ inputSchema: {
101
+ type: "object",
102
+ properties: {
103
+ task_id: { type: "string", description: "Task ID to update" },
104
+ plan_id: { type: "string", description: "Plan ID (required for API)" },
105
+ status: {
106
+ type: "string",
107
+ enum: ["not_started", "in_progress", "completed", "blocked"],
108
+ description: "New status"
109
+ },
110
+ note: { type: "string", description: "Optional note explaining the status change (especially useful for 'blocked')" }
111
+ },
112
+ required: ["task_id", "plan_id", "status"]
113
+ }
114
+ },
115
+ {
116
+ name: "quick_log",
117
+ description: "Add a progress note to a task. Use this to document work as you go - helps humans follow along and other agents understand what happened.",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ task_id: { type: "string", description: "Task ID" },
122
+ plan_id: { type: "string", description: "Plan ID" },
123
+ message: { type: "string", description: "What you did or learned" },
124
+ log_type: {
125
+ type: "string",
126
+ enum: ["progress", "decision", "blocker", "completion"],
127
+ default: "progress",
128
+ description: "Type of log entry"
129
+ }
130
+ },
131
+ required: ["task_id", "plan_id", "message"]
132
+ }
133
+ },
134
+
135
+ // ========================================
136
+ // CONTEXT LOADING - Get everything you need
137
+ // Use before starting work on a plan/goal
138
+ // ========================================
139
+ {
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.",
142
+ inputSchema: {
143
+ type: "object",
144
+ 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
+ }
149
+ }
150
+ },
151
+ {
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.",
154
+ inputSchema: {
155
+ type: "object",
156
+ 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
+ }
165
+ }
166
+ },
167
+
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
+ // ========================================
194
+ // MARKDOWN EXPORT/IMPORT - Filesystem pattern
195
+ // ========================================
196
+ {
197
+ name: "export_plan_markdown",
198
+ description: "Export a plan as markdown text. Useful for reviewing plans in text format or saving to files.",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ plan_id: { type: "string", description: "Plan to export" },
203
+ include_descriptions: { type: "boolean", default: true, description: "Include task descriptions" },
204
+ include_status: { type: "boolean", default: true, description: "Include status indicators" }
205
+ },
206
+ required: ["plan_id"]
207
+ }
208
+ },
209
+ {
210
+ name: "import_plan_markdown",
211
+ description: "Create a new plan from markdown text. Parses headings as phases and list items as tasks. Great for converting text plans into structured AgentPlanner plans.",
212
+ inputSchema: {
213
+ type: "object",
214
+ properties: {
215
+ markdown: {
216
+ type: "string",
217
+ description: "Markdown text to parse. Use # for title, ## for phases, - for tasks"
218
+ },
219
+ title: {
220
+ type: "string",
221
+ description: "Plan title (optional - extracted from first # heading if not provided)"
222
+ },
223
+ goal_id: { type: "string", description: "Optionally link to a goal" }
224
+ },
225
+ required: ["markdown"]
226
+ }
227
+ },
228
+
59
229
  // ===== UNIFIED SEARCH TOOL =====
60
230
  {
61
231
  name: "search",
@@ -89,7 +259,7 @@ function setupTools(server) {
89
259
  type: {
90
260
  type: "string",
91
261
  description: "Filter by type",
92
- enum: ["plan", "node", "phase", "task", "milestone", "artifact", "log"]
262
+ enum: ["plan", "node", "phase", "task", "milestone", "log"]
93
263
  },
94
264
  limit: {
95
265
  type: "integer",
@@ -165,6 +335,31 @@ function setupTools(server) {
165
335
  required: ["plan_id"]
166
336
  }
167
337
  },
338
+ {
339
+ name: "share_plan",
340
+ description: "Share a plan by making it public or private. Public plans can be viewed by anyone with the link.",
341
+ inputSchema: {
342
+ type: "object",
343
+ properties: {
344
+ plan_id: { type: "string", description: "Plan ID to share" },
345
+ visibility: {
346
+ type: "string",
347
+ description: "Plan visibility setting",
348
+ enum: ["public", "private"],
349
+ default: "public"
350
+ },
351
+ github_repo_owner: {
352
+ type: "string",
353
+ description: "GitHub repository owner (optional, for linking public plans to a repo)"
354
+ },
355
+ github_repo_name: {
356
+ type: "string",
357
+ description: "GitHub repository name (optional, for linking public plans to a repo)"
358
+ }
359
+ },
360
+ required: ["plan_id"]
361
+ }
362
+ },
168
363
 
169
364
  // ===== NODE MANAGEMENT TOOLS =====
170
365
  {
@@ -249,7 +444,7 @@ function setupTools(server) {
249
444
  },
250
445
  {
251
446
  name: "get_node_context",
252
- description: "Get comprehensive context for a node including children, logs, and artifacts",
447
+ description: "Get comprehensive context for a node including children and logs",
253
448
  inputSchema: {
254
449
  type: "object",
255
450
  properties: {
@@ -320,30 +515,6 @@ function setupTools(server) {
320
515
  }
321
516
  },
322
517
 
323
- // ===== ARTIFACT MANAGEMENT =====
324
- {
325
- name: "manage_artifact",
326
- description: "Add, get, or search for artifacts",
327
- inputSchema: {
328
- type: "object",
329
- properties: {
330
- action: {
331
- type: "string",
332
- description: "Action to perform",
333
- enum: ["add", "get", "search", "list"]
334
- },
335
- plan_id: { type: "string", description: "Plan ID" },
336
- node_id: { type: "string", description: "Node ID" },
337
- artifact_id: { type: "string", description: "Artifact ID (for 'get' action)" },
338
- name: { type: "string", description: "Artifact name (for 'add' or 'search')" },
339
- content_type: { type: "string", description: "Content MIME type (for 'add')" },
340
- url: { type: "string", description: "URL where artifact can be accessed (for 'add')" },
341
- metadata: { type: "object", description: "Additional metadata (for 'add')" }
342
- },
343
- required: ["action", "plan_id", "node_id"]
344
- }
345
- },
346
-
347
518
  // ===== BATCH OPERATIONS =====
348
519
  {
349
520
  name: "batch_update_nodes",
@@ -373,72 +544,947 @@ function setupTools(server) {
373
544
  required: ["plan_id", "updates"]
374
545
  }
375
546
  },
547
+
548
+ // ===== PLAN STRUCTURE & SUMMARY =====
376
549
  {
377
- name: "batch_get_artifacts",
378
- description: "Get multiple artifacts at once",
550
+ 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.",
379
552
  inputSchema: {
380
553
  type: "object",
381
554
  properties: {
382
555
  plan_id: { type: "string", description: "Plan ID" },
383
- artifact_requests: {
384
- type: "array",
385
- description: "List of artifact requests",
556
+ include_details: {
557
+ type: "boolean",
558
+ description: "Include full node details (description, context, agent_instructions, etc.). Default is false for efficient context usage.",
559
+ default: false
560
+ }
561
+ },
562
+ required: ["plan_id"]
563
+ }
564
+ },
565
+ {
566
+ name: "get_plan_summary",
567
+ description: "Get a comprehensive summary with statistics",
568
+ inputSchema: {
569
+ type: "object",
570
+ properties: {
571
+ plan_id: { type: "string", description: "Plan ID" }
572
+ },
573
+ required: ["plan_id"]
574
+ }
575
+ },
576
+
577
+ // ===== 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
+ {
603
+ 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.",
605
+ inputSchema: {
606
+ type: "object",
607
+ properties: {
608
+ plan_id: {
609
+ type: "string",
610
+ description: "Plan ID"
611
+ },
612
+ include_knowledge: {
613
+ type: "boolean",
614
+ description: "Include knowledge entries",
615
+ default: true
616
+ }
617
+ },
618
+ required: ["plan_id"]
619
+ }
620
+ },
621
+
622
+ // ===== ORGANIZATION TOOLS =====
623
+ {
624
+ name: "list_organizations",
625
+ description: "List all organizations the user is a member of",
626
+ inputSchema: {
627
+ type: "object",
628
+ properties: {}
629
+ }
630
+ },
631
+ {
632
+ name: "get_organization",
633
+ description: "Get organization details including member count and plan count",
634
+ inputSchema: {
635
+ type: "object",
636
+ properties: {
637
+ organization_id: { type: "string", description: "Organization ID" }
638
+ },
639
+ required: ["organization_id"]
640
+ }
641
+ },
642
+ {
643
+ name: "create_organization",
644
+ description: "Create a new organization. You become the owner.",
645
+ inputSchema: {
646
+ type: "object",
647
+ properties: {
648
+ name: { type: "string", description: "Organization name" },
649
+ description: { type: "string", description: "Organization description" },
650
+ slug: { type: "string", description: "URL-friendly slug (auto-generated if not provided)" }
651
+ },
652
+ required: ["name"]
653
+ }
654
+ },
655
+ {
656
+ name: "update_organization",
657
+ description: "Update organization details (owner only)",
658
+ inputSchema: {
659
+ type: "object",
660
+ properties: {
661
+ organization_id: { type: "string", description: "Organization ID" },
662
+ name: { type: "string", description: "New name" },
663
+ description: { type: "string", description: "New description" }
664
+ },
665
+ required: ["organization_id"]
666
+ }
667
+ },
668
+
669
+ // ===== GOAL TOOLS =====
670
+ {
671
+ name: "list_goals",
672
+ description: "List goals, optionally filtered by organization or status",
673
+ inputSchema: {
674
+ type: "object",
675
+ properties: {
676
+ organization_id: { type: "string", description: "Filter by organization ID" },
677
+ status: {
678
+ type: "string",
679
+ description: "Filter by status",
680
+ enum: ["active", "achieved", "at_risk", "abandoned"]
681
+ }
682
+ }
683
+ }
684
+ },
685
+ {
686
+ name: "get_goal",
687
+ description: "Get goal details including linked plans",
688
+ inputSchema: {
689
+ type: "object",
690
+ properties: {
691
+ goal_id: { type: "string", description: "Goal ID" }
692
+ },
693
+ required: ["goal_id"]
694
+ }
695
+ },
696
+ {
697
+ name: "create_goal",
698
+ description: "Create a new goal within an organization",
699
+ inputSchema: {
700
+ type: "object",
701
+ properties: {
702
+ organization_id: { type: "string", description: "Organization ID" },
703
+ title: { type: "string", description: "Goal title" },
704
+ description: { type: "string", description: "Goal description" },
705
+ success_metrics: {
706
+ type: "array",
707
+ description: "Success metrics array [{metric, target, current, unit}]",
386
708
  items: {
387
709
  type: "object",
388
710
  properties: {
389
- node_id: { type: "string", description: "Node ID" },
390
- artifact_id: { type: "string", description: "Artifact ID" }
391
- },
392
- required: ["node_id", "artifact_id"]
711
+ metric: { type: "string" },
712
+ target: { type: "number" },
713
+ current: { type: "number" },
714
+ unit: { type: "string" }
715
+ }
393
716
  }
394
- }
717
+ },
718
+ time_horizon: { type: "string", description: "Target date (ISO format)" },
719
+ github_repo_url: { type: "string", description: "Related GitHub repo URL" }
395
720
  },
396
- required: ["plan_id", "artifact_requests"]
721
+ required: ["organization_id", "title"]
722
+ }
723
+ },
724
+ {
725
+ name: "update_goal",
726
+ description: "Update goal details or status",
727
+ inputSchema: {
728
+ type: "object",
729
+ properties: {
730
+ goal_id: { type: "string", description: "Goal ID" },
731
+ title: { type: "string", description: "New title" },
732
+ description: { type: "string", description: "New description" },
733
+ status: {
734
+ type: "string",
735
+ description: "New status",
736
+ enum: ["active", "achieved", "at_risk", "abandoned"]
737
+ },
738
+ success_metrics: { type: "array", description: "Updated metrics" },
739
+ time_horizon: { type: "string", description: "New target date" }
740
+ },
741
+ required: ["goal_id"]
742
+ }
743
+ },
744
+ {
745
+ name: "link_plan_to_goal",
746
+ description: "Link a plan to a goal (shows the plan contributes to this goal)",
747
+ inputSchema: {
748
+ type: "object",
749
+ properties: {
750
+ goal_id: { type: "string", description: "Goal ID" },
751
+ plan_id: { type: "string", description: "Plan ID to link" }
752
+ },
753
+ required: ["goal_id", "plan_id"]
754
+ }
755
+ },
756
+ {
757
+ name: "unlink_plan_from_goal",
758
+ description: "Remove a plan-goal link",
759
+ inputSchema: {
760
+ type: "object",
761
+ properties: {
762
+ goal_id: { type: "string", description: "Goal ID" },
763
+ plan_id: { type: "string", description: "Plan ID to unlink" }
764
+ },
765
+ required: ["goal_id", "plan_id"]
766
+ }
767
+ },
768
+
769
+ // ===== KNOWLEDGE TOOLS =====
770
+ {
771
+ name: "add_knowledge_entry",
772
+ description: "Add a knowledge entry (decision, context, constraint, learning, reference, note) to a scope",
773
+ inputSchema: {
774
+ type: "object",
775
+ 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" } }
791
+ },
792
+ required: ["scope", "scope_id", "entry_type", "title", "content"]
793
+ }
794
+ },
795
+ {
796
+ name: "list_knowledge_entries",
797
+ description: "List knowledge entries for a scope",
798
+ inputSchema: {
799
+ type: "object",
800
+ 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 }
814
+ },
815
+ required: ["scope", "scope_id"]
816
+ }
817
+ },
818
+ {
819
+ name: "search_knowledge",
820
+ description: "Search knowledge entries across scopes using text search",
821
+ inputSchema: {
822
+ type: "object",
823
+ 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 }
837
+ },
838
+ required: ["query"]
839
+ }
840
+ },
841
+ {
842
+ name: "update_knowledge_entry",
843
+ description: "Update a knowledge entry",
844
+ inputSchema: {
845
+ type: "object",
846
+ 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" } }
852
+ },
853
+ required: ["entry_id"]
854
+ }
855
+ },
856
+ {
857
+ name: "delete_knowledge_entry",
858
+ description: "Delete a knowledge entry",
859
+ inputSchema: {
860
+ type: "object",
861
+ properties: {
862
+ entry_id: { type: "string", description: "Entry ID to delete" }
863
+ },
864
+ required: ["entry_id"]
865
+ }
866
+ },
867
+
868
+ // ===== HELPER / GUIDANCE TOOLS =====
869
+ {
870
+ name: "get_started",
871
+ description: "Get guidance on how to use AgentPlanner effectively. Returns an overview of the system and recommended workflows for common tasks. Call this when you're new to AgentPlanner or need to understand how to approach a task.",
872
+ inputSchema: {
873
+ type: "object",
874
+ properties: {
875
+ topic: {
876
+ type: "string",
877
+ enum: ["overview", "planning", "execution", "knowledge", "collaboration"],
878
+ description: "Specific topic to learn about: 'overview' (system intro), 'planning' (creating plans), 'execution' (working through tasks), 'knowledge' (storing decisions/learnings), 'collaboration' (working with others)",
879
+ default: "overview"
880
+ }
881
+ }
882
+ }
883
+ },
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
+ ]
897
+ };
898
+ });
899
+
900
+ // Handler for calling tools
901
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
902
+ const { name, arguments: args } = request.params;
903
+
904
+ // Only log in development mode
905
+ if (process.env.NODE_ENV === 'development') {
906
+ console.error(`Calling tool: ${name} with arguments:`, args);
907
+ }
908
+
909
+ try {
910
+ // ========================================
911
+ // QUICK ACTIONS IMPLEMENTATIONS
912
+ // ========================================
913
+
914
+ if (name === "quick_plan") {
915
+ const { title, description, tasks, goal_id, organization_id } = args;
916
+
917
+ if (!tasks || tasks.length === 0) {
918
+ return formatResponse({
919
+ error: "At least one task is required",
920
+ suggestion: "Provide an array of task titles in the 'tasks' parameter"
921
+ });
922
+ }
923
+
924
+ // Create the plan
925
+ const planData = { title };
926
+ if (description) planData.description = description;
927
+ if (organization_id) planData.organization_id = organization_id;
928
+
929
+ const plan = await apiClient.plans.createPlan(planData);
930
+
931
+ // Get the root node
932
+ const nodes = await apiClient.nodes.getNodes(plan.id);
933
+ const rootNode = nodes.find(n => n.node_type === 'root') || nodes[0];
934
+
935
+ // Create a phase for the tasks
936
+ const phase = await apiClient.nodes.createNode(plan.id, {
937
+ parent_id: rootNode.id,
938
+ node_type: 'phase',
939
+ title: 'Tasks',
940
+ status: 'not_started',
941
+ order_index: 0
942
+ });
943
+
944
+ // Create tasks
945
+ const createdTasks = [];
946
+ for (let i = 0; i < tasks.length; i++) {
947
+ const task = await apiClient.nodes.createNode(plan.id, {
948
+ parent_id: phase.id,
949
+ node_type: 'task',
950
+ title: tasks[i],
951
+ status: 'not_started',
952
+ order_index: i
953
+ });
954
+ createdTasks.push({ id: task.id, title: task.title });
955
+ }
956
+
957
+ // Link to goal if provided
958
+ if (goal_id) {
959
+ try {
960
+ await apiClient.goals.linkPlan(goal_id, plan.id);
961
+ } catch (e) {
962
+ // Goal linking failed, continue anyway
963
+ }
964
+ }
965
+
966
+ return formatResponse({
967
+ success: true,
968
+ message: `Plan "${title}" created with ${tasks.length} tasks`,
969
+ plan_id: plan.id,
970
+ plan_url: `https://www.agentplanner.io/app/plans/${plan.id}`,
971
+ phase_id: phase.id,
972
+ task_ids: createdTasks.map(t => t.id),
973
+ tasks: createdTasks,
974
+ next_steps: [
975
+ "Use quick_status to update task progress",
976
+ "Use quick_log to document your work",
977
+ "Use get_context to load full plan details"
978
+ ]
979
+ });
980
+ }
981
+
982
+ if (name === "quick_task") {
983
+ const { plan_id, title, description, phase_id, agent_instructions } = args;
984
+
985
+ // Get plan structure to find the phase
986
+ const nodes = await apiClient.nodes.getNodes(plan_id);
987
+ const flatNodes = flattenNodes(nodes);
988
+
989
+ // Find target phase
990
+ let targetPhaseId = phase_id;
991
+ if (!targetPhaseId) {
992
+ // Find first phase
993
+ const phases = flatNodes.filter(n => n.node_type === 'phase');
994
+ if (phases.length > 0) {
995
+ targetPhaseId = phases[0].id;
996
+ } else {
997
+ // No phase exists, create one
998
+ const rootNode = flatNodes.find(n => n.node_type === 'root') || nodes[0];
999
+ const newPhase = await apiClient.nodes.createNode(plan_id, {
1000
+ parent_id: rootNode.id,
1001
+ node_type: 'phase',
1002
+ title: 'Tasks',
1003
+ status: 'not_started',
1004
+ order_index: 0
1005
+ });
1006
+ targetPhaseId = newPhase.id;
1007
+ }
1008
+ }
1009
+
1010
+ // Get task count in phase for order_index
1011
+ const phaseTasks = flatNodes.filter(n => n.parent_id === targetPhaseId && n.node_type === 'task');
1012
+
1013
+ // Create the task
1014
+ const taskData = {
1015
+ parent_id: targetPhaseId,
1016
+ node_type: 'task',
1017
+ title,
1018
+ status: 'not_started',
1019
+ order_index: phaseTasks.length
1020
+ };
1021
+ if (description) taskData.description = description;
1022
+ if (agent_instructions) taskData.agent_instructions = agent_instructions;
1023
+
1024
+ const task = await apiClient.nodes.createNode(plan_id, taskData);
1025
+
1026
+ return formatResponse({
1027
+ success: true,
1028
+ message: `Task "${title}" added to plan`,
1029
+ task_id: task.id,
1030
+ plan_id: plan_id,
1031
+ phase_id: targetPhaseId,
1032
+ task_url: `https://www.agentplanner.io/app/plans/${plan_id}?node=${task.id}`,
1033
+ next_steps: [
1034
+ "Use quick_status to mark as in_progress when you start",
1035
+ "Use quick_log to document progress"
1036
+ ]
1037
+ });
1038
+ }
1039
+
1040
+ if (name === "quick_status") {
1041
+ const { task_id, plan_id, status, note } = args;
1042
+
1043
+ // Update the task status
1044
+ const updateData = { status };
1045
+ const updated = await apiClient.nodes.updateNode(plan_id, task_id, updateData);
1046
+
1047
+ // Add a log entry if note provided or if blocking
1048
+ if (note || status === 'blocked') {
1049
+ const logMessage = note || (status === 'blocked' ? 'Task blocked - needs attention' : `Status changed to ${status}`);
1050
+ try {
1051
+ await apiClient.logs.addLogEntry(plan_id, task_id, {
1052
+ type: status === 'blocked' ? 'blocker' : 'progress',
1053
+ content: logMessage
1054
+ });
1055
+ } catch (e) {
1056
+ // Log failed, continue
1057
+ }
1058
+ }
1059
+
1060
+ // Get next tasks for suggestion
1061
+ let nextTasks = [];
1062
+ try {
1063
+ const nodes = await apiClient.nodes.getNodes(plan_id);
1064
+ const flatNodes = flattenNodes(nodes);
1065
+ nextTasks = flatNodes
1066
+ .filter(n => n.node_type === 'task' && n.status === 'not_started')
1067
+ .slice(0, 3)
1068
+ .map(n => ({ id: n.id, title: n.title }));
1069
+ } catch (e) {
1070
+ // Failed to get next tasks, continue
1071
+ }
1072
+
1073
+ const response = {
1074
+ success: true,
1075
+ message: `Task status updated to "${status}"`,
1076
+ task_id,
1077
+ plan_id,
1078
+ new_status: status
1079
+ };
1080
+
1081
+ if (status === 'completed' && nextTasks.length > 0) {
1082
+ response.next_tasks = nextTasks;
1083
+ response.suggestion = "Here are the next tasks to work on";
1084
+ } else if (status === 'blocked') {
1085
+ response.suggestion = "Task marked as blocked. A human will be notified to help unblock.";
1086
+ }
1087
+
1088
+ return formatResponse(response);
1089
+ }
1090
+
1091
+ if (name === "quick_log") {
1092
+ const { task_id, plan_id, message, log_type = 'progress' } = args;
1093
+
1094
+ const logEntry = await apiClient.logs.addLogEntry(plan_id, task_id, {
1095
+ type: log_type,
1096
+ content: message
1097
+ });
1098
+
1099
+ return formatResponse({
1100
+ success: true,
1101
+ message: "Progress logged",
1102
+ log_id: logEntry.id,
1103
+ task_id,
1104
+ plan_id,
1105
+ logged: message,
1106
+ tip: "Good practice! Logging helps humans follow your work."
1107
+ });
1108
+ }
1109
+
1110
+ // ========================================
1111
+ // CONTEXT LOADING IMPLEMENTATIONS
1112
+ // ========================================
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
+
1203
+ if (name === "get_my_tasks") {
1204
+ const { plan_id, status = ["blocked", "in_progress"] } = args;
1205
+
1206
+ const tasks = {
1207
+ retrieved_at: new Date().toISOString(),
1208
+ needs_attention: [],
1209
+ ready_to_start: []
1210
+ };
1211
+
1212
+ // Get plans to check
1213
+ let plansToCheck = [];
1214
+ if (plan_id) {
1215
+ plansToCheck = [{ id: plan_id }];
1216
+ } else {
1217
+ plansToCheck = await apiClient.plans.getPlans();
1218
+ }
1219
+
1220
+ for (const plan of plansToCheck.slice(0, 10)) { // Limit to 10 plans
1221
+ try {
1222
+ const nodes = await apiClient.nodes.getNodes(plan.id);
1223
+ const flatNodes = flattenNodes(nodes);
1224
+
1225
+ const matchingTasks = flatNodes
1226
+ .filter(n => n.node_type === 'task' && status.includes(n.status))
1227
+ .map(n => ({
1228
+ id: n.id,
1229
+ title: n.title,
1230
+ status: n.status,
1231
+ plan_id: plan.id,
1232
+ plan_title: plan.title
1233
+ }));
1234
+
1235
+ tasks.needs_attention.push(...matchingTasks);
1236
+
1237
+ // Also get a few ready-to-start tasks
1238
+ const readyTasks = flatNodes
1239
+ .filter(n => n.node_type === 'task' && n.status === 'not_started')
1240
+ .slice(0, 3)
1241
+ .map(n => ({
1242
+ id: n.id,
1243
+ title: n.title,
1244
+ plan_id: plan.id,
1245
+ plan_title: plan.title
1246
+ }));
1247
+
1248
+ tasks.ready_to_start.push(...readyTasks);
1249
+ } catch (e) {
1250
+ // Skip this plan
1251
+ }
1252
+ }
1253
+
1254
+ tasks.summary = {
1255
+ blocked: tasks.needs_attention.filter(t => t.status === 'blocked').length,
1256
+ in_progress: tasks.needs_attention.filter(t => t.status === 'in_progress').length,
1257
+ ready: tasks.ready_to_start.length
1258
+ };
1259
+
1260
+ return formatResponse(tasks);
1261
+ }
1262
+
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
+
1291
+ // ========================================
1292
+ // MARKDOWN EXPORT
1293
+ // ========================================
1294
+
1295
+ if (name === "export_plan_markdown") {
1296
+ const { plan_id, include_descriptions = true, include_status = true } = args;
1297
+
1298
+ const plan = await apiClient.plans.getPlan(plan_id);
1299
+ const nodes = await apiClient.nodes.getNodes(plan_id);
1300
+
1301
+ let markdown = `# ${plan.title}\n\n`;
1302
+ if (plan.description) {
1303
+ markdown += `${plan.description}\n\n`;
1304
+ }
1305
+
1306
+ const statusEmoji = {
1307
+ not_started: '⬜',
1308
+ in_progress: '🔄',
1309
+ completed: '✅',
1310
+ blocked: '🚫',
1311
+ cancelled: '❌'
1312
+ };
1313
+
1314
+ // Process nodes recursively
1315
+ const processNode = (node, depth = 0) => {
1316
+ const indent = ' '.repeat(depth);
1317
+ const status = include_status ? (statusEmoji[node.status] || '⬜') + ' ' : '';
1318
+
1319
+ if (node.node_type === 'phase') {
1320
+ markdown += `\n${indent}## ${node.title}\n`;
1321
+ if (include_descriptions && node.description) {
1322
+ markdown += `${indent}${node.description}\n`;
1323
+ }
1324
+ } else if (node.node_type === 'task') {
1325
+ markdown += `${indent}- ${status}${node.title}\n`;
1326
+ if (include_descriptions && node.description) {
1327
+ markdown += `${indent} _${node.description}_\n`;
1328
+ }
1329
+ } else if (node.node_type === 'milestone') {
1330
+ markdown += `${indent}- 🎯 ${status}**${node.title}**\n`;
397
1331
  }
398
- },
1332
+
1333
+ if (node.children) {
1334
+ node.children.forEach(child => processNode(child, depth + 1));
1335
+ }
1336
+ };
399
1337
 
400
- // ===== PLAN STRUCTURE & SUMMARY =====
401
- {
402
- name: "get_plan_structure",
403
- description: "Get the complete hierarchical structure of a plan",
404
- inputSchema: {
405
- type: "object",
406
- properties: {
407
- plan_id: { type: "string", description: "Plan ID" },
408
- include_details: {
409
- type: "boolean",
410
- description: "Include full node details",
411
- default: false
412
- }
413
- },
414
- required: ["plan_id"]
1338
+ // Start from root's children
1339
+ if (nodes.length > 0 && nodes[0].children) {
1340
+ nodes[0].children.forEach(child => processNode(child, 0));
1341
+ }
1342
+
1343
+ return formatResponse({
1344
+ plan_id,
1345
+ title: plan.title,
1346
+ markdown,
1347
+ tip: "You can save this to a file or share it as text"
1348
+ });
1349
+ }
1350
+
1351
+ if (name === "import_plan_markdown") {
1352
+ const { markdown, title: providedTitle, goal_id } = args;
1353
+
1354
+ // Parse markdown
1355
+ const lines = markdown.split('\n').map(l => l.trim()).filter(l => l);
1356
+
1357
+ let planTitle = providedTitle;
1358
+ let planDescription = '';
1359
+ const phases = [];
1360
+ let currentPhase = null;
1361
+
1362
+ for (const line of lines) {
1363
+ // H1 = Plan title
1364
+ if (line.startsWith('# ')) {
1365
+ if (!planTitle) {
1366
+ planTitle = line.slice(2).trim();
1367
+ }
415
1368
  }
416
- },
417
- {
418
- name: "get_plan_summary",
419
- description: "Get a comprehensive summary with statistics",
420
- inputSchema: {
421
- type: "object",
422
- properties: {
423
- plan_id: { type: "string", description: "Plan ID" }
424
- },
425
- required: ["plan_id"]
1369
+ // H2 = Phase
1370
+ else if (line.startsWith('## ')) {
1371
+ const phaseTitle = line.slice(3).trim();
1372
+ currentPhase = { title: phaseTitle, tasks: [] };
1373
+ phases.push(currentPhase);
1374
+ }
1375
+ // List item = Task (with optional status emoji)
1376
+ else if (line.startsWith('- ') || line.startsWith('* ')) {
1377
+ let taskTitle = line.slice(2).trim();
1378
+ let taskStatus = 'not_started';
1379
+
1380
+ // Parse status from emoji
1381
+ if (taskTitle.startsWith('✅')) {
1382
+ taskStatus = 'completed';
1383
+ taskTitle = taskTitle.slice(1).trim();
1384
+ } else if (taskTitle.startsWith('🔄')) {
1385
+ taskStatus = 'in_progress';
1386
+ taskTitle = taskTitle.slice(1).trim();
1387
+ } else if (taskTitle.startsWith('🚫')) {
1388
+ taskStatus = 'blocked';
1389
+ taskTitle = taskTitle.slice(1).trim();
1390
+ } else if (taskTitle.startsWith('⬜')) {
1391
+ taskTitle = taskTitle.slice(1).trim();
1392
+ }
1393
+
1394
+ // Skip milestone markers for now
1395
+ if (taskTitle.startsWith('🎯')) {
1396
+ taskTitle = taskTitle.slice(1).trim().replace(/\*\*/g, '');
1397
+ }
1398
+
1399
+ if (currentPhase) {
1400
+ currentPhase.tasks.push({ title: taskTitle, status: taskStatus });
1401
+ } else {
1402
+ // No phase yet, create a default one
1403
+ currentPhase = { title: 'Tasks', tasks: [{ title: taskTitle, status: taskStatus }] };
1404
+ phases.push(currentPhase);
1405
+ }
1406
+ }
1407
+ // Regular text after title = description
1408
+ else if (planTitle && !currentPhase && !line.startsWith('#')) {
1409
+ planDescription += (planDescription ? ' ' : '') + line;
426
1410
  }
427
1411
  }
428
- ]
429
- };
430
- });
431
-
432
- // Handler for calling tools
433
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
434
- const { name, arguments: args } = request.params;
435
-
436
- // Only log in development mode
437
- if (process.env.NODE_ENV === 'development') {
438
- console.error(`Calling tool: ${name} with arguments:`, args);
439
- }
440
-
441
- try {
1412
+
1413
+ if (!planTitle) {
1414
+ return formatResponse({
1415
+ error: "Could not extract plan title",
1416
+ suggestion: "Add a '# Title' heading at the start, or provide the 'title' parameter"
1417
+ });
1418
+ }
1419
+
1420
+ if (phases.length === 0) {
1421
+ return formatResponse({
1422
+ error: "No phases or tasks found",
1423
+ suggestion: "Use '## Phase Name' for phases and '- Task name' for tasks"
1424
+ });
1425
+ }
1426
+
1427
+ // Create the plan
1428
+ const planData = { title: planTitle };
1429
+ if (planDescription) planData.description = planDescription;
1430
+
1431
+ const plan = await apiClient.plans.createPlan(planData);
1432
+
1433
+ // Get root node
1434
+ const nodes = await apiClient.nodes.getNodes(plan.id);
1435
+ const rootNode = nodes.find(n => n.node_type === 'root') || nodes[0];
1436
+
1437
+ // Create phases and tasks
1438
+ const createdPhases = [];
1439
+ const createdTasks = [];
1440
+
1441
+ for (let i = 0; i < phases.length; i++) {
1442
+ const phaseData = phases[i];
1443
+
1444
+ const phase = await apiClient.nodes.createNode(plan.id, {
1445
+ parent_id: rootNode.id,
1446
+ node_type: 'phase',
1447
+ title: phaseData.title,
1448
+ status: 'not_started',
1449
+ order_index: i
1450
+ });
1451
+ createdPhases.push({ id: phase.id, title: phase.title });
1452
+
1453
+ for (let j = 0; j < phaseData.tasks.length; j++) {
1454
+ const taskData = phaseData.tasks[j];
1455
+
1456
+ const task = await apiClient.nodes.createNode(plan.id, {
1457
+ parent_id: phase.id,
1458
+ node_type: 'task',
1459
+ title: taskData.title,
1460
+ status: taskData.status,
1461
+ order_index: j
1462
+ });
1463
+ createdTasks.push({ id: task.id, title: task.title, status: task.status });
1464
+ }
1465
+ }
1466
+
1467
+ // Link to goal if provided
1468
+ if (goal_id) {
1469
+ try {
1470
+ await apiClient.goals.linkPlan(goal_id, plan.id);
1471
+ } catch (e) {}
1472
+ }
1473
+
1474
+ return formatResponse({
1475
+ success: true,
1476
+ message: `Plan "${planTitle}" created from markdown with ${phases.length} phases and ${createdTasks.length} tasks`,
1477
+ plan_id: plan.id,
1478
+ plan_url: `https://www.agentplanner.io/app/plans/${plan.id}`,
1479
+ phases: createdPhases,
1480
+ tasks: createdTasks,
1481
+ next_steps: [
1482
+ "Use get_context to review the imported plan",
1483
+ "Use quick_status to update task progress"
1484
+ ]
1485
+ });
1486
+ }
1487
+
442
1488
  // ===== UNIFIED SEARCH TOOL =====
443
1489
  if (name === "search") {
444
1490
  const { scope, scope_id, query, filters = {} } = args;
@@ -544,6 +1590,33 @@ function setupTools(server) {
544
1590
  });
545
1591
  }
546
1592
 
1593
+ if (name === "share_plan") {
1594
+ const { plan_id, visibility = "public", github_repo_owner, github_repo_name } = args;
1595
+
1596
+ const visibilityData = { visibility };
1597
+ if (github_repo_owner) visibilityData.github_repo_owner = github_repo_owner;
1598
+ if (github_repo_name) visibilityData.github_repo_name = github_repo_name;
1599
+
1600
+ const result = await apiClient.plans.updateVisibility(plan_id, visibilityData);
1601
+
1602
+ const shareUrl = visibility === "public"
1603
+ ? `https://www.agentplanner.io/app/plans/${plan_id}`
1604
+ : null;
1605
+
1606
+ return formatResponse({
1607
+ success: true,
1608
+ plan_id: plan_id,
1609
+ visibility: result.visibility,
1610
+ is_public: result.is_public,
1611
+ share_url: shareUrl,
1612
+ github_repo_owner: result.github_repo_owner,
1613
+ github_repo_name: result.github_repo_name,
1614
+ message: visibility === "public"
1615
+ ? `Plan is now public. Share URL: ${shareUrl}`
1616
+ : `Plan is now private.`
1617
+ });
1618
+ }
1619
+
547
1620
  // ===== NODE MANAGEMENT =====
548
1621
  if (name === "create_node") {
549
1622
  const { plan_id, ...nodeData } = args;
@@ -647,48 +1720,6 @@ function setupTools(server) {
647
1720
  return formatResponse(logs);
648
1721
  }
649
1722
 
650
- // ===== ARTIFACT MANAGEMENT =====
651
- if (name === "manage_artifact") {
652
- const { action, plan_id, node_id, ...params } = args;
653
-
654
- switch (action) {
655
- case "add":
656
- const { name, content_type, url, metadata } = params;
657
- const newArtifact = await apiClient.artifacts.addArtifact(plan_id, node_id, {
658
- name,
659
- content_type,
660
- url,
661
- metadata
662
- });
663
- return formatResponse(newArtifact);
664
-
665
- case "get":
666
- const { artifact_id } = params;
667
- const artifact = await apiClient.artifacts.getArtifact(plan_id, node_id, artifact_id);
668
- const content = await apiClient.artifacts.getArtifactContent(plan_id, node_id, artifact_id);
669
- return formatResponse({
670
- ...artifact,
671
- content
672
- });
673
-
674
- case "search":
675
- const { name: searchName } = params;
676
- const artifacts = await apiClient.artifacts.getArtifacts(plan_id, node_id);
677
- const searchLower = searchName.toLowerCase();
678
- const matches = artifacts.filter(a =>
679
- a.name.toLowerCase().includes(searchLower)
680
- );
681
- return formatResponse(matches);
682
-
683
- case "list":
684
- const allArtifacts = await apiClient.artifacts.getArtifacts(plan_id, node_id);
685
- return formatResponse(allArtifacts);
686
-
687
- default:
688
- throw new Error(`Unknown artifact action: ${action}`);
689
- }
690
- }
691
-
692
1723
  // ===== BATCH OPERATIONS =====
693
1724
  if (name === "batch_update_nodes") {
694
1725
  const { plan_id, updates } = args;
@@ -715,49 +1746,16 @@ function setupTools(server) {
715
1746
  });
716
1747
  }
717
1748
 
718
- if (name === "batch_get_artifacts") {
719
- const { plan_id, artifact_requests } = args;
720
-
721
- const results = [];
722
- const errors = [];
723
-
724
- for (const request of artifact_requests) {
725
- const { node_id, artifact_id } = request;
726
- try {
727
- const artifact = await apiClient.artifacts.getArtifact(plan_id, node_id, artifact_id);
728
- const content = await apiClient.artifacts.getArtifactContent(plan_id, node_id, artifact_id);
729
- results.push({
730
- node_id,
731
- artifact_id,
732
- success: true,
733
- data: { ...artifact, content }
734
- });
735
- } catch (error) {
736
- errors.push({
737
- node_id,
738
- artifact_id,
739
- success: false,
740
- error: error.message
741
- });
742
- }
743
- }
744
-
745
- return formatResponse({
746
- total: artifact_requests.length,
747
- successful: results.length,
748
- failed: errors.length,
749
- results,
750
- errors
751
- });
752
- }
753
-
754
1749
  // ===== PLAN STRUCTURE & SUMMARY =====
755
1750
  if (name === "get_plan_structure") {
756
1751
  const { plan_id, include_details = false } = args;
757
-
1752
+
758
1753
  const plan = await apiClient.plans.getPlan(plan_id);
759
- const nodes = await apiClient.nodes.getNodes(plan_id);
760
-
1754
+ // Pass include_details to the API - defaults to minimal fields
1755
+ const nodes = await apiClient.nodes.getNodes(plan_id, {
1756
+ include_details: include_details
1757
+ });
1758
+
761
1759
  // The API already returns a tree structure, not a flat list
762
1760
  // If it's already hierarchical, use it directly
763
1761
  let structure;
@@ -768,7 +1766,7 @@ function setupTools(server) {
768
1766
  // Flat list - build hierarchy
769
1767
  structure = buildNodeHierarchy(nodes, include_details);
770
1768
  }
771
-
1769
+
772
1770
  return formatResponse({
773
1771
  plan: {
774
1772
  id: plan.id,
@@ -805,6 +1803,369 @@ function setupTools(server) {
805
1803
  });
806
1804
  }
807
1805
 
1806
+ // ===== 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
+ if (name === "get_plan_context") {
1819
+ const { plan_id, include_knowledge = true } = args;
1820
+
1821
+ const result = await apiClient.context.getPlanContext(plan_id, {
1822
+ include_knowledge
1823
+ });
1824
+
1825
+ return formatResponse(result);
1826
+ }
1827
+
1828
+ // ===== ORGANIZATION TOOLS =====
1829
+ if (name === "list_organizations") {
1830
+ const result = await apiClient.organizations.list();
1831
+ return formatResponse(result);
1832
+ }
1833
+
1834
+ if (name === "get_organization") {
1835
+ const { organization_id } = args;
1836
+ const result = await apiClient.organizations.get(organization_id);
1837
+ return formatResponse(result);
1838
+ }
1839
+
1840
+ if (name === "create_organization") {
1841
+ const { name, description, slug } = args;
1842
+ const result = await apiClient.organizations.create({ name, description, slug });
1843
+ return formatResponse(result);
1844
+ }
1845
+
1846
+ if (name === "update_organization") {
1847
+ const { organization_id, ...updateData } = args;
1848
+ const result = await apiClient.organizations.update(organization_id, updateData);
1849
+ return formatResponse(result);
1850
+ }
1851
+
1852
+ // ===== GOAL TOOLS =====
1853
+ if (name === "list_goals") {
1854
+ const { organization_id, status } = args;
1855
+ const result = await apiClient.goals.list({ organization_id, status });
1856
+ return formatResponse(result);
1857
+ }
1858
+
1859
+ if (name === "get_goal") {
1860
+ const { goal_id } = args;
1861
+ const result = await apiClient.goals.get(goal_id);
1862
+ return formatResponse(result);
1863
+ }
1864
+
1865
+ if (name === "create_goal") {
1866
+ const { organization_id, title, description, success_metrics, time_horizon, github_repo_url } = args;
1867
+ const result = await apiClient.goals.create({
1868
+ organization_id,
1869
+ title,
1870
+ description,
1871
+ success_metrics,
1872
+ time_horizon,
1873
+ github_repo_url
1874
+ });
1875
+ return formatResponse(result);
1876
+ }
1877
+
1878
+ if (name === "update_goal") {
1879
+ const { goal_id, ...updateData } = args;
1880
+ const result = await apiClient.goals.update(goal_id, updateData);
1881
+ return formatResponse(result);
1882
+ }
1883
+
1884
+ if (name === "link_plan_to_goal") {
1885
+ const { goal_id, plan_id } = args;
1886
+ const result = await apiClient.goals.linkPlan(goal_id, plan_id);
1887
+ return formatResponse({
1888
+ success: true,
1889
+ message: `Plan ${plan_id} linked to goal ${goal_id}`,
1890
+ ...result
1891
+ });
1892
+ }
1893
+
1894
+ if (name === "unlink_plan_from_goal") {
1895
+ const { goal_id, plan_id } = args;
1896
+ const result = await apiClient.goals.unlinkPlan(goal_id, plan_id);
1897
+ return formatResponse({
1898
+ success: true,
1899
+ message: `Plan ${plan_id} unlinked from goal ${goal_id}`,
1900
+ ...result
1901
+ });
1902
+ }
1903
+
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
1915
+ });
1916
+ return formatResponse(result);
1917
+ }
1918
+
1919
+ if (name === "list_knowledge_entries") {
1920
+ const { scope, scope_id, entry_type, tags, limit = 50 } = args;
1921
+
1922
+ const result = await apiClient.knowledge.listEntries({ scope, scope_id, entry_type, tags, limit });
1923
+ return formatResponse(result);
1924
+ }
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);
1940
+ return formatResponse(result);
1941
+ }
1942
+
1943
+ if (name === "update_knowledge_entry") {
1944
+ const { entry_id, ...updateData } = args;
1945
+ const result = await apiClient.knowledge.updateEntry(entry_id, updateData);
1946
+ return formatResponse(result);
1947
+ }
1948
+
1949
+ if (name === "delete_knowledge_entry") {
1950
+ const { entry_id } = args;
1951
+ await apiClient.knowledge.deleteEntry(entry_id);
1952
+ return formatResponse({
1953
+ success: true,
1954
+ message: `Knowledge entry ${entry_id} deleted`
1955
+ });
1956
+ }
1957
+
1958
+ // ===== HELPER TOOLS =====
1959
+ if (name === "get_started") {
1960
+ const { topic = "overview" } = args || {};
1961
+
1962
+ const guides = {
1963
+ overview: {
1964
+ title: "AgentPlanner Overview",
1965
+ description: "AgentPlanner is a collaborative planning system for AI agents and humans to work together on structured plans.",
1966
+ key_concepts: [
1967
+ "Organizations - Groups of users, goals, and resources",
1968
+ "Goals - High-level objectives with success metrics that plans work toward",
1969
+ "Plans - Hierarchical structures with phases, tasks, and milestones",
1970
+ "Nodes - Individual items in a plan (phases contain tasks and milestones)",
1971
+ "Knowledge - Persistent storage for decisions, context, constraints, and learnings"
1972
+ ],
1973
+ recommended_workflow: [
1974
+ "1. Check list_goals to understand current objectives",
1975
+ "2. Use list_plans to see existing plans",
1976
+ "3. Before working on a plan, use understand_context to get the full picture",
1977
+ "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"
1980
+ ],
1981
+ quick_tips: [
1982
+ "Always capture WHY decisions were made, not just WHAT",
1983
+ "Mark tasks 'blocked' with notes when stuck - this helps humans help you",
1984
+ "Use logs to document progress so others can follow your work"
1985
+ ]
1986
+ },
1987
+ planning: {
1988
+ title: "Planning Best Practices",
1989
+ description: "How to create and structure effective plans.",
1990
+ structure: [
1991
+ "Plans have a hierarchical structure: Plan → Phases → Tasks/Milestones",
1992
+ "Phases are major stages or milestones of work",
1993
+ "Tasks are actionable work items within phases",
1994
+ "Milestones mark significant checkpoints"
1995
+ ],
1996
+ tips: [
1997
+ "Break work into phases (major stages)",
1998
+ "Each phase should contain 3-7 tasks (not too granular, not too big)",
1999
+ "Add clear acceptance_criteria to tasks so completion is unambiguous",
2000
+ "Use agent_instructions to guide how AI agents should approach tasks",
2001
+ "Link plans to goals to track how work contributes to objectives"
2002
+ ],
2003
+ tools_to_use: ["create_plan", "create_node", "get_plan_structure", "link_plan_to_goal"]
2004
+ },
2005
+ execution: {
2006
+ title: "Executing Plans",
2007
+ description: "How to work through plans effectively.",
2008
+ workflow: [
2009
+ "1. Use get_plan_structure to see the full plan",
2010
+ "2. Find tasks with status 'not_started' or 'in_progress'",
2011
+ "3. Before starting a task, check search_knowledge for relevant context",
2012
+ "4. Update task status to 'in_progress' when you begin",
2013
+ "5. Add logs to document what you're doing",
2014
+ "6. Mark 'completed' when done, or 'blocked' if stuck"
2015
+ ],
2016
+ status_values: {
2017
+ not_started: "Work hasn't begun",
2018
+ in_progress: "Currently being worked on",
2019
+ completed: "Finished and verified",
2020
+ blocked: "Cannot proceed - add notes explaining why",
2021
+ cancelled: "No longer needed"
2022
+ },
2023
+ tips: [
2024
+ "Check get_plan_summary for current progress and blockers",
2025
+ "When blocked, clearly document what's blocking you",
2026
+ "Store learnings as you go - don't wait until the end"
2027
+ ],
2028
+ tools_to_use: ["get_plan_structure", "update_node", "add_log", "search_knowledge"]
2029
+ },
2030
+ knowledge: {
2031
+ title: "Knowledge Management",
2032
+ description: "How to capture and use organizational knowledge effectively.",
2033
+ entry_types: {
2034
+ decision: "Choices made and their rationale - ALWAYS capture WHY",
2035
+ context: "Background information needed to understand something",
2036
+ constraint: "Rules, limitations, or requirements that must be respected",
2037
+ learning: "Insights gained from experience - what worked, what didn't",
2038
+ reference: "Links to external resources or documentation",
2039
+ note: "General notes that don't fit other categories"
2040
+ },
2041
+ best_practices: [
2042
+ "ALWAYS capture significant decisions with reasoning",
2043
+ "Search knowledge BEFORE making decisions (check for constraints)",
2044
+ "Add learnings when you discover something useful",
2045
+ "Tag entries well for easier retrieval later",
2046
+ "Include enough context that future-you can understand"
2047
+ ],
2048
+ when_to_create_entries: [
2049
+ "When a decision is made (especially if non-obvious)",
2050
+ "When you learn something that might be useful later",
2051
+ "When you discover a constraint or rule",
2052
+ "When you find a useful resource or reference"
2053
+ ],
2054
+ tools_to_use: ["add_knowledge_entry", "search_knowledge", "list_knowledge_entries"]
2055
+ },
2056
+ collaboration: {
2057
+ title: "Collaboration",
2058
+ description: "Working with humans and other agents.",
2059
+ tips: [
2060
+ "Plans can be shared with collaborators (viewer, editor, admin roles)",
2061
+ "Use logs to document progress so others can follow your work",
2062
+ "Knowledge stores are shared within their scope (org/goal/plan)",
2063
+ "When stuck, mark tasks as 'blocked' with clear notes - humans will see this"
2064
+ ],
2065
+ communication: [
2066
+ "Logs are visible to all plan collaborators",
2067
+ "Knowledge entries persist and are searchable by others",
2068
+ "Clear status updates help humans understand where things stand"
2069
+ ],
2070
+ tools_to_use: ["list_organizations", "list_goals", "add_log"]
2071
+ }
2072
+ };
2073
+
2074
+ return formatResponse(guides[topic] || guides.overview);
2075
+ }
2076
+
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
+ }
2163
+ }
2164
+
2165
+ context.recommendation = "Review the statistics, needs_attention, and knowledge entries before starting work.";
2166
+ return formatResponse(context);
2167
+ }
2168
+
808
2169
  // Tool not found
809
2170
  throw new Error(`Unknown tool: ${name}`);
810
2171
  } catch (error) {
@@ -935,6 +2296,26 @@ function buildNodeHierarchy(nodes, includeDetails = false) {
935
2296
  return rootNodes;
936
2297
  }
937
2298
 
2299
+ /**
2300
+ * Flatten a hierarchical node structure into a flat array
2301
+ */
2302
+ function flattenNodes(nodes) {
2303
+ const flat = [];
2304
+
2305
+ const processNode = (node) => {
2306
+ flat.push(node);
2307
+ if (node.children && node.children.length > 0) {
2308
+ node.children.forEach(processNode);
2309
+ }
2310
+ };
2311
+
2312
+ if (Array.isArray(nodes)) {
2313
+ nodes.forEach(processNode);
2314
+ }
2315
+
2316
+ return flat;
2317
+ }
2318
+
938
2319
  /**
939
2320
  * Calculate plan statistics
940
2321
  */