nodestorm-mcp 0.1.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 +151 -0
- package/dist/client.d.ts +170 -0
- package/dist/client.js +143 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +572 -0
- package/dist/tools/describe.d.ts +18 -0
- package/dist/tools/describe.js +284 -0
- package/dist/workflow-guide.md +1655 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* NodeStorm MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Provides tools for AI agents to create, read, update, and delete NodeStorm workflows.
|
|
6
|
+
*
|
|
7
|
+
* Environment variables:
|
|
8
|
+
* - NODESTORM_API_KEY (required): API key for authentication
|
|
9
|
+
* - NODESTORM_API_URL (optional): API base URL (default: https://nodestorm.ai)
|
|
10
|
+
*/
|
|
11
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
+
import { NodeStormClient, NodeStormApiError } from './client.js';
|
|
15
|
+
import { getNodeDefinitions } from './tools/describe.js';
|
|
16
|
+
import { readFileSync } from 'fs';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
import { dirname, join } from 'path';
|
|
19
|
+
// Get the directory of this file
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
// Read environment variables
|
|
23
|
+
const apiKey = process.env.NODESTORM_API_KEY;
|
|
24
|
+
const apiUrl = process.env.NODESTORM_API_URL || 'https://nodestorm.ai';
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
console.error('Error: NODESTORM_API_KEY environment variable is required');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
// Initialize client
|
|
30
|
+
const client = new NodeStormClient(apiKey, apiUrl);
|
|
31
|
+
// Load workflow guide (bundled with package)
|
|
32
|
+
function loadWorkflowGuide() {
|
|
33
|
+
try {
|
|
34
|
+
const guidePath = join(__dirname, 'workflow-guide.md');
|
|
35
|
+
return readFileSync(guidePath, 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return '# Workflow Guide\n\nGuide file not found. Please reinstall the package.';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Create MCP server
|
|
42
|
+
const server = new Server({
|
|
43
|
+
name: 'nodestorm-mcp',
|
|
44
|
+
version: '0.1.0',
|
|
45
|
+
}, {
|
|
46
|
+
capabilities: {
|
|
47
|
+
tools: {},
|
|
48
|
+
resources: {},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
// Tool definitions
|
|
52
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
53
|
+
return {
|
|
54
|
+
tools: [
|
|
55
|
+
{
|
|
56
|
+
name: 'create_flow',
|
|
57
|
+
description: 'Create a NodeStorm flow from a natural language description, or directly from JSON (admin-only).',
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
prompt: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: 'What the flow should do. Mutually exclusive with workflow_json.',
|
|
64
|
+
},
|
|
65
|
+
name: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'Flow name (optional, will be generated if not provided)',
|
|
68
|
+
},
|
|
69
|
+
workflow_json: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
description: 'Direct workflow JSON bypass (admin-only). Skips the AI generator. Mutually exclusive with prompt.',
|
|
72
|
+
properties: {
|
|
73
|
+
nodes: { type: 'array', items: { type: 'object' } },
|
|
74
|
+
edges: { type: 'array', items: { type: 'object' } },
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
description: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
description: 'Description of the workflow',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'get_flow',
|
|
86
|
+
description: 'Get a flow by ID',
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {
|
|
90
|
+
id: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
description: 'Flow ID',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
required: ['id'],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'update_flow',
|
|
100
|
+
description: 'Update flow METADATA: name, description, schedule_*. Does NOT modify nodes or edges. ' +
|
|
101
|
+
'For any structural change use update_flow_structure.',
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
id: {
|
|
106
|
+
type: 'string',
|
|
107
|
+
description: 'Flow ID',
|
|
108
|
+
},
|
|
109
|
+
flow: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
description: 'Metadata-only updates',
|
|
112
|
+
properties: {
|
|
113
|
+
name: { type: 'string' },
|
|
114
|
+
description: { type: 'string' },
|
|
115
|
+
schedule_enabled: { type: 'boolean' },
|
|
116
|
+
schedule_cron: { type: 'string' },
|
|
117
|
+
schedule_timezone: { type: 'string' },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
required: ['id', 'flow'],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'update_flow_structure',
|
|
126
|
+
description: "Apply a list of structural operations atomically to an existing flow's nodes and edges. " +
|
|
127
|
+
'Each operation is one of: ' +
|
|
128
|
+
'{ op: "edit_node", node_id, data?, position? }, ' +
|
|
129
|
+
'{ op: "add_node", node: { id?, type, position, data } }, ' +
|
|
130
|
+
'{ op: "remove_node", node_id }, ' +
|
|
131
|
+
'{ op: "add_edge", source, target, source_handle?, target_handle? }, ' +
|
|
132
|
+
'{ op: "remove_edge", edge_id }. ' +
|
|
133
|
+
'All ops apply in a single transaction; if any op fails or the final flow is invalid, the whole batch rolls back. ' +
|
|
134
|
+
'Order matters: each op sees the cumulative state from prior ops in the batch. ' +
|
|
135
|
+
'To remove a middle node and reconnect, batch [remove_node, add_edge] together. ' +
|
|
136
|
+
'For a single change, pass a single-op array. ' +
|
|
137
|
+
'Server generates ids for new nodes/edges. data deep-merges (hashes recurse, arrays replace, null deletes); ' +
|
|
138
|
+
'position replaces outright when both x and y are provided.',
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {
|
|
142
|
+
flow_id: {
|
|
143
|
+
type: 'string',
|
|
144
|
+
description: 'Flow ID',
|
|
145
|
+
},
|
|
146
|
+
operations: {
|
|
147
|
+
type: 'array',
|
|
148
|
+
description: 'Ordered list of operations. Discriminated by `op`.',
|
|
149
|
+
items: {
|
|
150
|
+
anyOf: [
|
|
151
|
+
{
|
|
152
|
+
type: 'object',
|
|
153
|
+
title: 'edit_node',
|
|
154
|
+
properties: {
|
|
155
|
+
op: { type: 'string', enum: ['edit_node'] },
|
|
156
|
+
node_id: { type: 'string', description: 'Id of the node to edit' },
|
|
157
|
+
data: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
description: 'Deep-merged into existing data (hashes recurse, arrays replace outright, null deletes a key)',
|
|
160
|
+
},
|
|
161
|
+
position: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
description: 'Replaces position outright; both x and y required',
|
|
164
|
+
properties: { x: { type: 'number' }, y: { type: 'number' } },
|
|
165
|
+
required: ['x', 'y'],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
required: ['op', 'node_id'],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
type: 'object',
|
|
172
|
+
title: 'add_node',
|
|
173
|
+
properties: {
|
|
174
|
+
op: { type: 'string', enum: ['add_node'] },
|
|
175
|
+
node: {
|
|
176
|
+
type: 'object',
|
|
177
|
+
description: 'Node payload. Server generates id if not provided.',
|
|
178
|
+
properties: {
|
|
179
|
+
id: { type: 'string' },
|
|
180
|
+
type: { type: 'string', description: 'Node type — see describe_nodes for the catalog' },
|
|
181
|
+
data: { type: 'object' },
|
|
182
|
+
position: {
|
|
183
|
+
type: 'object',
|
|
184
|
+
properties: { x: { type: 'number' }, y: { type: 'number' } },
|
|
185
|
+
required: ['x', 'y'],
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
required: ['type'],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
required: ['op', 'node'],
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
type: 'object',
|
|
195
|
+
title: 'remove_node',
|
|
196
|
+
properties: {
|
|
197
|
+
op: { type: 'string', enum: ['remove_node'] },
|
|
198
|
+
node_id: { type: 'string' },
|
|
199
|
+
},
|
|
200
|
+
required: ['op', 'node_id'],
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
type: 'object',
|
|
204
|
+
title: 'add_edge',
|
|
205
|
+
properties: {
|
|
206
|
+
op: { type: 'string', enum: ['add_edge'] },
|
|
207
|
+
source: { type: 'string', description: 'Source node id' },
|
|
208
|
+
target: { type: 'string', description: 'Target node id' },
|
|
209
|
+
source_handle: { type: 'string', description: 'Optional named output handle on source' },
|
|
210
|
+
target_handle: { type: 'string', description: 'Optional named input handle on target' },
|
|
211
|
+
},
|
|
212
|
+
required: ['op', 'source', 'target'],
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
type: 'object',
|
|
216
|
+
title: 'remove_edge',
|
|
217
|
+
properties: {
|
|
218
|
+
op: { type: 'string', enum: ['remove_edge'] },
|
|
219
|
+
edge_id: { type: 'string' },
|
|
220
|
+
},
|
|
221
|
+
required: ['op', 'edge_id'],
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
required: ['flow_id', 'operations'],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: 'delete_flow',
|
|
232
|
+
description: 'Delete a flow',
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: 'object',
|
|
235
|
+
properties: {
|
|
236
|
+
id: {
|
|
237
|
+
type: 'string',
|
|
238
|
+
description: 'Flow ID',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
required: ['id'],
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'list_flows',
|
|
246
|
+
description: "List or search the user's workflows. " +
|
|
247
|
+
'Pass `query` to filter by title/description. ' +
|
|
248
|
+
"scope='user' (default) searches user's own flows; scope='public' searches published community/official entries.",
|
|
249
|
+
inputSchema: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties: {
|
|
252
|
+
query: {
|
|
253
|
+
type: 'string',
|
|
254
|
+
description: 'Optional search query',
|
|
255
|
+
},
|
|
256
|
+
scope: {
|
|
257
|
+
type: 'string',
|
|
258
|
+
enum: ['user', 'public'],
|
|
259
|
+
description: 'Search scope (default: user)',
|
|
260
|
+
},
|
|
261
|
+
limit: {
|
|
262
|
+
type: 'number',
|
|
263
|
+
description: 'Max results to return (default: 20)',
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'describe_nodes',
|
|
270
|
+
description: 'Get node type definitions for building flows',
|
|
271
|
+
inputSchema: {
|
|
272
|
+
type: 'object',
|
|
273
|
+
properties: {
|
|
274
|
+
types: {
|
|
275
|
+
type: 'array',
|
|
276
|
+
items: { type: 'string' },
|
|
277
|
+
description: "Filter to specific node types (e.g., ['prompt', 'list'])",
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: 'get_flow_run',
|
|
284
|
+
description: 'Get details of a specific flow run by ID, including execution results and node outputs',
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: 'object',
|
|
287
|
+
properties: {
|
|
288
|
+
id: {
|
|
289
|
+
type: 'string',
|
|
290
|
+
description: 'Flow run ID',
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
required: ['id'],
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: 'search_chat_history',
|
|
298
|
+
description: 'Search chat message history using semantic (meaning-based) search. Returns past messages ranked by relevance to the query. Supports filtering by role, source, and date range.',
|
|
299
|
+
inputSchema: {
|
|
300
|
+
type: 'object',
|
|
301
|
+
properties: {
|
|
302
|
+
query: {
|
|
303
|
+
type: 'string',
|
|
304
|
+
description: 'Natural language search query',
|
|
305
|
+
},
|
|
306
|
+
limit: {
|
|
307
|
+
type: 'number',
|
|
308
|
+
description: 'Max results (default 20, max 50)',
|
|
309
|
+
},
|
|
310
|
+
role: {
|
|
311
|
+
type: 'string',
|
|
312
|
+
enum: ['user', 'assistant'],
|
|
313
|
+
description: 'Filter by message role',
|
|
314
|
+
},
|
|
315
|
+
source: {
|
|
316
|
+
type: 'string',
|
|
317
|
+
enum: ['chat', 'heartbeat', 'telegram'],
|
|
318
|
+
description: 'Filter by message source',
|
|
319
|
+
},
|
|
320
|
+
from: {
|
|
321
|
+
type: 'string',
|
|
322
|
+
description: 'Start date filter (ISO8601)',
|
|
323
|
+
},
|
|
324
|
+
to: {
|
|
325
|
+
type: 'string',
|
|
326
|
+
description: 'End date filter (ISO8601)',
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
required: ['query'],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: 'list_flow_runs',
|
|
334
|
+
description: 'List flow runs with optional filters for debugging and monitoring',
|
|
335
|
+
inputSchema: {
|
|
336
|
+
type: 'object',
|
|
337
|
+
properties: {
|
|
338
|
+
flow_id: {
|
|
339
|
+
type: 'number',
|
|
340
|
+
description: 'Filter by flow ID',
|
|
341
|
+
},
|
|
342
|
+
status: {
|
|
343
|
+
type: 'string',
|
|
344
|
+
enum: ['success', 'error', 'pending', 'running'],
|
|
345
|
+
description: 'Filter by status',
|
|
346
|
+
},
|
|
347
|
+
page: {
|
|
348
|
+
type: 'number',
|
|
349
|
+
description: 'Page number (default: 1)',
|
|
350
|
+
},
|
|
351
|
+
per_page: {
|
|
352
|
+
type: 'number',
|
|
353
|
+
description: 'Results per page (default: 20, max: 100)',
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
};
|
|
360
|
+
});
|
|
361
|
+
// Tool execution
|
|
362
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
363
|
+
const { name, arguments: args } = request.params;
|
|
364
|
+
try {
|
|
365
|
+
switch (name) {
|
|
366
|
+
case 'create_flow': {
|
|
367
|
+
const { prompt, name: flowName, workflow_json, description } = args;
|
|
368
|
+
const result = await client.createFlow({ prompt, name: flowName, workflow_json, description });
|
|
369
|
+
return {
|
|
370
|
+
content: [
|
|
371
|
+
{
|
|
372
|
+
type: 'text',
|
|
373
|
+
text: JSON.stringify(result, null, 2),
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
case 'get_flow': {
|
|
379
|
+
const { id } = args;
|
|
380
|
+
const result = await client.getFlow(id);
|
|
381
|
+
return {
|
|
382
|
+
content: [
|
|
383
|
+
{
|
|
384
|
+
type: 'text',
|
|
385
|
+
text: JSON.stringify(result, null, 2),
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
case 'update_flow': {
|
|
391
|
+
const { id, flow } = args;
|
|
392
|
+
const result = await client.updateFlow(id, flow);
|
|
393
|
+
return {
|
|
394
|
+
content: [
|
|
395
|
+
{
|
|
396
|
+
type: 'text',
|
|
397
|
+
text: JSON.stringify(result, null, 2),
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
case 'update_flow_structure': {
|
|
403
|
+
const { flow_id, operations } = args;
|
|
404
|
+
const result = await client.updateFlowStructure(flow_id, operations);
|
|
405
|
+
return {
|
|
406
|
+
content: [
|
|
407
|
+
{
|
|
408
|
+
type: 'text',
|
|
409
|
+
text: JSON.stringify(result, null, 2),
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
case 'delete_flow': {
|
|
415
|
+
const { id } = args;
|
|
416
|
+
const result = await client.deleteFlow(id);
|
|
417
|
+
return {
|
|
418
|
+
content: [
|
|
419
|
+
{
|
|
420
|
+
type: 'text',
|
|
421
|
+
text: JSON.stringify(result, null, 2),
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
case 'list_flows': {
|
|
427
|
+
const { query, scope = 'user', limit = 20 } = args;
|
|
428
|
+
const result = await client.listFlows({ query, scope, limit });
|
|
429
|
+
return {
|
|
430
|
+
content: [
|
|
431
|
+
{
|
|
432
|
+
type: 'text',
|
|
433
|
+
text: JSON.stringify(result, null, 2),
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
case 'describe_nodes': {
|
|
439
|
+
const { types } = args;
|
|
440
|
+
const nodes = getNodeDefinitions(types);
|
|
441
|
+
return {
|
|
442
|
+
content: [
|
|
443
|
+
{
|
|
444
|
+
type: 'text',
|
|
445
|
+
text: JSON.stringify({ nodes }, null, 2),
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
case 'get_flow_run': {
|
|
451
|
+
const { id } = args;
|
|
452
|
+
const result = await client.getFlowRun(id);
|
|
453
|
+
return {
|
|
454
|
+
content: [
|
|
455
|
+
{
|
|
456
|
+
type: 'text',
|
|
457
|
+
text: JSON.stringify(result, null, 2),
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
case 'search_chat_history': {
|
|
463
|
+
const { query, limit, role, source, from, to } = args;
|
|
464
|
+
const result = await client.searchChatHistory({ query, limit, role, source, from, to });
|
|
465
|
+
return {
|
|
466
|
+
content: [
|
|
467
|
+
{
|
|
468
|
+
type: 'text',
|
|
469
|
+
text: JSON.stringify(result, null, 2),
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
case 'list_flow_runs': {
|
|
475
|
+
const { flow_id, status, page, per_page } = args;
|
|
476
|
+
const result = await client.listFlowRuns({
|
|
477
|
+
flowId: flow_id,
|
|
478
|
+
status,
|
|
479
|
+
page,
|
|
480
|
+
perPage: per_page,
|
|
481
|
+
});
|
|
482
|
+
return {
|
|
483
|
+
content: [
|
|
484
|
+
{
|
|
485
|
+
type: 'text',
|
|
486
|
+
text: JSON.stringify(result, null, 2),
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
default:
|
|
492
|
+
return {
|
|
493
|
+
content: [
|
|
494
|
+
{
|
|
495
|
+
type: 'text',
|
|
496
|
+
text: JSON.stringify({ error: `Unknown tool: ${name}`, code: 'invalid' }),
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
isError: true,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
if (error instanceof NodeStormApiError) {
|
|
505
|
+
return {
|
|
506
|
+
content: [
|
|
507
|
+
{
|
|
508
|
+
type: 'text',
|
|
509
|
+
text: JSON.stringify({
|
|
510
|
+
error: error.message,
|
|
511
|
+
code: error.code,
|
|
512
|
+
errors: error.errors,
|
|
513
|
+
}),
|
|
514
|
+
},
|
|
515
|
+
],
|
|
516
|
+
isError: true,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
content: [
|
|
521
|
+
{
|
|
522
|
+
type: 'text',
|
|
523
|
+
text: JSON.stringify({
|
|
524
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
525
|
+
code: 'server_error',
|
|
526
|
+
}),
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
isError: true,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
// Resource definitions
|
|
534
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
535
|
+
return {
|
|
536
|
+
resources: [
|
|
537
|
+
{
|
|
538
|
+
uri: 'nodestorm://workflow-guide',
|
|
539
|
+
name: 'Workflow Builder Guide',
|
|
540
|
+
description: 'Complete guide for building NodeStorm flows: node types, edge rules, iteration semantics, validation, patterns, and examples',
|
|
541
|
+
mimeType: 'text/markdown',
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
};
|
|
545
|
+
});
|
|
546
|
+
// Resource reading
|
|
547
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
548
|
+
const { uri } = request.params;
|
|
549
|
+
if (uri === 'nodestorm://workflow-guide') {
|
|
550
|
+
const guide = loadWorkflowGuide();
|
|
551
|
+
return {
|
|
552
|
+
contents: [
|
|
553
|
+
{
|
|
554
|
+
uri,
|
|
555
|
+
mimeType: 'text/markdown',
|
|
556
|
+
text: guide,
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
562
|
+
});
|
|
563
|
+
// Start the server
|
|
564
|
+
async function main() {
|
|
565
|
+
const transport = new StdioServerTransport();
|
|
566
|
+
await server.connect(transport);
|
|
567
|
+
console.error('NodeStorm MCP server started');
|
|
568
|
+
}
|
|
569
|
+
main().catch((error) => {
|
|
570
|
+
console.error('Fatal error:', error);
|
|
571
|
+
process.exit(1);
|
|
572
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node definitions for the describe_nodes tool
|
|
3
|
+
* These are bundled statically to avoid API calls
|
|
4
|
+
*/
|
|
5
|
+
export interface NodeDefinition {
|
|
6
|
+
type: string;
|
|
7
|
+
description: string;
|
|
8
|
+
category: string;
|
|
9
|
+
config_schema: Record<string, any>;
|
|
10
|
+
handles: {
|
|
11
|
+
inputs: string[];
|
|
12
|
+
outputs: string[];
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get node definitions, optionally filtered by type
|
|
17
|
+
*/
|
|
18
|
+
export declare function getNodeDefinitions(types?: string[]): NodeDefinition[];
|