nodestorm-mcp 0.1.0 → 0.1.2
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 +83 -29
- package/dist/client.d.ts +63 -3
- package/dist/client.js +35 -0
- package/dist/index.js +109 -52
- package/dist/instructions.d.ts +8 -0
- package/dist/instructions.js +23 -0
- package/dist/tools/docs.d.ts +7 -0
- package/dist/tools/docs.js +51 -0
- package/package.json +4 -4
- package/dist/tools/describe.d.ts +0 -18
- package/dist/tools/describe.js +0 -284
- package/dist/workflow-guide.md +0 -1655
package/README.md
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
# nodestorm-mcp
|
|
2
2
|
|
|
3
|
-
MCP (Model Context Protocol) server for NodeStorm workflow automation.
|
|
3
|
+
MCP (Model Context Protocol) server for [NodeStorm](https://nodestorm.ai) workflow automation.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
npx nodestorm-mcp
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Or install globally:
|
|
7
|
+
No install step needed — MCP clients run it via `npx`:
|
|
12
8
|
|
|
13
9
|
```bash
|
|
14
|
-
|
|
10
|
+
npx -y nodestorm-mcp
|
|
15
11
|
```
|
|
16
12
|
|
|
17
13
|
## Configuration
|
|
18
14
|
|
|
19
|
-
### Claude Code
|
|
15
|
+
### Claude Code
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
From your project directory:
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
```bash
|
|
20
|
+
claude mcp add nodestorm -e NODESTORM_API_KEY=ns_your_access_token_here -- npx -y nodestorm-mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or add to a `.mcp.json` in your project root (or `~/.claude.json` for all projects):
|
|
25
24
|
|
|
26
25
|
```json
|
|
27
26
|
{
|
|
@@ -37,6 +36,13 @@ Add to your Claude configuration file:
|
|
|
37
36
|
}
|
|
38
37
|
```
|
|
39
38
|
|
|
39
|
+
### Claude Desktop
|
|
40
|
+
|
|
41
|
+
Add the same `mcpServers` block to:
|
|
42
|
+
|
|
43
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
44
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
45
|
+
|
|
40
46
|
### Environment Variables
|
|
41
47
|
|
|
42
48
|
| Variable | Required | Description |
|
|
@@ -55,6 +61,8 @@ Note: Access tokens require an active NodeStorm subscription.
|
|
|
55
61
|
|
|
56
62
|
## Available Tools
|
|
57
63
|
|
|
64
|
+
At connect the server sends short instructions (0.1.2): what NodeStorm is, which tool a nodestorm.ai link maps to (`/files/N`, `/flows/N`, `/flow-runs/N`, `/help/slug`), and to read a node's doc with `get_doc` before `execute_node`. MCP clients such as Claude Code show them to the model at session start.
|
|
65
|
+
|
|
58
66
|
### create_flow
|
|
59
67
|
Create a new workflow from a natural language description.
|
|
60
68
|
|
|
@@ -72,10 +80,18 @@ Output: { id, name, nodes, edges, url, updated_at }
|
|
|
72
80
|
```
|
|
73
81
|
|
|
74
82
|
### update_flow
|
|
75
|
-
|
|
83
|
+
Update workflow **metadata** (name, description, schedule). Does not modify nodes or edges — use `update_flow_structure` for that.
|
|
76
84
|
|
|
77
85
|
```
|
|
78
|
-
Input: { id: "abc123", flow: { name,
|
|
86
|
+
Input: { id: "abc123", flow: { name, description, schedule_enabled, ... } }
|
|
87
|
+
Output: { id, name, nodes, edges, url, updated_at }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### update_flow_structure
|
|
91
|
+
Apply a list of structural operations (add/update/remove nodes and edges) atomically to an existing workflow.
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
Input: { flow_id: "abc123", operations: [{ op: "add_node", ... }, ...] }
|
|
79
95
|
Output: { id, name, nodes, edges, url, updated_at }
|
|
80
96
|
```
|
|
81
97
|
|
|
@@ -88,37 +104,75 @@ Output: { success: true }
|
|
|
88
104
|
```
|
|
89
105
|
|
|
90
106
|
### list_flows
|
|
91
|
-
List
|
|
107
|
+
List or search your workflows (`scope: "public"` searches published community entries).
|
|
92
108
|
|
|
93
109
|
```
|
|
94
|
-
Input: {
|
|
110
|
+
Input: { query: "pricing", scope: "user", limit: 20 }
|
|
95
111
|
Output: { flows: [...], total }
|
|
96
112
|
```
|
|
97
113
|
|
|
98
|
-
###
|
|
99
|
-
|
|
114
|
+
### get_flow_run
|
|
115
|
+
Get details of a specific flow run, including execution results and node outputs.
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
Input: { id: "run123" }
|
|
119
|
+
Output: { id, flow_id, status, node_results, started_at, finished_at }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### list_flow_runs
|
|
123
|
+
List flow runs with optional filters, for debugging and monitoring.
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
Input: { flow_id: "abc123", status: "failed", limit: 20 }
|
|
127
|
+
Output: { runs: [...], total }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### search_chat_history
|
|
131
|
+
Semantic (meaning-based) search over your past NodeStorm chat messages.
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
Input: { query: "how did I set up the scraper flow", limit: 10 }
|
|
135
|
+
Output: { messages: [...] }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### execute_node
|
|
139
|
+
Run one node directly, without a flow. `config` is the node's `data` as in a flow; read the node's doc with `get_doc` first. Each call counts as one node execution against the monthly quota and is capped at about 90 seconds. Nodes that send or write take effect at once.
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
Input: { node_type: "file", config: { mode: "read", file_id: 123 } } // read a file (nodestorm.ai/files/123)
|
|
143
|
+
Input: { node_type: "folder", config: { folder_id: "top" } } // list the files in a folder ("top" = no folder)
|
|
144
|
+
Input: { node_type: "prompt", config: { input: "Summarize" }, input: "..." }
|
|
145
|
+
Output: { success, node_type, output }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### list_docs
|
|
149
|
+
List NodeStorm product documentation pages. Pass `category` to filter (e.g. `"node"` for the node catalog).
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
Input: { category: "node" } // optional filter
|
|
153
|
+
Output: { pages: [{ slug, title, category, description }] }
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### get_doc
|
|
157
|
+
Get a documentation page by slug, including its full markdown body. Node pages use kebab-case slugs like `node-prompt` and `node-http-request`.
|
|
100
158
|
|
|
101
159
|
```
|
|
102
|
-
Input: {
|
|
103
|
-
Output: {
|
|
160
|
+
Input: { slug: "workflow-execution" }
|
|
161
|
+
Output: { page: { slug, title, category, description, body, updated_at } }
|
|
104
162
|
```
|
|
105
163
|
|
|
106
|
-
###
|
|
107
|
-
|
|
164
|
+
### search_docs
|
|
165
|
+
Semantic (meaning-based) search over the NodeStorm documentation.
|
|
108
166
|
|
|
109
167
|
```
|
|
110
|
-
Input: {
|
|
111
|
-
Output: {
|
|
168
|
+
Input: { query: "how does iteration work" }
|
|
169
|
+
Output: { results: [{ slug, title, category, chunk_content, relevance }] }
|
|
112
170
|
```
|
|
113
171
|
|
|
114
172
|
## Resources
|
|
115
173
|
|
|
116
174
|
### workflow-guide
|
|
117
|
-
|
|
118
|
-
- Node types and their configurations
|
|
119
|
-
- Edge connection rules
|
|
120
|
-
- Iteration patterns
|
|
121
|
-
- Validation requirements
|
|
175
|
+
Pointer to the live NodeStorm docs. The bundled guide was replaced by the docs tools above — start with `get_doc("workflow-execution")` and `list_docs({ category: "node" })`.
|
|
122
176
|
|
|
123
177
|
## Example Usage
|
|
124
178
|
|
|
@@ -131,7 +185,7 @@ Created "Competitor Pricing Analysis"
|
|
|
131
185
|
|
|
132
186
|
User: Add 3 more URLs to the list
|
|
133
187
|
|
|
134
|
-
Claude: [Uses
|
|
188
|
+
Claude: [Uses update_flow_structure with add_node/add_edge operations]
|
|
135
189
|
Updated flow with 3 additional URLs.
|
|
136
190
|
```
|
|
137
191
|
|
package/dist/client.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface FlowRun {
|
|
|
29
29
|
flow_id: number;
|
|
30
30
|
flow_title: string;
|
|
31
31
|
user_id?: number;
|
|
32
|
-
status: 'success' | 'error' | 'pending' | 'running';
|
|
32
|
+
status: 'success' | 'partial' | 'error' | 'pending' | 'running';
|
|
33
33
|
node_count: number;
|
|
34
34
|
duration_seconds: number | null;
|
|
35
35
|
result?: {
|
|
@@ -50,7 +50,7 @@ export interface FlowRunListItem {
|
|
|
50
50
|
id: number;
|
|
51
51
|
flow_id: number;
|
|
52
52
|
flow_title: string;
|
|
53
|
-
status: 'success' | 'error' | 'pending' | 'running';
|
|
53
|
+
status: 'success' | 'partial' | 'error' | 'pending' | 'running';
|
|
54
54
|
node_count: number;
|
|
55
55
|
duration_seconds: number | null;
|
|
56
56
|
created_at: string;
|
|
@@ -104,6 +104,10 @@ export declare class NodeStormClient {
|
|
|
104
104
|
schedule_enabled?: boolean;
|
|
105
105
|
schedule_cron?: string;
|
|
106
106
|
schedule_timezone?: string;
|
|
107
|
+
settings?: {
|
|
108
|
+
on_step_failure?: 'continue' | 'stop';
|
|
109
|
+
notify_on_failure?: boolean;
|
|
110
|
+
};
|
|
107
111
|
}): Promise<Flow>;
|
|
108
112
|
/**
|
|
109
113
|
* Apply atomic structural operations (edit/add/remove nodes and edges) to an existing flow.
|
|
@@ -136,10 +140,24 @@ export declare class NodeStormClient {
|
|
|
136
140
|
*/
|
|
137
141
|
listFlowRuns(options?: {
|
|
138
142
|
flowId?: number;
|
|
139
|
-
status?: 'success' | 'error' | 'pending' | 'running';
|
|
143
|
+
status?: 'success' | 'partial' | 'error' | 'pending' | 'running';
|
|
140
144
|
page?: number;
|
|
141
145
|
perPage?: number;
|
|
142
146
|
}): Promise<FlowRunListResponse>;
|
|
147
|
+
/**
|
|
148
|
+
* List documentation pages, optionally filtered by category
|
|
149
|
+
*/
|
|
150
|
+
listDocs(options?: {
|
|
151
|
+
category?: string;
|
|
152
|
+
}): Promise<DocListResponse>;
|
|
153
|
+
/**
|
|
154
|
+
* Get a documentation page by slug, including its full markdown body
|
|
155
|
+
*/
|
|
156
|
+
getDoc(slug: string): Promise<DocPageResponse>;
|
|
157
|
+
/**
|
|
158
|
+
* Search documentation using semantic search
|
|
159
|
+
*/
|
|
160
|
+
searchDocs(query: string): Promise<DocSearchResponse>;
|
|
143
161
|
/**
|
|
144
162
|
* Search chat message history using semantic search
|
|
145
163
|
*/
|
|
@@ -151,6 +169,42 @@ export declare class NodeStormClient {
|
|
|
151
169
|
from?: string;
|
|
152
170
|
to?: string;
|
|
153
171
|
}): Promise<ChatSearchResponse>;
|
|
172
|
+
/**
|
|
173
|
+
* Execute a single node directly, outside any flow. Capped at ~90s server-side;
|
|
174
|
+
* long-running work belongs in a real flow.
|
|
175
|
+
*/
|
|
176
|
+
executeNode(params: {
|
|
177
|
+
node_type: string;
|
|
178
|
+
config?: object;
|
|
179
|
+
input?: unknown;
|
|
180
|
+
}): Promise<ExecuteNodeResponse>;
|
|
181
|
+
}
|
|
182
|
+
export interface DocListItem {
|
|
183
|
+
slug: string;
|
|
184
|
+
title: string;
|
|
185
|
+
category: string;
|
|
186
|
+
visibility: string;
|
|
187
|
+
description: string | null;
|
|
188
|
+
}
|
|
189
|
+
export interface DocListResponse {
|
|
190
|
+
pages: DocListItem[];
|
|
191
|
+
}
|
|
192
|
+
export interface DocPage extends DocListItem {
|
|
193
|
+
body: string;
|
|
194
|
+
updated_at: string;
|
|
195
|
+
}
|
|
196
|
+
export interface DocPageResponse {
|
|
197
|
+
page: DocPage;
|
|
198
|
+
}
|
|
199
|
+
export interface DocSearchResult {
|
|
200
|
+
slug: string;
|
|
201
|
+
title: string;
|
|
202
|
+
category: string;
|
|
203
|
+
chunk_content: string;
|
|
204
|
+
relevance: number;
|
|
205
|
+
}
|
|
206
|
+
export interface DocSearchResponse {
|
|
207
|
+
results: DocSearchResult[];
|
|
154
208
|
}
|
|
155
209
|
export interface ChatSearchResult {
|
|
156
210
|
content: string;
|
|
@@ -162,6 +216,12 @@ export interface ChatSearchResult {
|
|
|
162
216
|
export interface ChatSearchResponse {
|
|
163
217
|
results: ChatSearchResult[];
|
|
164
218
|
}
|
|
219
|
+
export interface ExecuteNodeResponse {
|
|
220
|
+
success: boolean;
|
|
221
|
+
node_type: string;
|
|
222
|
+
/** The worker's result (NodeResult shape: { data, data_type, success, error?, metadata? }) */
|
|
223
|
+
output: unknown;
|
|
224
|
+
}
|
|
165
225
|
export declare class NodeStormApiError extends Error {
|
|
166
226
|
code: string;
|
|
167
227
|
statusCode: number;
|
package/dist/client.js
CHANGED
|
@@ -110,6 +110,31 @@ export class NodeStormClient {
|
|
|
110
110
|
const path = queryString ? `/flow-runs?${queryString}` : '/flow-runs';
|
|
111
111
|
return this.request(path);
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* List documentation pages, optionally filtered by category
|
|
115
|
+
*/
|
|
116
|
+
async listDocs(options = {}) {
|
|
117
|
+
const params = new URLSearchParams();
|
|
118
|
+
if (options.category)
|
|
119
|
+
params.append('category', options.category);
|
|
120
|
+
const queryString = params.toString();
|
|
121
|
+
const path = queryString ? `/docs?${queryString}` : '/docs';
|
|
122
|
+
return this.request(path);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get a documentation page by slug, including its full markdown body
|
|
126
|
+
*/
|
|
127
|
+
async getDoc(slug) {
|
|
128
|
+
return this.request(`/docs/${encodeURIComponent(slug)}`);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Search documentation using semantic search
|
|
132
|
+
*/
|
|
133
|
+
async searchDocs(query) {
|
|
134
|
+
const params = new URLSearchParams();
|
|
135
|
+
params.append('q', query);
|
|
136
|
+
return this.request(`/docs/search?${params.toString()}`);
|
|
137
|
+
}
|
|
113
138
|
/**
|
|
114
139
|
* Search chat message history using semantic search
|
|
115
140
|
*/
|
|
@@ -128,6 +153,16 @@ export class NodeStormClient {
|
|
|
128
153
|
params.append('to', options.to);
|
|
129
154
|
return this.request(`/chat/search?${params.toString()}`);
|
|
130
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Execute a single node directly, outside any flow. Capped at ~90s server-side;
|
|
158
|
+
* long-running work belongs in a real flow.
|
|
159
|
+
*/
|
|
160
|
+
async executeNode(params) {
|
|
161
|
+
return this.request('/nodes/execute', {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
body: JSON.stringify(params),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
131
166
|
}
|
|
132
167
|
export class NodeStormApiError extends Error {
|
|
133
168
|
code;
|
package/dist/index.js
CHANGED
|
@@ -8,17 +8,15 @@
|
|
|
8
8
|
* - NODESTORM_API_KEY (required): API key for authentication
|
|
9
9
|
* - NODESTORM_API_URL (optional): API base URL (default: https://nodestorm.ai)
|
|
10
10
|
*/
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
11
12
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
13
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
14
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
14
15
|
import { NodeStormClient, NodeStormApiError } from './client.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// Get the directory of this file
|
|
20
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
-
const __dirname = dirname(__filename);
|
|
16
|
+
import { DOCS_TOOL_DEFINITIONS } from './tools/docs.js';
|
|
17
|
+
import { EXECUTE_NODE_DESCRIPTION, SERVER_INSTRUCTIONS } from './instructions.js';
|
|
18
|
+
// One version string: package.json's. Before 0.1.2 this was hard-coded and drifted.
|
|
19
|
+
const { version: VERSION } = createRequire(import.meta.url)('../package.json');
|
|
22
20
|
// Read environment variables
|
|
23
21
|
const apiKey = process.env.NODESTORM_API_KEY;
|
|
24
22
|
const apiUrl = process.env.NODESTORM_API_URL || 'https://nodestorm.ai';
|
|
@@ -28,25 +26,29 @@ if (!apiKey) {
|
|
|
28
26
|
}
|
|
29
27
|
// Initialize client
|
|
30
28
|
const client = new NodeStormClient(apiKey, apiUrl);
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
29
|
+
// The bundled workflow guide was retired in favor of the live docs (see the docs tools).
|
|
30
|
+
// This pointer keeps the resource URI working for existing clients.
|
|
31
|
+
const WORKFLOW_GUIDE_POINTER = [
|
|
32
|
+
'# Workflow Guide',
|
|
33
|
+
'',
|
|
34
|
+
'The workflow guide moved into the live NodeStorm docs.',
|
|
35
|
+
'Use the `list_docs` / `get_doc` / `search_docs` tools instead of this resource.',
|
|
36
|
+
'',
|
|
37
|
+
"Start with `get_doc('workflow-execution')` for the execution model, and",
|
|
38
|
+
"`list_docs({ category: 'node' })` for the node catalog (slugs are kebab-case,",
|
|
39
|
+
'e.g. `node-prompt`, `node-http-request`).',
|
|
40
|
+
].join('\n');
|
|
41
41
|
// Create MCP server
|
|
42
42
|
const server = new Server({
|
|
43
43
|
name: 'nodestorm-mcp',
|
|
44
|
-
version:
|
|
44
|
+
version: VERSION,
|
|
45
45
|
}, {
|
|
46
46
|
capabilities: {
|
|
47
47
|
tools: {},
|
|
48
48
|
resources: {},
|
|
49
49
|
},
|
|
50
|
+
// 099.9: shown to the model at session start (URL → tool map, read docs first).
|
|
51
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
50
52
|
});
|
|
51
53
|
// Tool definitions
|
|
52
54
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -97,8 +99,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
97
99
|
},
|
|
98
100
|
{
|
|
99
101
|
name: 'update_flow',
|
|
100
|
-
description: 'Update flow METADATA: name, description, schedule_
|
|
101
|
-
'
|
|
102
|
+
description: 'Update flow METADATA: name, description, schedule_*, and the flow settings ' +
|
|
103
|
+
'(settings.on_step_failure: "continue" | "stop"; settings.notify_on_failure: boolean). ' +
|
|
104
|
+
'Does NOT modify nodes or edges. For any structural change use update_flow_structure.',
|
|
102
105
|
inputSchema: {
|
|
103
106
|
type: 'object',
|
|
104
107
|
properties: {
|
|
@@ -114,7 +117,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
114
117
|
description: { type: 'string' },
|
|
115
118
|
schedule_enabled: { type: 'boolean' },
|
|
116
119
|
schedule_cron: { type: 'string' },
|
|
117
|
-
schedule_timezone: { type: 'string' },
|
|
120
|
+
schedule_timezone: { type: 'string', description: 'IANA name, e.g. America/New_York' },
|
|
121
|
+
settings: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
description: 'Flow settings; keys sent are merged into the stored ones',
|
|
124
|
+
properties: {
|
|
125
|
+
on_step_failure: { type: 'string', enum: ['continue', 'stop'] },
|
|
126
|
+
notify_on_failure: { type: 'boolean' },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
118
129
|
},
|
|
119
130
|
},
|
|
120
131
|
},
|
|
@@ -177,7 +188,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
177
188
|
description: 'Node payload. Server generates id if not provided.',
|
|
178
189
|
properties: {
|
|
179
190
|
id: { type: 'string' },
|
|
180
|
-
type: {
|
|
191
|
+
type: {
|
|
192
|
+
type: 'string',
|
|
193
|
+
description: "Node type — see list_docs({category: 'node'}) for the catalog and get_doc for each node's schema",
|
|
194
|
+
},
|
|
181
195
|
data: { type: 'object' },
|
|
182
196
|
position: {
|
|
183
197
|
type: 'object',
|
|
@@ -265,20 +279,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
265
279
|
},
|
|
266
280
|
},
|
|
267
281
|
},
|
|
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
282
|
{
|
|
283
283
|
name: 'get_flow_run',
|
|
284
284
|
description: 'Get details of a specific flow run by ID, including execution results and node outputs',
|
|
@@ -341,7 +341,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
341
341
|
},
|
|
342
342
|
status: {
|
|
343
343
|
type: 'string',
|
|
344
|
-
enum: ['success', 'error', 'pending', 'running'],
|
|
344
|
+
enum: ['success', 'partial', 'error', 'pending', 'running'],
|
|
345
345
|
description: 'Filter by status',
|
|
346
346
|
},
|
|
347
347
|
page: {
|
|
@@ -355,6 +355,28 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
355
355
|
},
|
|
356
356
|
},
|
|
357
357
|
},
|
|
358
|
+
{
|
|
359
|
+
name: 'execute_node',
|
|
360
|
+
description: EXECUTE_NODE_DESCRIPTION,
|
|
361
|
+
inputSchema: {
|
|
362
|
+
type: 'object',
|
|
363
|
+
properties: {
|
|
364
|
+
node_type: {
|
|
365
|
+
type: 'string',
|
|
366
|
+
description: "Node type to run — see list_docs({category: 'node'}) for the catalog",
|
|
367
|
+
},
|
|
368
|
+
config: {
|
|
369
|
+
type: 'object',
|
|
370
|
+
description: "Node configuration, same shape as the node's `data` in a flow",
|
|
371
|
+
},
|
|
372
|
+
input: {
|
|
373
|
+
description: 'Optional input passed as the parent node result (any JSON value)',
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
required: ['node_type'],
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
...DOCS_TOOL_DEFINITIONS,
|
|
358
380
|
],
|
|
359
381
|
};
|
|
360
382
|
});
|
|
@@ -435,18 +457,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
435
457
|
],
|
|
436
458
|
};
|
|
437
459
|
}
|
|
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
460
|
case 'get_flow_run': {
|
|
451
461
|
const { id } = args;
|
|
452
462
|
const result = await client.getFlowRun(id);
|
|
@@ -488,6 +498,54 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
488
498
|
],
|
|
489
499
|
};
|
|
490
500
|
}
|
|
501
|
+
case 'execute_node': {
|
|
502
|
+
const { node_type, config, input } = args;
|
|
503
|
+
const result = await client.executeNode({ node_type, config, input });
|
|
504
|
+
return {
|
|
505
|
+
content: [
|
|
506
|
+
{
|
|
507
|
+
type: 'text',
|
|
508
|
+
text: JSON.stringify(result, null, 2),
|
|
509
|
+
},
|
|
510
|
+
],
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
case 'list_docs': {
|
|
514
|
+
const { category } = args;
|
|
515
|
+
const result = await client.listDocs({ category });
|
|
516
|
+
return {
|
|
517
|
+
content: [
|
|
518
|
+
{
|
|
519
|
+
type: 'text',
|
|
520
|
+
text: JSON.stringify(result, null, 2),
|
|
521
|
+
},
|
|
522
|
+
],
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
case 'get_doc': {
|
|
526
|
+
const { slug } = args;
|
|
527
|
+
const result = await client.getDoc(slug);
|
|
528
|
+
return {
|
|
529
|
+
content: [
|
|
530
|
+
{
|
|
531
|
+
type: 'text',
|
|
532
|
+
text: JSON.stringify(result, null, 2),
|
|
533
|
+
},
|
|
534
|
+
],
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
case 'search_docs': {
|
|
538
|
+
const { query } = args;
|
|
539
|
+
const result = await client.searchDocs(query);
|
|
540
|
+
return {
|
|
541
|
+
content: [
|
|
542
|
+
{
|
|
543
|
+
type: 'text',
|
|
544
|
+
text: JSON.stringify(result, null, 2),
|
|
545
|
+
},
|
|
546
|
+
],
|
|
547
|
+
};
|
|
548
|
+
}
|
|
491
549
|
default:
|
|
492
550
|
return {
|
|
493
551
|
content: [
|
|
@@ -537,7 +595,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
537
595
|
{
|
|
538
596
|
uri: 'nodestorm://workflow-guide',
|
|
539
597
|
name: 'Workflow Builder Guide',
|
|
540
|
-
description: '
|
|
598
|
+
description: 'Pointer to the live NodeStorm docs — use the list_docs / get_doc / search_docs tools instead',
|
|
541
599
|
mimeType: 'text/markdown',
|
|
542
600
|
},
|
|
543
601
|
],
|
|
@@ -547,13 +605,12 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
547
605
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
548
606
|
const { uri } = request.params;
|
|
549
607
|
if (uri === 'nodestorm://workflow-guide') {
|
|
550
|
-
const guide = loadWorkflowGuide();
|
|
551
608
|
return {
|
|
552
609
|
contents: [
|
|
553
610
|
{
|
|
554
611
|
uri,
|
|
555
612
|
mimeType: 'text/markdown',
|
|
556
|
-
text:
|
|
613
|
+
text: WORKFLOW_GUIDE_POINTER,
|
|
557
614
|
},
|
|
558
615
|
],
|
|
559
616
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the server tells a client at connect (099.9). Shown to the model at
|
|
3
|
+
* session start, so it reaches the right tool first try instead of trying
|
|
4
|
+
* curl or a hosting dashboard on a nodestorm.ai link. Kept here, outside
|
|
5
|
+
* index.ts, so tests can import it without starting the server.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SERVER_INSTRUCTIONS = "NodeStorm runs recurring reports and tasks as flows of nodes (LLM prompts, search, scraping, files, tables, email, integrations). You act on one user's account through their access token.\n\nLinks from nodestorm.ai map to tools:\n- /files/N: execute_node {node_type: \"file\", config: {mode: \"read\", file_id: N}} returns the file's text.\n- /flows/N: get_flow {id: N}. /flows/N/runs: list_flow_runs {flow_id: N}. /flow-runs/N: get_flow_run {id: N}.\n- /help/SLUG: get_doc {slug: SLUG}.\nThere is no HTTP download for files or flows; do not fetch nodestorm.ai URLs directly.\n\nBefore execute_node or update_flow_structure, read the node's doc with get_doc (list_docs {category: \"node\"} lists them): the config keys come from there. Each execute_node call counts as one node execution against the user's monthly quota and is capped at about 90 seconds. Nodes that send or write (email, integrations, file create or update) take effect at once; confirm with the user first.";
|
|
8
|
+
export declare const EXECUTE_NODE_DESCRIPTION: string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the server tells a client at connect (099.9). Shown to the model at
|
|
3
|
+
* session start, so it reaches the right tool first try instead of trying
|
|
4
|
+
* curl or a hosting dashboard on a nodestorm.ai link. Kept here, outside
|
|
5
|
+
* index.ts, so tests can import it without starting the server.
|
|
6
|
+
*/
|
|
7
|
+
export const SERVER_INSTRUCTIONS = `NodeStorm runs recurring reports and tasks as flows of nodes (LLM prompts, search, scraping, files, tables, email, integrations). You act on one user's account through their access token.
|
|
8
|
+
|
|
9
|
+
Links from nodestorm.ai map to tools:
|
|
10
|
+
- /files/N: execute_node {node_type: "file", config: {mode: "read", file_id: N}} returns the file's text.
|
|
11
|
+
- /flows/N: get_flow {id: N}. /flows/N/runs: list_flow_runs {flow_id: N}. /flow-runs/N: get_flow_run {id: N}.
|
|
12
|
+
- /help/SLUG: get_doc {slug: SLUG}.
|
|
13
|
+
There is no HTTP download for files or flows; do not fetch nodestorm.ai URLs directly.
|
|
14
|
+
|
|
15
|
+
Before execute_node or update_flow_structure, read the node's doc with get_doc (list_docs {category: "node"} lists them): the config keys come from there. Each execute_node call counts as one node execution against the user's monthly quota and is capped at about 90 seconds. Nodes that send or write (email, integrations, file create or update) take effect at once; confirm with the user first.`;
|
|
16
|
+
export const EXECUTE_NODE_DESCRIPTION = 'Run a single NodeStorm node directly, without creating a flow — any node type. ' +
|
|
17
|
+
"config is identical to the node's `data` in a flow (see the node's doc via get_doc); " +
|
|
18
|
+
'optional `input` is passed to the node as its parent result. ' +
|
|
19
|
+
'Recipes: read a file (a nodestorm.ai/files/N link): {node_type: "file", config: {mode: "read", file_id: N}}. ' +
|
|
20
|
+
'List the files in a folder: {node_type: "folder", config: {folder_id: ID}}, with folder_id "top" for files in no folder. ' +
|
|
21
|
+
'Each call counts as one node execution against the monthly quota. ' +
|
|
22
|
+
'Runs that send or write take effect immediately; confirm with your user first. ' +
|
|
23
|
+
'Capped at ~90 seconds — long-running work belongs in a real flow instead.';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs tool definitions for the docs endpoints
|
|
3
|
+
* NodeStorm product documentation — node behavior, workflow mechanics, guides.
|
|
4
|
+
* These wrap the backend's v1 docs endpoints via NodeStormClient.
|
|
5
|
+
*/
|
|
6
|
+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
export declare const DOCS_TOOL_DEFINITIONS: Tool[];
|