agent-planner-mcp 0.2.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/README.md +324 -0
- package/package.json +53 -0
- package/src/api-client.js +540 -0
- package/src/index.js +68 -0
- package/src/integrations/search-integration.js +88 -0
- package/src/search-plan-wrapper.js +33 -0
- package/src/setup.js +347 -0
- package/src/test-server.js +3 -0
- package/src/tools/search-wrapper.js +188 -0
- package/src/tools.js +995 -0
package/src/tools.js
ADDED
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools Implementation
|
|
3
|
+
*
|
|
4
|
+
* Provides comprehensive planning tools for AI agents:
|
|
5
|
+
* - Full CRUD operations on all entities
|
|
6
|
+
* - Unified search across all scopes
|
|
7
|
+
* - Batch operations for efficiency
|
|
8
|
+
* - Rich context retrieval
|
|
9
|
+
* - Text responses for Claude Desktop compatibility
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');
|
|
13
|
+
const apiClient = require('./api-client');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Format JSON data as text for Claude Desktop
|
|
17
|
+
*/
|
|
18
|
+
function formatResponse(data) {
|
|
19
|
+
// If data is an error object with a message, return just the message
|
|
20
|
+
if (data && data.error) {
|
|
21
|
+
return {
|
|
22
|
+
isError: true,
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: data.error
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// For successful responses, stringify the data
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: "text",
|
|
37
|
+
text: JSON.stringify(data, null, 2)
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Setup tools for the MCP server
|
|
45
|
+
* @param {Server} server - MCP server instance
|
|
46
|
+
*/
|
|
47
|
+
function setupTools(server) {
|
|
48
|
+
// Suppress console logs when not in debug mode
|
|
49
|
+
if (process.env.NODE_ENV !== 'development') {
|
|
50
|
+
// Silent mode for production
|
|
51
|
+
} else {
|
|
52
|
+
console.error('Setting up MCP tools...');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handler for listing available tools
|
|
56
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
57
|
+
return {
|
|
58
|
+
tools: [
|
|
59
|
+
// ===== UNIFIED SEARCH TOOL =====
|
|
60
|
+
{
|
|
61
|
+
name: "search",
|
|
62
|
+
description: "Universal search tool for plans, nodes, and content",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
scope: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "Search scope",
|
|
69
|
+
enum: ["global", "plans", "plan", "node"],
|
|
70
|
+
default: "global"
|
|
71
|
+
},
|
|
72
|
+
scope_id: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Plan ID (if scope is 'plan') or Node ID (if scope is 'node')"
|
|
75
|
+
},
|
|
76
|
+
query: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "Search query"
|
|
79
|
+
},
|
|
80
|
+
filters: {
|
|
81
|
+
type: "object",
|
|
82
|
+
description: "Optional filters",
|
|
83
|
+
properties: {
|
|
84
|
+
status: {
|
|
85
|
+
type: "string",
|
|
86
|
+
description: "Filter by status",
|
|
87
|
+
enum: ["draft", "active", "completed", "archived", "not_started", "in_progress", "blocked"]
|
|
88
|
+
},
|
|
89
|
+
type: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description: "Filter by type",
|
|
92
|
+
enum: ["plan", "node", "phase", "task", "milestone", "artifact", "log"]
|
|
93
|
+
},
|
|
94
|
+
limit: {
|
|
95
|
+
type: "integer",
|
|
96
|
+
description: "Maximum number of results",
|
|
97
|
+
default: 20
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
required: ["query"]
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// ===== PLAN MANAGEMENT TOOLS =====
|
|
107
|
+
{
|
|
108
|
+
name: "list_plans",
|
|
109
|
+
description: "List all plans or filter by status",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
status: {
|
|
114
|
+
type: "string",
|
|
115
|
+
description: "Optional filter by plan status",
|
|
116
|
+
enum: ["draft", "active", "completed", "archived"]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "create_plan",
|
|
123
|
+
description: "Create a new plan",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
title: { type: "string", description: "Plan title" },
|
|
128
|
+
description: { type: "string", description: "Plan description" },
|
|
129
|
+
status: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "Plan status",
|
|
132
|
+
enum: ["draft", "active", "completed", "archived"],
|
|
133
|
+
default: "draft"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
required: ["title"]
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "update_plan",
|
|
141
|
+
description: "Update an existing plan",
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: "object",
|
|
144
|
+
properties: {
|
|
145
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
146
|
+
title: { type: "string", description: "New plan title" },
|
|
147
|
+
description: { type: "string", description: "New plan description" },
|
|
148
|
+
status: {
|
|
149
|
+
type: "string",
|
|
150
|
+
description: "New plan status",
|
|
151
|
+
enum: ["draft", "active", "completed", "archived"]
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
required: ["plan_id"]
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "delete_plan",
|
|
159
|
+
description: "Delete a plan",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: "object",
|
|
162
|
+
properties: {
|
|
163
|
+
plan_id: { type: "string", description: "Plan ID to delete" }
|
|
164
|
+
},
|
|
165
|
+
required: ["plan_id"]
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// ===== NODE MANAGEMENT TOOLS =====
|
|
170
|
+
{
|
|
171
|
+
name: "create_node",
|
|
172
|
+
description: "Create a new node in a plan",
|
|
173
|
+
inputSchema: {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties: {
|
|
176
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
177
|
+
parent_id: { type: "string", description: "Parent node ID (optional, defaults to root)" },
|
|
178
|
+
node_type: {
|
|
179
|
+
type: "string",
|
|
180
|
+
description: "Node type",
|
|
181
|
+
enum: ["phase", "task", "milestone"]
|
|
182
|
+
},
|
|
183
|
+
title: { type: "string", description: "Node title" },
|
|
184
|
+
description: { type: "string", description: "Node description" },
|
|
185
|
+
status: {
|
|
186
|
+
type: "string",
|
|
187
|
+
description: "Node status",
|
|
188
|
+
enum: ["not_started", "in_progress", "completed", "blocked"],
|
|
189
|
+
default: "not_started"
|
|
190
|
+
},
|
|
191
|
+
context: { type: "string", description: "Additional context for the node" },
|
|
192
|
+
agent_instructions: { type: "string", description: "Instructions for AI agents working on this node" },
|
|
193
|
+
acceptance_criteria: { type: "string", description: "Criteria for node completion" },
|
|
194
|
+
due_date: { type: "string", description: "Due date (ISO format)" },
|
|
195
|
+
metadata: { type: "object", description: "Additional metadata" }
|
|
196
|
+
},
|
|
197
|
+
required: ["plan_id", "node_type", "title"]
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: "update_node",
|
|
202
|
+
description: "Update a node's properties",
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: "object",
|
|
205
|
+
properties: {
|
|
206
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
207
|
+
node_id: { type: "string", description: "Node ID" },
|
|
208
|
+
title: { type: "string", description: "New node title" },
|
|
209
|
+
description: { type: "string", description: "New node description" },
|
|
210
|
+
status: {
|
|
211
|
+
type: "string",
|
|
212
|
+
description: "New node status",
|
|
213
|
+
enum: ["not_started", "in_progress", "completed", "blocked"]
|
|
214
|
+
},
|
|
215
|
+
context: { type: "string", description: "New context" },
|
|
216
|
+
agent_instructions: { type: "string", description: "New agent instructions" },
|
|
217
|
+
acceptance_criteria: { type: "string", description: "New acceptance criteria" },
|
|
218
|
+
due_date: { type: "string", description: "New due date (ISO format)" },
|
|
219
|
+
metadata: { type: "object", description: "New metadata" }
|
|
220
|
+
},
|
|
221
|
+
required: ["plan_id", "node_id"]
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: "delete_node",
|
|
226
|
+
description: "Delete a node and all its children",
|
|
227
|
+
inputSchema: {
|
|
228
|
+
type: "object",
|
|
229
|
+
properties: {
|
|
230
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
231
|
+
node_id: { type: "string", description: "Node ID to delete" }
|
|
232
|
+
},
|
|
233
|
+
required: ["plan_id", "node_id"]
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "move_node",
|
|
238
|
+
description: "Move a node to a different parent or position",
|
|
239
|
+
inputSchema: {
|
|
240
|
+
type: "object",
|
|
241
|
+
properties: {
|
|
242
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
243
|
+
node_id: { type: "string", description: "Node ID to move" },
|
|
244
|
+
parent_id: { type: "string", description: "New parent node ID" },
|
|
245
|
+
order_index: { type: "integer", description: "New position index" }
|
|
246
|
+
},
|
|
247
|
+
required: ["plan_id", "node_id"]
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "get_node_context",
|
|
252
|
+
description: "Get comprehensive context for a node including children, logs, and artifacts",
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: "object",
|
|
255
|
+
properties: {
|
|
256
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
257
|
+
node_id: { type: "string", description: "Node ID" }
|
|
258
|
+
},
|
|
259
|
+
required: ["plan_id", "node_id"]
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "get_node_ancestry",
|
|
264
|
+
description: "Get the path from root to a specific node",
|
|
265
|
+
inputSchema: {
|
|
266
|
+
type: "object",
|
|
267
|
+
properties: {
|
|
268
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
269
|
+
node_id: { type: "string", description: "Node ID" }
|
|
270
|
+
},
|
|
271
|
+
required: ["plan_id", "node_id"]
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
// ===== LOGGING TOOLS (Replaces Comments) =====
|
|
276
|
+
{
|
|
277
|
+
name: "add_log",
|
|
278
|
+
description: "Add a log entry to a node (replaces comments)",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
283
|
+
node_id: { type: "string", description: "Node ID" },
|
|
284
|
+
content: { type: "string", description: "Log content" },
|
|
285
|
+
log_type: {
|
|
286
|
+
type: "string",
|
|
287
|
+
description: "Type of log entry",
|
|
288
|
+
enum: ["progress", "reasoning", "challenge", "decision", "comment"],
|
|
289
|
+
default: "comment"
|
|
290
|
+
},
|
|
291
|
+
tags: {
|
|
292
|
+
type: "array",
|
|
293
|
+
description: "Tags for categorizing the log entry",
|
|
294
|
+
items: { type: "string" }
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
required: ["plan_id", "node_id", "content"]
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "get_logs",
|
|
302
|
+
description: "Get log entries for a node",
|
|
303
|
+
inputSchema: {
|
|
304
|
+
type: "object",
|
|
305
|
+
properties: {
|
|
306
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
307
|
+
node_id: { type: "string", description: "Node ID" },
|
|
308
|
+
log_type: {
|
|
309
|
+
type: "string",
|
|
310
|
+
description: "Filter by log type",
|
|
311
|
+
enum: ["progress", "reasoning", "challenge", "decision", "comment"]
|
|
312
|
+
},
|
|
313
|
+
limit: {
|
|
314
|
+
type: "integer",
|
|
315
|
+
description: "Maximum number of logs to return",
|
|
316
|
+
default: 50
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
required: ["plan_id", "node_id"]
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
|
|
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
|
+
// ===== BATCH OPERATIONS =====
|
|
348
|
+
{
|
|
349
|
+
name: "batch_update_nodes",
|
|
350
|
+
description: "Update multiple nodes at once",
|
|
351
|
+
inputSchema: {
|
|
352
|
+
type: "object",
|
|
353
|
+
properties: {
|
|
354
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
355
|
+
updates: {
|
|
356
|
+
type: "array",
|
|
357
|
+
description: "List of node updates",
|
|
358
|
+
items: {
|
|
359
|
+
type: "object",
|
|
360
|
+
properties: {
|
|
361
|
+
node_id: { type: "string", description: "Node ID" },
|
|
362
|
+
status: {
|
|
363
|
+
type: "string",
|
|
364
|
+
enum: ["not_started", "in_progress", "completed", "blocked"]
|
|
365
|
+
},
|
|
366
|
+
title: { type: "string" },
|
|
367
|
+
description: { type: "string" }
|
|
368
|
+
},
|
|
369
|
+
required: ["node_id"]
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
required: ["plan_id", "updates"]
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: "batch_get_artifacts",
|
|
378
|
+
description: "Get multiple artifacts at once",
|
|
379
|
+
inputSchema: {
|
|
380
|
+
type: "object",
|
|
381
|
+
properties: {
|
|
382
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
383
|
+
artifact_requests: {
|
|
384
|
+
type: "array",
|
|
385
|
+
description: "List of artifact requests",
|
|
386
|
+
items: {
|
|
387
|
+
type: "object",
|
|
388
|
+
properties: {
|
|
389
|
+
node_id: { type: "string", description: "Node ID" },
|
|
390
|
+
artifact_id: { type: "string", description: "Artifact ID" }
|
|
391
|
+
},
|
|
392
|
+
required: ["node_id", "artifact_id"]
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
required: ["plan_id", "artifact_requests"]
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
|
|
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"]
|
|
415
|
+
}
|
|
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"]
|
|
426
|
+
}
|
|
427
|
+
}
|
|
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 {
|
|
442
|
+
// ===== UNIFIED SEARCH TOOL =====
|
|
443
|
+
if (name === "search") {
|
|
444
|
+
const { scope, scope_id, query, filters = {} } = args;
|
|
445
|
+
|
|
446
|
+
let results = [];
|
|
447
|
+
|
|
448
|
+
switch (scope) {
|
|
449
|
+
case "global":
|
|
450
|
+
// Global search across all plans
|
|
451
|
+
const searchWrapper = require('./tools/search-wrapper');
|
|
452
|
+
results = await searchWrapper.globalSearch(query);
|
|
453
|
+
break;
|
|
454
|
+
|
|
455
|
+
case "plans":
|
|
456
|
+
// Search only in plan titles/descriptions
|
|
457
|
+
const plans = await apiClient.plans.getPlans();
|
|
458
|
+
|
|
459
|
+
// Handle wildcard queries
|
|
460
|
+
if (query === '*' || query === '' || !query) {
|
|
461
|
+
// Return all plans (with optional status filter)
|
|
462
|
+
results = plans.filter(plan =>
|
|
463
|
+
!filters.status || plan.status === filters.status
|
|
464
|
+
);
|
|
465
|
+
} else {
|
|
466
|
+
// Normal search
|
|
467
|
+
const queryLower = query.toLowerCase();
|
|
468
|
+
results = plans.filter(plan => {
|
|
469
|
+
const titleMatch = plan.title.toLowerCase().includes(queryLower);
|
|
470
|
+
const descMatch = plan.description?.toLowerCase().includes(queryLower);
|
|
471
|
+
const statusMatch = !filters.status || plan.status === filters.status;
|
|
472
|
+
return (titleMatch || descMatch) && statusMatch;
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
break;
|
|
476
|
+
|
|
477
|
+
case "plan":
|
|
478
|
+
// Search within a specific plan
|
|
479
|
+
if (!scope_id) {
|
|
480
|
+
throw new Error("scope_id (plan_id) is required when scope is 'plan'");
|
|
481
|
+
}
|
|
482
|
+
const searchWrapperPlan = require('./tools/search-wrapper');
|
|
483
|
+
results = await searchWrapperPlan.searchPlan(scope_id, query);
|
|
484
|
+
break;
|
|
485
|
+
|
|
486
|
+
case "node":
|
|
487
|
+
// Search within a specific node's children
|
|
488
|
+
if (!scope_id) {
|
|
489
|
+
throw new Error("scope_id (node_id) is required when scope is 'node'");
|
|
490
|
+
}
|
|
491
|
+
// This would need a specific implementation
|
|
492
|
+
results = [];
|
|
493
|
+
break;
|
|
494
|
+
|
|
495
|
+
default:
|
|
496
|
+
// Default to global search
|
|
497
|
+
const searchWrapperDefault = require('./tools/search-wrapper');
|
|
498
|
+
results = await searchWrapperDefault.globalSearch(query);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Apply filters
|
|
502
|
+
if (filters.type) {
|
|
503
|
+
results = results.filter(item => item.type === filters.type);
|
|
504
|
+
}
|
|
505
|
+
if (filters.limit) {
|
|
506
|
+
results = results.slice(0, filters.limit);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return formatResponse({
|
|
510
|
+
query,
|
|
511
|
+
scope,
|
|
512
|
+
scope_id,
|
|
513
|
+
filters,
|
|
514
|
+
count: results.length,
|
|
515
|
+
results
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// ===== PLAN MANAGEMENT =====
|
|
520
|
+
if (name === "list_plans") {
|
|
521
|
+
const { status } = args;
|
|
522
|
+
const plans = await apiClient.plans.getPlans();
|
|
523
|
+
const filteredPlans = status ? plans.filter(p => p.status === status) : plans;
|
|
524
|
+
return formatResponse(filteredPlans);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (name === "create_plan") {
|
|
528
|
+
const result = await apiClient.plans.createPlan(args);
|
|
529
|
+
return formatResponse(result);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (name === "update_plan") {
|
|
533
|
+
const { plan_id, ...planData } = args;
|
|
534
|
+
const result = await apiClient.plans.updatePlan(plan_id, planData);
|
|
535
|
+
return formatResponse(result);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (name === "delete_plan") {
|
|
539
|
+
const { plan_id } = args;
|
|
540
|
+
await apiClient.plans.deletePlan(plan_id);
|
|
541
|
+
return formatResponse({
|
|
542
|
+
success: true,
|
|
543
|
+
message: `Plan ${plan_id} deleted successfully`
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ===== NODE MANAGEMENT =====
|
|
548
|
+
if (name === "create_node") {
|
|
549
|
+
const { plan_id, ...nodeData } = args;
|
|
550
|
+
const result = await apiClient.nodes.createNode(plan_id, nodeData);
|
|
551
|
+
return formatResponse(result);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (name === "update_node") {
|
|
555
|
+
const { plan_id, node_id, ...nodeData } = args;
|
|
556
|
+
const result = await apiClient.nodes.updateNode(plan_id, node_id, nodeData);
|
|
557
|
+
return formatResponse(result);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (name === "delete_node") {
|
|
561
|
+
const { plan_id, node_id } = args;
|
|
562
|
+
await apiClient.nodes.deleteNode(plan_id, node_id);
|
|
563
|
+
return formatResponse({
|
|
564
|
+
success: true,
|
|
565
|
+
message: `Node ${node_id} and its children deleted successfully`
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (name === "move_node") {
|
|
570
|
+
const { plan_id, node_id, parent_id, order_index } = args;
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
// Call the move endpoint - using POST as per API definition
|
|
574
|
+
const response = await apiClient.axiosInstance.post(
|
|
575
|
+
`/plans/${plan_id}/nodes/${node_id}/move`,
|
|
576
|
+
{
|
|
577
|
+
parent_id: parent_id || null,
|
|
578
|
+
order_index: order_index !== undefined ? order_index : null
|
|
579
|
+
}
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
return formatResponse(response.data);
|
|
583
|
+
} catch (error) {
|
|
584
|
+
// If endpoint still doesn't work, try updating the node directly
|
|
585
|
+
if (error.response && error.response.status === 404) {
|
|
586
|
+
console.error('Move endpoint not found, trying direct update');
|
|
587
|
+
// Fallback to updating the node's parent_id via regular update
|
|
588
|
+
const updateResponse = await apiClient.nodes.updateNode(plan_id, node_id, {
|
|
589
|
+
parent_id: parent_id || null,
|
|
590
|
+
order_index: order_index !== undefined ? order_index : null
|
|
591
|
+
});
|
|
592
|
+
return formatResponse(updateResponse);
|
|
593
|
+
}
|
|
594
|
+
throw error;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (name === "get_node_context") {
|
|
599
|
+
const { plan_id, node_id } = args;
|
|
600
|
+
|
|
601
|
+
// Get node with context
|
|
602
|
+
const response = await apiClient.axiosInstance.get(
|
|
603
|
+
`/plans/${plan_id}/nodes/${node_id}/context`
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
return formatResponse(response.data);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (name === "get_node_ancestry") {
|
|
610
|
+
const { plan_id, node_id } = args;
|
|
611
|
+
|
|
612
|
+
// Get node ancestry
|
|
613
|
+
const response = await apiClient.axiosInstance.get(
|
|
614
|
+
`/plans/${plan_id}/nodes/${node_id}/ancestry`
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
return formatResponse(response.data);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// ===== LOGGING =====
|
|
621
|
+
if (name === "add_log") {
|
|
622
|
+
const { plan_id, node_id, content, log_type = "comment", tags } = args;
|
|
623
|
+
|
|
624
|
+
const logData = {
|
|
625
|
+
content,
|
|
626
|
+
log_type,
|
|
627
|
+
tags
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
const result = await apiClient.logs.addLogEntry(plan_id, node_id, logData);
|
|
631
|
+
return formatResponse(result);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (name === "get_logs") {
|
|
635
|
+
const { plan_id, node_id, log_type, limit = 50 } = args;
|
|
636
|
+
|
|
637
|
+
let logs = await apiClient.logs.getLogs(plan_id, node_id);
|
|
638
|
+
|
|
639
|
+
// Apply filters
|
|
640
|
+
if (log_type) {
|
|
641
|
+
logs = logs.filter(log => log.log_type === log_type);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Apply limit
|
|
645
|
+
logs = logs.slice(0, limit);
|
|
646
|
+
|
|
647
|
+
return formatResponse(logs);
|
|
648
|
+
}
|
|
649
|
+
|
|
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
|
+
// ===== BATCH OPERATIONS =====
|
|
693
|
+
if (name === "batch_update_nodes") {
|
|
694
|
+
const { plan_id, updates } = args;
|
|
695
|
+
|
|
696
|
+
const results = [];
|
|
697
|
+
const errors = [];
|
|
698
|
+
|
|
699
|
+
for (const update of updates) {
|
|
700
|
+
const { node_id, ...updateData } = update;
|
|
701
|
+
try {
|
|
702
|
+
const result = await apiClient.nodes.updateNode(plan_id, node_id, updateData);
|
|
703
|
+
results.push({ node_id, success: true, data: result });
|
|
704
|
+
} catch (error) {
|
|
705
|
+
errors.push({ node_id, success: false, error: error.message });
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
return formatResponse({
|
|
710
|
+
total: updates.length,
|
|
711
|
+
successful: results.length,
|
|
712
|
+
failed: errors.length,
|
|
713
|
+
results,
|
|
714
|
+
errors
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
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
|
+
// ===== PLAN STRUCTURE & SUMMARY =====
|
|
755
|
+
if (name === "get_plan_structure") {
|
|
756
|
+
const { plan_id, include_details = false } = args;
|
|
757
|
+
|
|
758
|
+
const plan = await apiClient.plans.getPlan(plan_id);
|
|
759
|
+
const nodes = await apiClient.nodes.getNodes(plan_id);
|
|
760
|
+
|
|
761
|
+
// The API already returns a tree structure, not a flat list
|
|
762
|
+
// If it's already hierarchical, use it directly
|
|
763
|
+
let structure;
|
|
764
|
+
if (Array.isArray(nodes) && nodes.length > 0 && nodes[0].children !== undefined) {
|
|
765
|
+
// Already hierarchical - use directly
|
|
766
|
+
structure = nodes;
|
|
767
|
+
} else {
|
|
768
|
+
// Flat list - build hierarchy
|
|
769
|
+
structure = buildNodeHierarchy(nodes, include_details);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
return formatResponse({
|
|
773
|
+
plan: {
|
|
774
|
+
id: plan.id,
|
|
775
|
+
title: plan.title,
|
|
776
|
+
status: plan.status,
|
|
777
|
+
description: plan.description
|
|
778
|
+
},
|
|
779
|
+
structure
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (name === "get_plan_summary") {
|
|
784
|
+
const { plan_id } = args;
|
|
785
|
+
|
|
786
|
+
const plan = await apiClient.plans.getPlan(plan_id);
|
|
787
|
+
const nodes = await apiClient.nodes.getNodes(plan_id);
|
|
788
|
+
|
|
789
|
+
// Calculate statistics
|
|
790
|
+
const stats = calculatePlanStatistics(nodes);
|
|
791
|
+
|
|
792
|
+
return formatResponse({
|
|
793
|
+
plan: {
|
|
794
|
+
id: plan.id,
|
|
795
|
+
title: plan.title,
|
|
796
|
+
status: plan.status,
|
|
797
|
+
description: plan.description,
|
|
798
|
+
created_at: plan.created_at,
|
|
799
|
+
updated_at: plan.updated_at
|
|
800
|
+
},
|
|
801
|
+
statistics: stats,
|
|
802
|
+
progress_percentage: stats.total > 0
|
|
803
|
+
? ((stats.status_counts.completed / stats.total) * 100).toFixed(1)
|
|
804
|
+
: 0
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Tool not found
|
|
809
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
810
|
+
} catch (error) {
|
|
811
|
+
if (process.env.NODE_ENV === 'development') {
|
|
812
|
+
console.error(`Error calling tool ${name}:`, error);
|
|
813
|
+
}
|
|
814
|
+
return {
|
|
815
|
+
isError: true,
|
|
816
|
+
content: [
|
|
817
|
+
{
|
|
818
|
+
type: "text",
|
|
819
|
+
text: `Error: ${error.message}`
|
|
820
|
+
}
|
|
821
|
+
]
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
if (process.env.NODE_ENV === 'development') {
|
|
827
|
+
console.error('Tools setup complete');
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Build hierarchical node structure
|
|
833
|
+
*/
|
|
834
|
+
function buildNodeHierarchy(nodes, includeDetails = false) {
|
|
835
|
+
if (!nodes || nodes.length === 0) {
|
|
836
|
+
return [];
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Debug logging to understand the structure
|
|
840
|
+
if (process.env.NODE_ENV === 'development') {
|
|
841
|
+
console.error('Building hierarchy for nodes:', nodes.length);
|
|
842
|
+
if (nodes[0]) {
|
|
843
|
+
console.error('Sample node:', {
|
|
844
|
+
id: nodes[0].id,
|
|
845
|
+
parent_id: nodes[0].parent_id,
|
|
846
|
+
node_type: nodes[0].node_type
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const nodeMap = new Map();
|
|
852
|
+
const rootNodes = [];
|
|
853
|
+
|
|
854
|
+
// First pass: create all nodes in the map
|
|
855
|
+
nodes.forEach(node => {
|
|
856
|
+
const nodeData = includeDetails ? { ...node } : {
|
|
857
|
+
id: node.id,
|
|
858
|
+
title: node.title,
|
|
859
|
+
node_type: node.node_type,
|
|
860
|
+
status: node.status,
|
|
861
|
+
parent_id: node.parent_id,
|
|
862
|
+
order_index: node.order_index
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
// Initialize with empty children array
|
|
866
|
+
nodeMap.set(node.id, {
|
|
867
|
+
...nodeData,
|
|
868
|
+
children: []
|
|
869
|
+
});
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// Second pass: build parent-child relationships
|
|
873
|
+
nodes.forEach(node => {
|
|
874
|
+
const currentNode = nodeMap.get(node.id);
|
|
875
|
+
|
|
876
|
+
if (node.parent_id) {
|
|
877
|
+
const parent = nodeMap.get(node.parent_id);
|
|
878
|
+
if (parent) {
|
|
879
|
+
// Add as child to parent
|
|
880
|
+
parent.children.push(currentNode);
|
|
881
|
+
} else {
|
|
882
|
+
// Parent not found, treat as root
|
|
883
|
+
if (process.env.NODE_ENV === 'development') {
|
|
884
|
+
console.error(`Parent ${node.parent_id} not found for node ${node.id}`);
|
|
885
|
+
}
|
|
886
|
+
rootNodes.push(currentNode);
|
|
887
|
+
}
|
|
888
|
+
} else {
|
|
889
|
+
// No parent_id means it's a root node
|
|
890
|
+
rootNodes.push(currentNode);
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
// Special case: if we have a single root node of type 'root', return its children
|
|
895
|
+
if (rootNodes.length === 1 && rootNodes[0].node_type === 'root') {
|
|
896
|
+
// Return the root node itself with its children
|
|
897
|
+
const rootNode = rootNodes[0];
|
|
898
|
+
|
|
899
|
+
// Sort children by order_index
|
|
900
|
+
const sortNodes = (nodeArray) => {
|
|
901
|
+
nodeArray.sort((a, b) => {
|
|
902
|
+
const orderA = a.order_index ?? 999;
|
|
903
|
+
const orderB = b.order_index ?? 999;
|
|
904
|
+
return orderA - orderB;
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
nodeArray.forEach(node => {
|
|
908
|
+
if (node.children && node.children.length > 0) {
|
|
909
|
+
sortNodes(node.children);
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
sortNodes(rootNode.children);
|
|
915
|
+
return [rootNode]; // Return root with its properly sorted children
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Sort all root nodes and their children
|
|
919
|
+
const sortNodes = (nodeArray) => {
|
|
920
|
+
nodeArray.sort((a, b) => {
|
|
921
|
+
const orderA = a.order_index ?? 999;
|
|
922
|
+
const orderB = b.order_index ?? 999;
|
|
923
|
+
return orderA - orderB;
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
nodeArray.forEach(node => {
|
|
927
|
+
if (node.children && node.children.length > 0) {
|
|
928
|
+
sortNodes(node.children);
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
sortNodes(rootNodes);
|
|
934
|
+
|
|
935
|
+
return rootNodes;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Calculate plan statistics
|
|
940
|
+
*/
|
|
941
|
+
function calculatePlanStatistics(nodes) {
|
|
942
|
+
const stats = {
|
|
943
|
+
total: 0,
|
|
944
|
+
type_counts: {
|
|
945
|
+
root: 0,
|
|
946
|
+
phase: 0,
|
|
947
|
+
task: 0,
|
|
948
|
+
milestone: 0
|
|
949
|
+
},
|
|
950
|
+
status_counts: {
|
|
951
|
+
not_started: 0,
|
|
952
|
+
in_progress: 0,
|
|
953
|
+
completed: 0,
|
|
954
|
+
blocked: 0
|
|
955
|
+
},
|
|
956
|
+
in_progress_nodes: [],
|
|
957
|
+
blocked_nodes: []
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
const processNode = (node) => {
|
|
961
|
+
stats.total++;
|
|
962
|
+
|
|
963
|
+
if (node.node_type && stats.type_counts[node.node_type] !== undefined) {
|
|
964
|
+
stats.type_counts[node.node_type]++;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (node.status && stats.status_counts[node.status] !== undefined) {
|
|
968
|
+
stats.status_counts[node.status]++;
|
|
969
|
+
|
|
970
|
+
if (node.status === 'in_progress') {
|
|
971
|
+
stats.in_progress_nodes.push({
|
|
972
|
+
id: node.id,
|
|
973
|
+
title: node.title,
|
|
974
|
+
type: node.node_type
|
|
975
|
+
});
|
|
976
|
+
} else if (node.status === 'blocked') {
|
|
977
|
+
stats.blocked_nodes.push({
|
|
978
|
+
id: node.id,
|
|
979
|
+
title: node.title,
|
|
980
|
+
type: node.node_type
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
if (node.children && node.children.length > 0) {
|
|
986
|
+
node.children.forEach(processNode);
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
nodes.forEach(processNode);
|
|
991
|
+
|
|
992
|
+
return stats;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
module.exports = { setupTools };
|