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/LICENSE +21 -0
- package/README.md +39 -0
- package/package.json +8 -2
- package/src/api-client.js +273 -114
- package/src/index.js +59 -27
- package/src/integrations/search-integration.js +3 -5
- package/src/server-http.js +510 -0
- package/src/session-manager.js +201 -0
- package/src/tools/search-wrapper.js +12 -6
- package/src/tools.js +1540 -159
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Talking Agents Oy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -112,6 +112,45 @@ NODE_ENV=production
|
|
|
112
112
|
npm start
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
## Transport Modes
|
|
116
|
+
|
|
117
|
+
The Planning System MCP Server supports two transport modes:
|
|
118
|
+
|
|
119
|
+
### 🖥️ stdio Mode (Default)
|
|
120
|
+
For **local use** with Claude Desktop, Claude Code, and other local MCP clients:
|
|
121
|
+
- Default transport when running `npm start`
|
|
122
|
+
- Communication via stdin/stdout
|
|
123
|
+
- Best for development and personal use
|
|
124
|
+
- See sections below for Claude Desktop configuration
|
|
125
|
+
|
|
126
|
+
### 🌐 HTTP/SSE Mode
|
|
127
|
+
For **remote access** via Anthropic's MCP Connector and cloud deployments:
|
|
128
|
+
- Implements MCP Streamable HTTP specification (2025-06-18)
|
|
129
|
+
- RESTful JSON-RPC API over HTTP
|
|
130
|
+
- Production-ready for Cloud Run deployment
|
|
131
|
+
- Supports session management and concurrent connections
|
|
132
|
+
- **Documentation**: See [HTTP_MODE.md](./HTTP_MODE.md)
|
|
133
|
+
- **Deployment**: See [MCP_REGISTRY.md](./MCP_REGISTRY.md)
|
|
134
|
+
|
|
135
|
+
**Quick Start (HTTP Mode):**
|
|
136
|
+
```bash
|
|
137
|
+
# Local development
|
|
138
|
+
npm run start:http
|
|
139
|
+
# Server runs on http://127.0.0.1:3100
|
|
140
|
+
|
|
141
|
+
# Production deployment
|
|
142
|
+
./deploy.sh
|
|
143
|
+
# Deploys to Google Cloud Run (europe-north1)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Use Cases:**
|
|
147
|
+
- ✅ **Anthropic Messages API**: Use with Claude via MCP Connector
|
|
148
|
+
- ✅ **Multi-Agent Systems**: agent-runtime integration
|
|
149
|
+
- ✅ **Cloud Deployment**: Scalable, always-available service
|
|
150
|
+
- ✅ **MCP Registry**: Discoverable via registry lookup
|
|
151
|
+
|
|
152
|
+
For detailed HTTP mode documentation, see [HTTP_MODE.md](./HTTP_MODE.md).
|
|
153
|
+
|
|
115
154
|
## Using with Claude Desktop
|
|
116
155
|
|
|
117
156
|
### Option 1: Using npx (Recommended - Simplest Setup)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-planner-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "MCP server interface for the Planning System API",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node src/index.js",
|
|
11
|
+
"start:http": "MCP_TRANSPORT=http node src/index.js",
|
|
11
12
|
"dev": "nodemon src/index.js",
|
|
13
|
+
"dev:http": "MCP_TRANSPORT=http nodemon src/index.js",
|
|
12
14
|
"test": "jest",
|
|
13
15
|
"test:tools": "node test-tools.js",
|
|
14
16
|
"setup": "node src/setup.js",
|
|
@@ -27,7 +29,7 @@
|
|
|
27
29
|
"license": "MIT",
|
|
28
30
|
"repository": {
|
|
29
31
|
"type": "git",
|
|
30
|
-
"url": "git+https://github.com/
|
|
32
|
+
"url": "git+https://github.com/TAgents/agent-planner-mcp.git"
|
|
31
33
|
},
|
|
32
34
|
"homepage": "https://agentplanner.io",
|
|
33
35
|
"bugs": {
|
|
@@ -37,12 +39,16 @@
|
|
|
37
39
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
38
40
|
"axios": "^1.6.2",
|
|
39
41
|
"dotenv": "^16.3.1",
|
|
42
|
+
"express": "^4.18.2",
|
|
40
43
|
"ignore": "^7.0.3"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"jest": "^29.7.0",
|
|
44
47
|
"nodemon": "^3.0.1"
|
|
45
48
|
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
46
52
|
"engines": {
|
|
47
53
|
"node": ">=16.0.0"
|
|
48
54
|
},
|
package/src/api-client.js
CHANGED
|
@@ -26,23 +26,28 @@ const apiClient = axios.create({
|
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
// Log API requests in
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
// Log API requests only in development mode
|
|
30
|
+
if (process.env.NODE_ENV === 'development') {
|
|
31
|
+
apiClient.interceptors.request.use(request => {
|
|
32
|
+
console.error(`API Request: ${request.method.toUpperCase()} ${request.url}`);
|
|
33
|
+
return request;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Handle API responses - log details only in development, always handle auth errors helpfully
|
|
36
38
|
apiClient.interceptors.response.use(
|
|
37
39
|
response => {
|
|
38
|
-
|
|
40
|
+
if (process.env.NODE_ENV === 'development') {
|
|
41
|
+
console.error(`API Response: ${response.status} ${response.statusText}`);
|
|
42
|
+
}
|
|
39
43
|
return response;
|
|
40
44
|
},
|
|
41
45
|
error => {
|
|
46
|
+
// Always log auth errors helpfully (but not the token itself)
|
|
42
47
|
if (error.response && error.response.status === 401) {
|
|
43
|
-
console.error('API Error: Authentication failed (401). Please check that your USER_API_TOKEN is correct
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
console.error('API Error: Authentication failed (401). Please check that your USER_API_TOKEN is correct and not revoked.');
|
|
49
|
+
} else if (process.env.NODE_ENV === 'development') {
|
|
50
|
+
// Only log other errors in development
|
|
46
51
|
console.error('API Error:', error.response ? error.response.data : error.message);
|
|
47
52
|
}
|
|
48
53
|
return Promise.reject(error);
|
|
@@ -100,6 +105,30 @@ const plans = {
|
|
|
100
105
|
*/
|
|
101
106
|
deletePlan: async (planId) => {
|
|
102
107
|
await apiClient.delete(`/plans/${planId}`);
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Update plan visibility (make public or private)
|
|
112
|
+
* @param {string} planId - Plan ID
|
|
113
|
+
* @param {Object} visibilityData - Visibility settings
|
|
114
|
+
* @param {string} visibilityData.visibility - 'public' or 'private'
|
|
115
|
+
* @param {string} [visibilityData.github_repo_owner] - GitHub repo owner (for public plans)
|
|
116
|
+
* @param {string} [visibilityData.github_repo_name] - GitHub repo name (for public plans)
|
|
117
|
+
* @returns {Promise<Object>} - Updated plan with visibility info
|
|
118
|
+
*/
|
|
119
|
+
updateVisibility: async (planId, visibilityData) => {
|
|
120
|
+
const response = await apiClient.put(`/plans/${planId}/visibility`, visibilityData);
|
|
121
|
+
return response.data;
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get a public plan (no authentication required in browser, but API token needed for MCP)
|
|
126
|
+
* @param {string} planId - Plan ID
|
|
127
|
+
* @returns {Promise<Object>} - Public plan details
|
|
128
|
+
*/
|
|
129
|
+
getPublicPlan: async (planId) => {
|
|
130
|
+
const response = await apiClient.get(`/plans/${planId}/public`);
|
|
131
|
+
return response.data;
|
|
103
132
|
}
|
|
104
133
|
};
|
|
105
134
|
|
|
@@ -110,10 +139,18 @@ const nodes = {
|
|
|
110
139
|
/**
|
|
111
140
|
* Get nodes for a plan
|
|
112
141
|
* @param {string} planId - Plan ID
|
|
142
|
+
* @param {Object} options - Optional query parameters
|
|
143
|
+
* @param {boolean} options.include_details - Include full node details (default: false)
|
|
113
144
|
* @returns {Promise<Array>} - List of nodes
|
|
114
145
|
*/
|
|
115
|
-
getNodes: async (planId) => {
|
|
116
|
-
const
|
|
146
|
+
getNodes: async (planId, options = {}) => {
|
|
147
|
+
const params = new URLSearchParams();
|
|
148
|
+
if (options.include_details) {
|
|
149
|
+
params.append('include_details', 'true');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const queryString = params.toString() ? `?${params.toString()}` : '';
|
|
153
|
+
const response = await apiClient.get(`/plans/${planId}/nodes${queryString}`);
|
|
117
154
|
return response.data;
|
|
118
155
|
},
|
|
119
156
|
|
|
@@ -230,101 +267,6 @@ const logs = {
|
|
|
230
267
|
}
|
|
231
268
|
};
|
|
232
269
|
|
|
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
270
|
/**
|
|
329
271
|
* Activity-related API functions
|
|
330
272
|
*/
|
|
@@ -335,7 +277,7 @@ const activity = {
|
|
|
335
277
|
* @returns {Promise<Array>} - Activity feed
|
|
336
278
|
*/
|
|
337
279
|
getPlanActivity: async (planId) => {
|
|
338
|
-
const response = await apiClient.get(`/activity/
|
|
280
|
+
const response = await apiClient.get(`/activity/plans/${planId}/activity`);
|
|
339
281
|
return response.data;
|
|
340
282
|
},
|
|
341
283
|
|
|
@@ -344,7 +286,7 @@ const activity = {
|
|
|
344
286
|
* @returns {Promise<Array>} - Activity feed
|
|
345
287
|
*/
|
|
346
288
|
getGlobalActivity: async () => {
|
|
347
|
-
const response = await apiClient.get('/activity');
|
|
289
|
+
const response = await apiClient.get('/activity/feed');
|
|
348
290
|
return response.data;
|
|
349
291
|
}
|
|
350
292
|
};
|
|
@@ -499,7 +441,7 @@ const tokens = {
|
|
|
499
441
|
* @returns {Promise<Array>} - List of API tokens
|
|
500
442
|
*/
|
|
501
443
|
getTokens: async () => {
|
|
502
|
-
const response = await apiClient.get('/
|
|
444
|
+
const response = await apiClient.get('/auth/token');
|
|
503
445
|
return response.data;
|
|
504
446
|
},
|
|
505
447
|
|
|
@@ -509,7 +451,7 @@ const tokens = {
|
|
|
509
451
|
* @returns {Promise<Object>} - Created token
|
|
510
452
|
*/
|
|
511
453
|
createToken: async (tokenData) => {
|
|
512
|
-
const response = await apiClient.post('/
|
|
454
|
+
const response = await apiClient.post('/auth/token', tokenData);
|
|
513
455
|
return response.data;
|
|
514
456
|
},
|
|
515
457
|
|
|
@@ -519,7 +461,221 @@ const tokens = {
|
|
|
519
461
|
* @returns {Promise<void>}
|
|
520
462
|
*/
|
|
521
463
|
revokeToken: async (tokenId) => {
|
|
522
|
-
await apiClient.delete(`/
|
|
464
|
+
await apiClient.delete(`/auth/token/${tokenId}`);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Organization API functions
|
|
470
|
+
*/
|
|
471
|
+
const organizations = {
|
|
472
|
+
list: async () => {
|
|
473
|
+
const response = await apiClient.get('/organizations');
|
|
474
|
+
return response.data.organizations || response.data;
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
get: async (orgId) => {
|
|
478
|
+
const response = await apiClient.get(`/organizations/${orgId}`);
|
|
479
|
+
return response.data;
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
create: async (data) => {
|
|
483
|
+
const response = await apiClient.post('/organizations', data);
|
|
484
|
+
return response.data;
|
|
485
|
+
},
|
|
486
|
+
|
|
487
|
+
update: async (orgId, data) => {
|
|
488
|
+
const response = await apiClient.put(`/organizations/${orgId}`, data);
|
|
489
|
+
return response.data;
|
|
490
|
+
},
|
|
491
|
+
|
|
492
|
+
delete: async (orgId) => {
|
|
493
|
+
const response = await apiClient.delete(`/organizations/${orgId}`);
|
|
494
|
+
return response.data;
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
listMembers: async (orgId) => {
|
|
498
|
+
const response = await apiClient.get(`/organizations/${orgId}/members`);
|
|
499
|
+
return response.data.members || response.data;
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
addMember: async (orgId, data) => {
|
|
503
|
+
const response = await apiClient.post(`/organizations/${orgId}/members`, data);
|
|
504
|
+
return response.data;
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
removeMember: async (orgId, memberId) => {
|
|
508
|
+
const response = await apiClient.delete(`/organizations/${orgId}/members/${memberId}`);
|
|
509
|
+
return response.data;
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Goals API functions
|
|
515
|
+
*/
|
|
516
|
+
const goals = {
|
|
517
|
+
list: async (filters = {}) => {
|
|
518
|
+
const params = new URLSearchParams();
|
|
519
|
+
if (filters.organization_id) params.append('organization_id', filters.organization_id);
|
|
520
|
+
if (filters.status) params.append('status', filters.status);
|
|
521
|
+
const response = await apiClient.get(`/goals?${params.toString()}`);
|
|
522
|
+
return response.data.goals || response.data;
|
|
523
|
+
},
|
|
524
|
+
|
|
525
|
+
get: async (goalId) => {
|
|
526
|
+
const response = await apiClient.get(`/goals/${goalId}`);
|
|
527
|
+
return response.data;
|
|
528
|
+
},
|
|
529
|
+
|
|
530
|
+
create: async (data) => {
|
|
531
|
+
const response = await apiClient.post('/goals', data);
|
|
532
|
+
return response.data;
|
|
533
|
+
},
|
|
534
|
+
|
|
535
|
+
update: async (goalId, data) => {
|
|
536
|
+
const response = await apiClient.put(`/goals/${goalId}`, data);
|
|
537
|
+
return response.data;
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
updateMetrics: async (goalId, metrics) => {
|
|
541
|
+
const response = await apiClient.put(`/goals/${goalId}/metrics`, { metrics });
|
|
542
|
+
return response.data;
|
|
543
|
+
},
|
|
544
|
+
|
|
545
|
+
delete: async (goalId) => {
|
|
546
|
+
const response = await apiClient.delete(`/goals/${goalId}`);
|
|
547
|
+
return response.data;
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
linkPlan: async (goalId, planId) => {
|
|
551
|
+
const response = await apiClient.post(`/goals/${goalId}/plans/${planId}`);
|
|
552
|
+
return response.data;
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
unlinkPlan: async (goalId, planId) => {
|
|
556
|
+
const response = await apiClient.delete(`/goals/${goalId}/plans/${planId}`);
|
|
557
|
+
return response.data;
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Knowledge Store API functions
|
|
563
|
+
*/
|
|
564
|
+
const knowledge = {
|
|
565
|
+
/**
|
|
566
|
+
* List knowledge entries with optional filters
|
|
567
|
+
* GET /knowledge
|
|
568
|
+
*/
|
|
569
|
+
listEntries: async (storeIdOrFilters, filters = {}) => {
|
|
570
|
+
const params = new URLSearchParams();
|
|
571
|
+
// Support both (storeId, filters) and (filters) calling patterns
|
|
572
|
+
if (typeof storeIdOrFilters === 'string') {
|
|
573
|
+
params.append('scopeId', storeIdOrFilters);
|
|
574
|
+
if (filters.entry_type) params.append('entryType', filters.entry_type);
|
|
575
|
+
if (filters.tags) params.append('tags', filters.tags);
|
|
576
|
+
if (filters.limit) params.append('limit', filters.limit);
|
|
577
|
+
if (filters.offset) params.append('offset', filters.offset);
|
|
578
|
+
} else if (typeof storeIdOrFilters === 'object') {
|
|
579
|
+
const f = storeIdOrFilters;
|
|
580
|
+
if (f.scope) params.append('scope', f.scope);
|
|
581
|
+
if (f.scope_id) params.append('scopeId', f.scope_id);
|
|
582
|
+
if (f.entry_type) params.append('entryType', f.entry_type);
|
|
583
|
+
if (f.limit) params.append('limit', f.limit);
|
|
584
|
+
if (f.offset) params.append('offset', f.offset);
|
|
585
|
+
}
|
|
586
|
+
const response = await apiClient.get(`/knowledge?${params.toString()}`);
|
|
587
|
+
return response.data;
|
|
588
|
+
},
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Alias for listEntries — used by get_context and understand_context tools
|
|
592
|
+
*/
|
|
593
|
+
getEntries: async (filters = {}) => {
|
|
594
|
+
return knowledge.listEntries(filters);
|
|
595
|
+
},
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Get a single knowledge entry
|
|
599
|
+
* GET /knowledge/:id
|
|
600
|
+
*/
|
|
601
|
+
getEntry: async (entryId) => {
|
|
602
|
+
const response = await apiClient.get(`/knowledge/${entryId}`);
|
|
603
|
+
return response.data;
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Create a knowledge entry
|
|
608
|
+
* POST /knowledge
|
|
609
|
+
*/
|
|
610
|
+
createEntry: async (data) => {
|
|
611
|
+
const response = await apiClient.post('/knowledge', data);
|
|
612
|
+
return response.data;
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Update a knowledge entry
|
|
617
|
+
* PUT /knowledge/:id
|
|
618
|
+
*/
|
|
619
|
+
updateEntry: async (entryId, data) => {
|
|
620
|
+
const response = await apiClient.put(`/knowledge/${entryId}`, data);
|
|
621
|
+
return response.data;
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Delete a knowledge entry
|
|
626
|
+
* DELETE /knowledge/:id
|
|
627
|
+
*/
|
|
628
|
+
deleteEntry: async (entryId) => {
|
|
629
|
+
const response = await apiClient.delete(`/knowledge/${entryId}`);
|
|
630
|
+
return response.data;
|
|
631
|
+
},
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Semantic search across knowledge entries
|
|
635
|
+
* POST /knowledge/search
|
|
636
|
+
*/
|
|
637
|
+
search: async (data) => {
|
|
638
|
+
const response = await apiClient.post('/knowledge/search', data);
|
|
639
|
+
return response.data;
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Agent Context API functions (leaf-up context loading)
|
|
645
|
+
*/
|
|
646
|
+
const context = {
|
|
647
|
+
/**
|
|
648
|
+
* Get focused context for a specific node (task/phase)
|
|
649
|
+
* Traverses from node up to root, including goals, org, and knowledge
|
|
650
|
+
* @param {string} nodeId - Node ID
|
|
651
|
+
* @param {Object} options - Options (include_knowledge, include_siblings)
|
|
652
|
+
* @returns {Promise<Object>} - Agent context
|
|
653
|
+
*/
|
|
654
|
+
getNodeContext: async (nodeId, options = {}) => {
|
|
655
|
+
const params = new URLSearchParams({ node_id: nodeId });
|
|
656
|
+
if (options.include_knowledge !== undefined) {
|
|
657
|
+
params.append('include_knowledge', options.include_knowledge);
|
|
658
|
+
}
|
|
659
|
+
if (options.include_siblings !== undefined) {
|
|
660
|
+
params.append('include_siblings', options.include_siblings);
|
|
661
|
+
}
|
|
662
|
+
const response = await apiClient.get(`/context?${params.toString()}`);
|
|
663
|
+
return response.data;
|
|
664
|
+
},
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Get plan-level context (overview, not full tree)
|
|
668
|
+
* @param {string} planId - Plan ID
|
|
669
|
+
* @param {Object} options - Options (include_knowledge)
|
|
670
|
+
* @returns {Promise<Object>} - Plan context
|
|
671
|
+
*/
|
|
672
|
+
getPlanContext: async (planId, options = {}) => {
|
|
673
|
+
const params = new URLSearchParams({ plan_id: planId });
|
|
674
|
+
if (options.include_knowledge !== undefined) {
|
|
675
|
+
params.append('include_knowledge', options.include_knowledge);
|
|
676
|
+
}
|
|
677
|
+
const response = await apiClient.get(`/context/plan?${params.toString()}`);
|
|
678
|
+
return response.data;
|
|
523
679
|
}
|
|
524
680
|
};
|
|
525
681
|
|
|
@@ -532,9 +688,12 @@ module.exports = {
|
|
|
532
688
|
nodes,
|
|
533
689
|
comments,
|
|
534
690
|
logs,
|
|
535
|
-
artifacts,
|
|
536
691
|
activity,
|
|
537
692
|
search,
|
|
538
693
|
tokens,
|
|
694
|
+
organizations,
|
|
695
|
+
goals,
|
|
696
|
+
knowledge,
|
|
697
|
+
context,
|
|
539
698
|
axiosInstance // Export for direct API calls
|
|
540
699
|
};
|
package/src/index.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
3
3
|
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
4
|
+
const { MCPHTTPServer } = require('./server-http');
|
|
4
5
|
const { setupTools } = require('./tools');
|
|
5
6
|
require('dotenv').config();
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Initialize the Planning System MCP Server
|
|
9
|
-
*
|
|
10
|
+
*
|
|
11
|
+
* Supports two transport modes:
|
|
12
|
+
* - stdio: For local use with Claude Desktop, Claude Code, etc.
|
|
13
|
+
* - http: For remote access via Anthropic's MCP Connector
|
|
14
|
+
*
|
|
15
|
+
* Set MCP_TRANSPORT=http to use HTTP mode
|
|
16
|
+
*
|
|
10
17
|
* Features:
|
|
11
18
|
* - Simplified architecture with tools-only interface
|
|
12
19
|
* - Full CRUD operations on all entities
|
|
@@ -17,47 +24,72 @@ require('dotenv').config();
|
|
|
17
24
|
*/
|
|
18
25
|
async function main() {
|
|
19
26
|
const isDev = process.env.NODE_ENV === 'development';
|
|
20
|
-
|
|
27
|
+
const transport = process.env.MCP_TRANSPORT || 'stdio';
|
|
28
|
+
|
|
21
29
|
if (isDev) {
|
|
22
30
|
console.error('Initializing Planning System MCP Server...');
|
|
31
|
+
console.error(`Transport mode: ${transport}`);
|
|
23
32
|
}
|
|
24
|
-
|
|
33
|
+
|
|
25
34
|
try {
|
|
26
35
|
// Log environment settings
|
|
27
36
|
console.error(`API URL: ${process.env.API_URL || 'http://localhost:3000'}`);
|
|
28
|
-
|
|
37
|
+
|
|
29
38
|
// Check for token
|
|
30
39
|
const userApiToken = process.env.USER_API_TOKEN || process.env.API_TOKEN;
|
|
31
40
|
console.error(`User API Token: ${userApiToken ? '***' + userApiToken.slice(-4) : 'NOT SET'}`);
|
|
32
41
|
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.
|
|
34
|
-
|
|
42
|
+
console.error(`MCP Server Version: ${process.env.MCP_SERVER_VERSION || '0.3.1'}`);
|
|
43
|
+
|
|
35
44
|
// Validate required environment variables
|
|
36
45
|
if (!userApiToken) {
|
|
37
46
|
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
47
|
}
|
|
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
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
if (transport === 'http') {
|
|
50
|
+
// HTTP/SSE transport mode
|
|
51
|
+
const httpServer = new MCPHTTPServer({
|
|
52
|
+
port: process.env.PORT || 3100,
|
|
53
|
+
host: process.env.HOST || '127.0.0.1'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await httpServer.start();
|
|
57
|
+
|
|
58
|
+
// Handle graceful shutdown
|
|
59
|
+
process.on('SIGINT', async () => {
|
|
60
|
+
console.error('\nShutting down MCP HTTP Server...');
|
|
61
|
+
await httpServer.stop();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
process.on('SIGTERM', async () => {
|
|
66
|
+
console.error('\nShutting down MCP HTTP Server...');
|
|
67
|
+
await httpServer.stop();
|
|
68
|
+
process.exit(0);
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
// Stdio transport mode (default)
|
|
72
|
+
const server = new Server({
|
|
73
|
+
name: process.env.MCP_SERVER_NAME || "planning-system-mcp",
|
|
74
|
+
version: process.env.MCP_SERVER_VERSION || "0.3.1"
|
|
75
|
+
}, {
|
|
76
|
+
capabilities: {
|
|
77
|
+
tools: {}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
console.error('MCP Server created');
|
|
82
|
+
|
|
83
|
+
// Setup tools
|
|
84
|
+
setupTools(server);
|
|
85
|
+
|
|
86
|
+
// Connect transport
|
|
87
|
+
const stdioTransport = new StdioServerTransport();
|
|
88
|
+
await server.connect(stdioTransport);
|
|
89
|
+
|
|
90
|
+
console.error('MCP Server running on stdio transport');
|
|
91
|
+
console.error('Ready to accept connections from agents');
|
|
92
|
+
}
|
|
61
93
|
} catch (error) {
|
|
62
94
|
console.error('Failed to initialize MCP server:', error);
|
|
63
95
|
process.exit(1);
|