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
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for interacting with the Planning System API
|
|
3
|
+
*/
|
|
4
|
+
const axios = require('axios');
|
|
5
|
+
require('dotenv').config();
|
|
6
|
+
|
|
7
|
+
// Get token from environment
|
|
8
|
+
const userApiToken = process.env.USER_API_TOKEN || process.env.API_TOKEN; // Support both new and old env var names
|
|
9
|
+
|
|
10
|
+
// Determine proper authentication scheme
|
|
11
|
+
// If token looks like a JWT (has two dots), use Bearer scheme, otherwise use ApiKey
|
|
12
|
+
const getAuthScheme = (token) => {
|
|
13
|
+
if (!token) return null;
|
|
14
|
+
// Simple check if it's a JWT (contains two dots for header.payload.signature)
|
|
15
|
+
return token.split('.').length === 3 ? 'Bearer' : 'ApiKey';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const authScheme = getAuthScheme(userApiToken);
|
|
19
|
+
|
|
20
|
+
// Create API client instance
|
|
21
|
+
const apiClient = axios.create({
|
|
22
|
+
baseURL: process.env.API_URL || 'http://localhost:3000',
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'Authorization': userApiToken ? `${authScheme} ${userApiToken}` : undefined
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Log API requests in debug mode
|
|
30
|
+
apiClient.interceptors.request.use(request => {
|
|
31
|
+
console.error(`API Request: ${request.method.toUpperCase()} ${request.url}`);
|
|
32
|
+
return request;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Log API responses in debug mode
|
|
36
|
+
apiClient.interceptors.response.use(
|
|
37
|
+
response => {
|
|
38
|
+
console.error(`API Response: ${response.status} ${response.statusText}`);
|
|
39
|
+
return response;
|
|
40
|
+
},
|
|
41
|
+
error => {
|
|
42
|
+
if (error.response && error.response.status === 401) {
|
|
43
|
+
console.error('API Error: Authentication failed (401). Please check that your USER_API_TOKEN is correct, valid, and not revoked.');
|
|
44
|
+
console.error('If you are still using the old API_TOKEN, please generate a USER_API_TOKEN from the agent-planner UI.');
|
|
45
|
+
} else {
|
|
46
|
+
console.error('API Error:', error.response ? error.response.data : error.message);
|
|
47
|
+
}
|
|
48
|
+
return Promise.reject(error);
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Plan-related API functions
|
|
54
|
+
*/
|
|
55
|
+
const plans = {
|
|
56
|
+
/**
|
|
57
|
+
* Get a list of plans accessible to the current user
|
|
58
|
+
* @returns {Promise<Array>} - List of plans
|
|
59
|
+
*/
|
|
60
|
+
getPlans: async () => {
|
|
61
|
+
const response = await apiClient.get('/plans');
|
|
62
|
+
return response.data;
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get a specific plan by ID
|
|
67
|
+
* @param {string} planId - Plan ID
|
|
68
|
+
* @returns {Promise<Object>} - Plan details
|
|
69
|
+
*/
|
|
70
|
+
getPlan: async (planId) => {
|
|
71
|
+
const response = await apiClient.get(`/plans/${planId}`);
|
|
72
|
+
return response.data;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create a new plan
|
|
77
|
+
* @param {Object} planData - Plan data (title, description, status)
|
|
78
|
+
* @returns {Promise<Object>} - Created plan
|
|
79
|
+
*/
|
|
80
|
+
createPlan: async (planData) => {
|
|
81
|
+
const response = await apiClient.post('/plans', planData);
|
|
82
|
+
return response.data;
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Update a plan
|
|
87
|
+
* @param {string} planId - Plan ID
|
|
88
|
+
* @param {Object} planData - Updated plan data
|
|
89
|
+
* @returns {Promise<Object>} - Updated plan
|
|
90
|
+
*/
|
|
91
|
+
updatePlan: async (planId, planData) => {
|
|
92
|
+
const response = await apiClient.put(`/plans/${planId}`, planData);
|
|
93
|
+
return response.data;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Delete a plan
|
|
98
|
+
* @param {string} planId - Plan ID
|
|
99
|
+
* @returns {Promise<void>}
|
|
100
|
+
*/
|
|
101
|
+
deletePlan: async (planId) => {
|
|
102
|
+
await apiClient.delete(`/plans/${planId}`);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Node-related API functions
|
|
108
|
+
*/
|
|
109
|
+
const nodes = {
|
|
110
|
+
/**
|
|
111
|
+
* Get nodes for a plan
|
|
112
|
+
* @param {string} planId - Plan ID
|
|
113
|
+
* @returns {Promise<Array>} - List of nodes
|
|
114
|
+
*/
|
|
115
|
+
getNodes: async (planId) => {
|
|
116
|
+
const response = await apiClient.get(`/plans/${planId}/nodes`);
|
|
117
|
+
return response.data;
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get a specific node
|
|
122
|
+
* @param {string} planId - Plan ID
|
|
123
|
+
* @param {string} nodeId - Node ID
|
|
124
|
+
* @returns {Promise<Object>} - Node details
|
|
125
|
+
*/
|
|
126
|
+
getNode: async (planId, nodeId) => {
|
|
127
|
+
const response = await apiClient.get(`/plans/${planId}/nodes/${nodeId}`);
|
|
128
|
+
return response.data;
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Create a new node
|
|
133
|
+
* @param {string} planId - Plan ID
|
|
134
|
+
* @param {Object} nodeData - Node data
|
|
135
|
+
* @returns {Promise<Object>} - Created node
|
|
136
|
+
*/
|
|
137
|
+
createNode: async (planId, nodeData) => {
|
|
138
|
+
const response = await apiClient.post(`/plans/${planId}/nodes`, nodeData);
|
|
139
|
+
return response.data;
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Update a node
|
|
144
|
+
* @param {string} planId - Plan ID
|
|
145
|
+
* @param {string} nodeId - Node ID
|
|
146
|
+
* @param {Object} nodeData - Updated node data
|
|
147
|
+
* @returns {Promise<Object>} - Updated node
|
|
148
|
+
*/
|
|
149
|
+
updateNode: async (planId, nodeId, nodeData) => {
|
|
150
|
+
const response = await apiClient.put(`/plans/${planId}/nodes/${nodeId}`, nodeData);
|
|
151
|
+
return response.data;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Update node status
|
|
156
|
+
* @param {string} planId - Plan ID
|
|
157
|
+
* @param {string} nodeId - Node ID
|
|
158
|
+
* @param {string} status - New status
|
|
159
|
+
* @returns {Promise<Object>} - Updated node
|
|
160
|
+
*/
|
|
161
|
+
updateNodeStatus: async (planId, nodeId, status) => {
|
|
162
|
+
const response = await apiClient.put(`/plans/${planId}/nodes/${nodeId}/status`, { status });
|
|
163
|
+
return response.data;
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Delete a node
|
|
168
|
+
* @param {string} planId - Plan ID
|
|
169
|
+
* @param {string} nodeId - Node ID
|
|
170
|
+
* @returns {Promise<void>}
|
|
171
|
+
*/
|
|
172
|
+
deleteNode: async (planId, nodeId) => {
|
|
173
|
+
await apiClient.delete(`/plans/${planId}/nodes/${nodeId}`);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Comment-related API functions
|
|
179
|
+
*/
|
|
180
|
+
const comments = {
|
|
181
|
+
/**
|
|
182
|
+
* Get comments for a node
|
|
183
|
+
* @param {string} planId - Plan ID
|
|
184
|
+
* @param {string} nodeId - Node ID
|
|
185
|
+
* @returns {Promise<Array>} - List of comments
|
|
186
|
+
*/
|
|
187
|
+
getComments: async (planId, nodeId) => {
|
|
188
|
+
const response = await apiClient.get(`/plans/${planId}/nodes/${nodeId}/comments`);
|
|
189
|
+
return response.data;
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Add a comment to a node
|
|
194
|
+
* @param {string} planId - Plan ID
|
|
195
|
+
* @param {string} nodeId - Node ID
|
|
196
|
+
* @param {Object} commentData - Comment data
|
|
197
|
+
* @returns {Promise<Object>} - Created comment
|
|
198
|
+
*/
|
|
199
|
+
addComment: async (planId, nodeId, commentData) => {
|
|
200
|
+
const response = await apiClient.post(`/plans/${planId}/nodes/${nodeId}/comments`, commentData);
|
|
201
|
+
return response.data;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Log-related API functions
|
|
207
|
+
*/
|
|
208
|
+
const logs = {
|
|
209
|
+
/**
|
|
210
|
+
* Get logs for a node
|
|
211
|
+
* @param {string} planId - Plan ID
|
|
212
|
+
* @param {string} nodeId - Node ID
|
|
213
|
+
* @returns {Promise<Array>} - List of logs
|
|
214
|
+
*/
|
|
215
|
+
getLogs: async (planId, nodeId) => {
|
|
216
|
+
const response = await apiClient.get(`/plans/${planId}/nodes/${nodeId}/logs`);
|
|
217
|
+
return response.data;
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Add a log entry to a node
|
|
222
|
+
* @param {string} planId - Plan ID
|
|
223
|
+
* @param {string} nodeId - Node ID
|
|
224
|
+
* @param {Object} logData - Log data
|
|
225
|
+
* @returns {Promise<Object>} - Created log entry
|
|
226
|
+
*/
|
|
227
|
+
addLogEntry: async (planId, nodeId, logData) => {
|
|
228
|
+
const response = await apiClient.post(`/plans/${planId}/nodes/${nodeId}/log`, logData);
|
|
229
|
+
return response.data;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Artifact-related API functions
|
|
235
|
+
*/
|
|
236
|
+
const artifacts = {
|
|
237
|
+
/**
|
|
238
|
+
* Get artifacts for a node
|
|
239
|
+
* @param {string} planId - Plan ID
|
|
240
|
+
* @param {string} nodeId - Node ID
|
|
241
|
+
* @returns {Promise<Array>} - List of artifacts
|
|
242
|
+
*/
|
|
243
|
+
getArtifacts: async (planId, nodeId) => {
|
|
244
|
+
const response = await apiClient.get(`/plans/${planId}/nodes/${nodeId}/artifacts`);
|
|
245
|
+
return response.data;
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get a specific artifact by ID
|
|
250
|
+
* @param {string} planId - Plan ID
|
|
251
|
+
* @param {string} nodeId - Node ID
|
|
252
|
+
* @param {string} artifactId - Artifact ID
|
|
253
|
+
* @returns {Promise<Object>} - Artifact details
|
|
254
|
+
*/
|
|
255
|
+
getArtifact: async (planId, nodeId, artifactId) => {
|
|
256
|
+
const response = await apiClient.get(`/plans/${planId}/nodes/${nodeId}/artifacts/${artifactId}`);
|
|
257
|
+
return response.data;
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get the content of an artifact
|
|
262
|
+
* @param {string} planId - Plan ID
|
|
263
|
+
* @param {string} nodeId - Node ID
|
|
264
|
+
* @param {string} artifactId - Artifact ID
|
|
265
|
+
* @returns {Promise<string>} - Artifact content
|
|
266
|
+
*/
|
|
267
|
+
getArtifactContent: async (planId, nodeId, artifactId) => {
|
|
268
|
+
try {
|
|
269
|
+
// First, get artifact details to check the URL
|
|
270
|
+
const artifact = await artifacts.getArtifact(planId, nodeId, artifactId);
|
|
271
|
+
|
|
272
|
+
// If the artifact has a URL, fetch the content
|
|
273
|
+
if (artifact.url) {
|
|
274
|
+
try {
|
|
275
|
+
// For local file paths, use fs instead of HTTP request
|
|
276
|
+
if (artifact.url.startsWith('/') && !artifact.url.startsWith('/api/')) {
|
|
277
|
+
const fs = require('fs').promises;
|
|
278
|
+
try {
|
|
279
|
+
// Read the file directly from the filesystem
|
|
280
|
+
const content = await fs.readFile(artifact.url, 'utf8');
|
|
281
|
+
return content;
|
|
282
|
+
} catch (fsError) {
|
|
283
|
+
console.error('Error reading artifact file:', fsError);
|
|
284
|
+
throw new Error(`Cannot read file at ${artifact.url}: ${fsError.message}`);
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
// For internal URLs (API routes), append to base URL
|
|
288
|
+
const contentUrl = artifact.url.startsWith('/api/')
|
|
289
|
+
? `${apiClient.defaults.baseURL}${artifact.url}`
|
|
290
|
+
: artifact.url;
|
|
291
|
+
|
|
292
|
+
const contentResponse = await axios.get(contentUrl, {
|
|
293
|
+
headers: {
|
|
294
|
+
'Authorization': apiClient.defaults.headers['Authorization'],
|
|
295
|
+
'Accept': artifact.content_type || 'text/plain'
|
|
296
|
+
},
|
|
297
|
+
responseType: 'text'
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return contentResponse.data;
|
|
301
|
+
}
|
|
302
|
+
} catch (fetchError) {
|
|
303
|
+
console.error('Error fetching artifact content:', fetchError);
|
|
304
|
+
throw new Error(`Failed to fetch artifact content: ${fetchError.message}`);
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
throw new Error('Artifact does not have a content URL');
|
|
308
|
+
}
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error('Error fetching artifact content:', error);
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Add an artifact to a node
|
|
317
|
+
* @param {string} planId - Plan ID
|
|
318
|
+
* @param {string} nodeId - Node ID
|
|
319
|
+
* @param {Object} artifactData - Artifact data
|
|
320
|
+
* @returns {Promise<Object>} - Created artifact
|
|
321
|
+
*/
|
|
322
|
+
addArtifact: async (planId, nodeId, artifactData) => {
|
|
323
|
+
const response = await apiClient.post(`/plans/${planId}/nodes/${nodeId}/artifacts`, artifactData);
|
|
324
|
+
return response.data;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Activity-related API functions
|
|
330
|
+
*/
|
|
331
|
+
const activity = {
|
|
332
|
+
/**
|
|
333
|
+
* Get activity feed for a plan
|
|
334
|
+
* @param {string} planId - Plan ID
|
|
335
|
+
* @returns {Promise<Array>} - Activity feed
|
|
336
|
+
*/
|
|
337
|
+
getPlanActivity: async (planId) => {
|
|
338
|
+
const response = await apiClient.get(`/activity/plan/${planId}`);
|
|
339
|
+
return response.data;
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Get global activity feed
|
|
344
|
+
* @returns {Promise<Array>} - Activity feed
|
|
345
|
+
*/
|
|
346
|
+
getGlobalActivity: async () => {
|
|
347
|
+
const response = await apiClient.get('/activity');
|
|
348
|
+
return response.data;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Search-related API functions
|
|
354
|
+
*/
|
|
355
|
+
const search = {
|
|
356
|
+
/**
|
|
357
|
+
* Search within a plan
|
|
358
|
+
* @param {string} planId - Plan ID
|
|
359
|
+
* @param {string} query - Search query
|
|
360
|
+
* @returns {Promise<Object>} - Search results
|
|
361
|
+
*/
|
|
362
|
+
searchPlan: async (planId, query) => {
|
|
363
|
+
if (process.env.NODE_ENV === 'development') {
|
|
364
|
+
console.log(`Searching plan ${planId} for "${query}"`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
// Try the documented endpoint first
|
|
369
|
+
const response = await apiClient.get(`/search/plan/${planId}`, {
|
|
370
|
+
params: { query: query } // API expects 'query' parameter
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
if (process.env.NODE_ENV === 'development') {
|
|
374
|
+
console.log('Search response status:', response.status);
|
|
375
|
+
console.log('Search response type:', typeof response.data);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return response.data;
|
|
379
|
+
} catch (error) {
|
|
380
|
+
// Try alternative endpoint format
|
|
381
|
+
if (error.response && error.response.status === 404) {
|
|
382
|
+
try {
|
|
383
|
+
const altResponse = await apiClient.get(`/plans/${planId}/search`, {
|
|
384
|
+
params: { query: query }
|
|
385
|
+
});
|
|
386
|
+
return altResponse.data;
|
|
387
|
+
} catch (altError) {
|
|
388
|
+
// Fallback to client-side search
|
|
389
|
+
console.error('Search endpoints not found, falling back to client-side search');
|
|
390
|
+
|
|
391
|
+
// Get all nodes and search client-side
|
|
392
|
+
try {
|
|
393
|
+
const nodes = await apiClient.get(`/plans/${planId}/nodes`);
|
|
394
|
+
const results = [];
|
|
395
|
+
|
|
396
|
+
const searchLower = query.toLowerCase();
|
|
397
|
+
const searchNodes = (nodeList) => {
|
|
398
|
+
nodeList.forEach(node => {
|
|
399
|
+
if (node.title?.toLowerCase().includes(searchLower) ||
|
|
400
|
+
node.description?.toLowerCase().includes(searchLower) ||
|
|
401
|
+
node.context?.toLowerCase().includes(searchLower)) {
|
|
402
|
+
results.push({
|
|
403
|
+
id: node.id,
|
|
404
|
+
type: 'node',
|
|
405
|
+
title: node.title,
|
|
406
|
+
content: node.description || node.context || '',
|
|
407
|
+
created_at: node.created_at,
|
|
408
|
+
user_id: node.created_by
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (node.children && node.children.length > 0) {
|
|
412
|
+
searchNodes(node.children);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
searchNodes(nodes.data);
|
|
418
|
+
|
|
419
|
+
return {
|
|
420
|
+
query,
|
|
421
|
+
results,
|
|
422
|
+
count: results.length
|
|
423
|
+
};
|
|
424
|
+
} catch (fallbackError) {
|
|
425
|
+
console.error('Fallback search failed:', fallbackError.message);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
console.error('Error searching plan:', error.message);
|
|
431
|
+
if (error.response) {
|
|
432
|
+
console.error('Response status:', error.response.status);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Return empty results on error
|
|
436
|
+
return { results: [], count: 0, query };
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Global search across all plans
|
|
442
|
+
* @param {string} query - Search query
|
|
443
|
+
* @returns {Promise<Object>} - Search results
|
|
444
|
+
*/
|
|
445
|
+
globalSearch: async (query) => {
|
|
446
|
+
try {
|
|
447
|
+
const response = await apiClient.get('/search', {
|
|
448
|
+
params: { query: query } // API expects 'query' parameter
|
|
449
|
+
});
|
|
450
|
+
return response.data;
|
|
451
|
+
} catch (error) {
|
|
452
|
+
console.error('Global search error:', error.message);
|
|
453
|
+
|
|
454
|
+
// Fallback: search through all accessible plans
|
|
455
|
+
if (error.response && (error.response.status === 404 || error.response.status === 500)) {
|
|
456
|
+
try {
|
|
457
|
+
const plansResponse = await apiClient.get('/plans');
|
|
458
|
+
const plans = plansResponse.data;
|
|
459
|
+
const results = [];
|
|
460
|
+
|
|
461
|
+
const searchLower = query.toLowerCase();
|
|
462
|
+
|
|
463
|
+
// Search in plans
|
|
464
|
+
plans.forEach(plan => {
|
|
465
|
+
if (plan.title?.toLowerCase().includes(searchLower) ||
|
|
466
|
+
plan.description?.toLowerCase().includes(searchLower)) {
|
|
467
|
+
results.push({
|
|
468
|
+
id: plan.id,
|
|
469
|
+
type: 'plan',
|
|
470
|
+
title: plan.title,
|
|
471
|
+
content: plan.description || '',
|
|
472
|
+
created_at: plan.created_at
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
query,
|
|
479
|
+
results,
|
|
480
|
+
count: results.length
|
|
481
|
+
};
|
|
482
|
+
} catch (fallbackError) {
|
|
483
|
+
console.error('Fallback global search failed:', fallbackError.message);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Return empty results on error
|
|
488
|
+
return { results: [], count: 0, query };
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* API Token functions
|
|
495
|
+
*/
|
|
496
|
+
const tokens = {
|
|
497
|
+
/**
|
|
498
|
+
* Get all API tokens
|
|
499
|
+
* @returns {Promise<Array>} - List of API tokens
|
|
500
|
+
*/
|
|
501
|
+
getTokens: async () => {
|
|
502
|
+
const response = await apiClient.get('/tokens');
|
|
503
|
+
return response.data;
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Create a new API token
|
|
508
|
+
* @param {Object} tokenData - Token data
|
|
509
|
+
* @returns {Promise<Object>} - Created token
|
|
510
|
+
*/
|
|
511
|
+
createToken: async (tokenData) => {
|
|
512
|
+
const response = await apiClient.post('/tokens', tokenData);
|
|
513
|
+
return response.data;
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Revoke an API token
|
|
518
|
+
* @param {string} tokenId - Token ID
|
|
519
|
+
* @returns {Promise<void>}
|
|
520
|
+
*/
|
|
521
|
+
revokeToken: async (tokenId) => {
|
|
522
|
+
await apiClient.delete(`/tokens/${tokenId}`);
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// Export API client functions
|
|
527
|
+
// Export the axios instance for direct use
|
|
528
|
+
const axiosInstance = apiClient;
|
|
529
|
+
|
|
530
|
+
module.exports = {
|
|
531
|
+
plans,
|
|
532
|
+
nodes,
|
|
533
|
+
comments,
|
|
534
|
+
logs,
|
|
535
|
+
artifacts,
|
|
536
|
+
activity,
|
|
537
|
+
search,
|
|
538
|
+
tokens,
|
|
539
|
+
axiosInstance // Export for direct API calls
|
|
540
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
3
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
4
|
+
const { setupTools } = require('./tools');
|
|
5
|
+
require('dotenv').config();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Initialize the Planning System MCP Server
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Simplified architecture with tools-only interface
|
|
12
|
+
* - Full CRUD operations on all entities
|
|
13
|
+
* - Unified search across all scopes
|
|
14
|
+
* - Batch operations for efficiency
|
|
15
|
+
* - Structured JSON responses
|
|
16
|
+
* - Comprehensive logging system
|
|
17
|
+
*/
|
|
18
|
+
async function main() {
|
|
19
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
20
|
+
|
|
21
|
+
if (isDev) {
|
|
22
|
+
console.error('Initializing Planning System MCP Server...');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Log environment settings
|
|
27
|
+
console.error(`API URL: ${process.env.API_URL || 'http://localhost:3000'}`);
|
|
28
|
+
|
|
29
|
+
// Check for token
|
|
30
|
+
const userApiToken = process.env.USER_API_TOKEN || process.env.API_TOKEN;
|
|
31
|
+
console.error(`User API Token: ${userApiToken ? '***' + userApiToken.slice(-4) : 'NOT SET'}`);
|
|
32
|
+
console.error(`MCP Server Name: ${process.env.MCP_SERVER_NAME || 'planning-system-mcp'}`);
|
|
33
|
+
console.error(`MCP Server Version: ${process.env.MCP_SERVER_VERSION || '0.2.0'}`);
|
|
34
|
+
|
|
35
|
+
// Validate required environment variables
|
|
36
|
+
if (!userApiToken) {
|
|
37
|
+
throw new Error('USER_API_TOKEN environment variable is required. Please generate one from the Agent Planner UI and set it in .env file.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Create MCP server instance
|
|
41
|
+
const server = new Server({
|
|
42
|
+
name: process.env.MCP_SERVER_NAME || "planning-system-mcp",
|
|
43
|
+
version: process.env.MCP_SERVER_VERSION || "0.2.0"
|
|
44
|
+
}, {
|
|
45
|
+
capabilities: {
|
|
46
|
+
tools: {}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.error('MCP Server created');
|
|
51
|
+
|
|
52
|
+
// Setup tools
|
|
53
|
+
setupTools(server);
|
|
54
|
+
|
|
55
|
+
// Connect transport
|
|
56
|
+
const transport = new StdioServerTransport();
|
|
57
|
+
await server.connect(transport);
|
|
58
|
+
|
|
59
|
+
console.error('MCP Server running on stdio transport');
|
|
60
|
+
console.error('Ready to accept connections from agents');
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Failed to initialize MCP server:', error);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Run the server
|
|
68
|
+
main();
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search integration for the MCP system
|
|
3
|
+
*
|
|
4
|
+
* This module integrates the search wrapper with the MCP system,
|
|
5
|
+
* providing functions that can be called from other parts of the application.
|
|
6
|
+
*/
|
|
7
|
+
const { searchPlan, globalSearch } = require('../tools/search-wrapper');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Perform a search within a plan and process the results
|
|
11
|
+
*
|
|
12
|
+
* @param {string} planId - ID of the plan to search
|
|
13
|
+
* @param {string} query - Search query
|
|
14
|
+
* @param {Function} [processResult] - Optional callback for each result
|
|
15
|
+
* @returns {Promise<Array>} - Array of processed search results
|
|
16
|
+
*/
|
|
17
|
+
async function searchPlanAndProcess(planId, query, processResult = null) {
|
|
18
|
+
try {
|
|
19
|
+
console.log(`Searching plan ${planId} for: "${query}"`);
|
|
20
|
+
|
|
21
|
+
// Get results using the wrapper
|
|
22
|
+
const results = await searchPlan(planId, query);
|
|
23
|
+
|
|
24
|
+
// If a process function is provided, map over the results
|
|
25
|
+
if (typeof processResult === 'function') {
|
|
26
|
+
return results.map(processResult);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return results;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Error in searchPlanAndProcess:', error.message);
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Search for content within a plan and extract relevant information
|
|
38
|
+
*
|
|
39
|
+
* @param {string} planId - ID of the plan to search
|
|
40
|
+
* @param {string} query - Search query
|
|
41
|
+
* @returns {Promise<Object>} - Organized search results
|
|
42
|
+
*/
|
|
43
|
+
async function findContentInPlan(planId, query) {
|
|
44
|
+
try {
|
|
45
|
+
const results = await searchPlan(planId, query);
|
|
46
|
+
|
|
47
|
+
// Organize results by type
|
|
48
|
+
const organizedResults = {
|
|
49
|
+
nodes: results.filter(r => r.type === 'node'),
|
|
50
|
+
comments: results.filter(r => r.type === 'comment'),
|
|
51
|
+
logs: results.filter(r => r.type === 'log'),
|
|
52
|
+
artifacts: results.filter(r => r.type === 'artifact'),
|
|
53
|
+
other: results.filter(r => !['node', 'comment', 'log', 'artifact'].includes(r.type))
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Add summary information
|
|
57
|
+
return {
|
|
58
|
+
query,
|
|
59
|
+
planId,
|
|
60
|
+
resultCount: results.length,
|
|
61
|
+
typeBreakdown: {
|
|
62
|
+
nodes: organizedResults.nodes.length,
|
|
63
|
+
comments: organizedResults.comments.length,
|
|
64
|
+
logs: organizedResults.logs.length,
|
|
65
|
+
artifacts: organizedResults.artifacts.length,
|
|
66
|
+
other: organizedResults.other.length
|
|
67
|
+
},
|
|
68
|
+
results: organizedResults
|
|
69
|
+
};
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Error in findContentInPlan:', error.message);
|
|
72
|
+
return {
|
|
73
|
+
query,
|
|
74
|
+
planId,
|
|
75
|
+
resultCount: 0,
|
|
76
|
+
typeBreakdown: { nodes: 0, comments: 0, logs: 0, artifacts: 0, other: 0 },
|
|
77
|
+
results: { nodes: [], comments: [], logs: [], artifacts: [], other: [] }
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
searchPlanAndProcess,
|
|
84
|
+
findContentInPlan,
|
|
85
|
+
// Also export the original wrapper functions
|
|
86
|
+
searchPlan,
|
|
87
|
+
globalSearch
|
|
88
|
+
};
|