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 ADDED
@@ -0,0 +1,151 @@
1
+ # nodestorm-mcp
2
+
3
+ MCP (Model Context Protocol) server for NodeStorm workflow automation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npx nodestorm-mcp
9
+ ```
10
+
11
+ Or install globally:
12
+
13
+ ```bash
14
+ npm install -g nodestorm-mcp
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ ### Claude Code / Claude Desktop
20
+
21
+ Add to your Claude configuration file:
22
+
23
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
24
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "nodestorm": {
30
+ "command": "npx",
31
+ "args": ["-y", "nodestorm-mcp"],
32
+ "env": {
33
+ "NODESTORM_API_KEY": "ns_your_access_token_here"
34
+ }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ### Environment Variables
41
+
42
+ | Variable | Required | Description |
43
+ |----------|----------|-------------|
44
+ | `NODESTORM_API_KEY` | Yes | Access token from NodeStorm Settings |
45
+ | `NODESTORM_API_URL` | No | API base URL (default: `https://nodestorm.ai`) |
46
+
47
+ ## Getting Your Access Token
48
+
49
+ 1. Log in to [NodeStorm](https://nodestorm.ai)
50
+ 2. Go to Settings → Access Tokens
51
+ 3. Click "Generate New Token"
52
+ 4. Copy the token (you won't see it again!)
53
+
54
+ Note: Access tokens require an active NodeStorm subscription.
55
+
56
+ ## Available Tools
57
+
58
+ ### create_flow
59
+ Create a new workflow from a natural language description.
60
+
61
+ ```
62
+ Input: { prompt: "Scrape 5 URLs and summarize each page" }
63
+ Output: { id, name, nodes, edges, url }
64
+ ```
65
+
66
+ ### get_flow
67
+ Retrieve an existing workflow by ID.
68
+
69
+ ```
70
+ Input: { id: "abc123" }
71
+ Output: { id, name, nodes, edges, url, updated_at }
72
+ ```
73
+
74
+ ### update_flow
75
+ Modify an existing workflow.
76
+
77
+ ```
78
+ Input: { id: "abc123", flow: { name, nodes, edges } }
79
+ Output: { id, name, nodes, edges, url, updated_at }
80
+ ```
81
+
82
+ ### delete_flow
83
+ Delete a workflow.
84
+
85
+ ```
86
+ Input: { id: "abc123" }
87
+ Output: { success: true }
88
+ ```
89
+
90
+ ### list_flows
91
+ List all your workflows.
92
+
93
+ ```
94
+ Input: { limit: 20, offset: 0 }
95
+ Output: { flows: [...], total }
96
+ ```
97
+
98
+ ### validate_flow
99
+ Check if a workflow is valid before saving.
100
+
101
+ ```
102
+ Input: { flow: { nodes, edges } }
103
+ Output: { valid: true } or { valid: false, errors: [...] }
104
+ ```
105
+
106
+ ### describe_nodes
107
+ Get node type definitions (no API call).
108
+
109
+ ```
110
+ Input: { types: ["prompt", "list"] } // optional filter
111
+ Output: { nodes: [{ type, description, category, config_schema, handles }] }
112
+ ```
113
+
114
+ ## Resources
115
+
116
+ ### workflow-guide
117
+ Complete guide for building NodeStorm workflows. Read this resource to understand:
118
+ - Node types and their configurations
119
+ - Edge connection rules
120
+ - Iteration patterns
121
+ - Validation requirements
122
+
123
+ ## Example Usage
124
+
125
+ ```
126
+ User: Create a flow that scrapes competitor websites and analyzes pricing
127
+
128
+ Claude: [Uses create_flow with prompt]
129
+ Created "Competitor Pricing Analysis"
130
+ → https://nodestorm.ai/flows/abc123
131
+
132
+ User: Add 3 more URLs to the list
133
+
134
+ Claude: [Uses get_flow, modifies nodes, uses update_flow]
135
+ Updated flow with 3 additional URLs.
136
+ ```
137
+
138
+ ## Error Codes
139
+
140
+ | Code | Meaning |
141
+ |------|---------|
142
+ | `unauthorized` | Invalid or missing API key |
143
+ | `forbidden` | Not authorized for this resource |
144
+ | `not_found` | Resource doesn't exist |
145
+ | `invalid` | Validation failed |
146
+ | `rate_limited` | Too many requests |
147
+ | `server_error` | Server error |
148
+
149
+ ## License
150
+
151
+ MIT
@@ -0,0 +1,170 @@
1
+ /**
2
+ * NodeStorm API Client
3
+ * Handles communication with the NodeStorm backend
4
+ */
5
+ export interface Flow {
6
+ id: string;
7
+ name: string;
8
+ nodes: any[];
9
+ edges: any[];
10
+ url: string;
11
+ updated_at?: string;
12
+ }
13
+ export interface FlowListItem {
14
+ id: string;
15
+ name: string;
16
+ url: string;
17
+ updated_at: string;
18
+ }
19
+ export interface FlowListResponse {
20
+ flows: FlowListItem[];
21
+ total: number;
22
+ }
23
+ export interface ValidationResult {
24
+ valid: boolean;
25
+ errors?: string[];
26
+ }
27
+ export interface FlowRun {
28
+ id: number;
29
+ flow_id: number;
30
+ flow_title: string;
31
+ user_id?: number;
32
+ status: 'success' | 'error' | 'pending' | 'running';
33
+ node_count: number;
34
+ duration_seconds: number | null;
35
+ result?: {
36
+ node_executions?: Array<{
37
+ node_id: string;
38
+ node_type: string;
39
+ result: any;
40
+ success?: boolean;
41
+ error?: string;
42
+ started_at?: string;
43
+ completed_at?: string;
44
+ }>;
45
+ };
46
+ created_at: string;
47
+ updated_at: string;
48
+ }
49
+ export interface FlowRunListItem {
50
+ id: number;
51
+ flow_id: number;
52
+ flow_title: string;
53
+ status: 'success' | 'error' | 'pending' | 'running';
54
+ node_count: number;
55
+ duration_seconds: number | null;
56
+ created_at: string;
57
+ updated_at: string;
58
+ }
59
+ export interface FlowRunListResponse {
60
+ flow_runs: FlowRunListItem[];
61
+ pagination: {
62
+ current_page: number;
63
+ per_page: number;
64
+ total_count: number;
65
+ total_pages: number;
66
+ };
67
+ }
68
+ export interface CreateFlowResponse extends Flow {
69
+ valid: boolean;
70
+ errors?: string[];
71
+ }
72
+ export interface ApiError {
73
+ error: string;
74
+ code: 'unauthorized' | 'forbidden' | 'not_found' | 'invalid' | 'rate_limited' | 'server_error';
75
+ errors?: string[];
76
+ }
77
+ export declare class NodeStormClient {
78
+ private apiKey;
79
+ private baseUrl;
80
+ constructor(apiKey: string, baseUrl?: string);
81
+ private request;
82
+ /**
83
+ * Create a new flow from a prompt or directly from JSON (admin-only).
84
+ */
85
+ createFlow(params: {
86
+ prompt?: string;
87
+ name?: string;
88
+ workflow_json?: {
89
+ nodes: unknown[];
90
+ edges: unknown[];
91
+ };
92
+ description?: string;
93
+ }): Promise<CreateFlowResponse>;
94
+ /**
95
+ * Get a flow by ID
96
+ */
97
+ getFlow(id: string): Promise<Flow>;
98
+ /**
99
+ * Update flow metadata. For structural changes (nodes/edges), use updateFlowStructure.
100
+ */
101
+ updateFlow(id: string, flow: {
102
+ name?: string;
103
+ description?: string;
104
+ schedule_enabled?: boolean;
105
+ schedule_cron?: string;
106
+ schedule_timezone?: string;
107
+ }): Promise<Flow>;
108
+ /**
109
+ * Apply atomic structural operations (edit/add/remove nodes and edges) to an existing flow.
110
+ * All ops apply in one transaction; the whole batch rolls back if any op fails or the final
111
+ * state is invalid. See update_flow_structure tool docs for op shapes.
112
+ */
113
+ updateFlowStructure(flowId: string, operations: any[]): Promise<any>;
114
+ /**
115
+ * Delete a flow
116
+ */
117
+ deleteFlow(id: string): Promise<{
118
+ success: boolean;
119
+ }>;
120
+ /**
121
+ * List or search user's flows. Pass `query` to filter by title/description.
122
+ * scope='user' (default) searches user's own flows; scope='public' searches
123
+ * published community/official entries.
124
+ */
125
+ listFlows(opts?: {
126
+ query?: string;
127
+ scope?: string;
128
+ limit?: number;
129
+ }): Promise<FlowListResponse>;
130
+ /**
131
+ * Get a flow run by ID
132
+ */
133
+ getFlowRun(id: string): Promise<FlowRun>;
134
+ /**
135
+ * List flow runs with optional filters
136
+ */
137
+ listFlowRuns(options?: {
138
+ flowId?: number;
139
+ status?: 'success' | 'error' | 'pending' | 'running';
140
+ page?: number;
141
+ perPage?: number;
142
+ }): Promise<FlowRunListResponse>;
143
+ /**
144
+ * Search chat message history using semantic search
145
+ */
146
+ searchChatHistory(options: {
147
+ query: string;
148
+ limit?: number;
149
+ role?: 'user' | 'assistant';
150
+ source?: 'chat' | 'heartbeat' | 'telegram';
151
+ from?: string;
152
+ to?: string;
153
+ }): Promise<ChatSearchResponse>;
154
+ }
155
+ export interface ChatSearchResult {
156
+ content: string;
157
+ role: string;
158
+ source: string;
159
+ relevance: number;
160
+ created_at: string;
161
+ }
162
+ export interface ChatSearchResponse {
163
+ results: ChatSearchResult[];
164
+ }
165
+ export declare class NodeStormApiError extends Error {
166
+ code: string;
167
+ statusCode: number;
168
+ errors?: string[];
169
+ constructor(message: string, code: string, statusCode: number, errors?: string[]);
170
+ }
package/dist/client.js ADDED
@@ -0,0 +1,143 @@
1
+ /**
2
+ * NodeStorm API Client
3
+ * Handles communication with the NodeStorm backend
4
+ */
5
+ export class NodeStormClient {
6
+ apiKey;
7
+ baseUrl;
8
+ constructor(apiKey, baseUrl = 'https://nodestorm.ai') {
9
+ this.apiKey = apiKey;
10
+ this.baseUrl = baseUrl;
11
+ }
12
+ async request(path, options = {}) {
13
+ const url = `${this.baseUrl}/api/v1${path}`;
14
+ const response = await fetch(url, {
15
+ ...options,
16
+ headers: {
17
+ 'Authorization': `Bearer ${this.apiKey}`,
18
+ 'Content-Type': 'application/json',
19
+ 'Accept': 'application/json',
20
+ ...options.headers,
21
+ },
22
+ });
23
+ const data = await response.json();
24
+ if (!response.ok) {
25
+ const error = data;
26
+ throw new NodeStormApiError(error.error, error.code, response.status, error.errors);
27
+ }
28
+ return data;
29
+ }
30
+ /**
31
+ * Create a new flow from a prompt or directly from JSON (admin-only).
32
+ */
33
+ async createFlow(params) {
34
+ return this.request('/flows', {
35
+ method: 'POST',
36
+ body: JSON.stringify(params),
37
+ });
38
+ }
39
+ /**
40
+ * Get a flow by ID
41
+ */
42
+ async getFlow(id) {
43
+ return this.request(`/flows/${id}`);
44
+ }
45
+ /**
46
+ * Update flow metadata. For structural changes (nodes/edges), use updateFlowStructure.
47
+ */
48
+ async updateFlow(id, flow) {
49
+ return this.request(`/flows/${id}`, {
50
+ method: 'PUT',
51
+ body: JSON.stringify({ flow }),
52
+ });
53
+ }
54
+ /**
55
+ * Apply atomic structural operations (edit/add/remove nodes and edges) to an existing flow.
56
+ * All ops apply in one transaction; the whole batch rolls back if any op fails or the final
57
+ * state is invalid. See update_flow_structure tool docs for op shapes.
58
+ */
59
+ async updateFlowStructure(flowId, operations) {
60
+ return this.request(`/flows/${flowId}/structure`, {
61
+ method: 'PATCH',
62
+ body: JSON.stringify({ operations }),
63
+ });
64
+ }
65
+ /**
66
+ * Delete a flow
67
+ */
68
+ async deleteFlow(id) {
69
+ return this.request(`/flows/${id}`, {
70
+ method: 'DELETE',
71
+ });
72
+ }
73
+ /**
74
+ * List or search user's flows. Pass `query` to filter by title/description.
75
+ * scope='user' (default) searches user's own flows; scope='public' searches
76
+ * published community/official entries.
77
+ */
78
+ async listFlows(opts = {}) {
79
+ const { query, scope = 'user', limit = 20 } = opts;
80
+ if (query || scope === 'public') {
81
+ const params = new URLSearchParams();
82
+ if (query)
83
+ params.set('query', query);
84
+ params.set('scope', scope);
85
+ params.set('limit', String(limit));
86
+ return this.request(`/flows/search?${params.toString()}`);
87
+ }
88
+ return this.request(`/flows?limit=${limit}`);
89
+ }
90
+ /**
91
+ * Get a flow run by ID
92
+ */
93
+ async getFlowRun(id) {
94
+ return this.request(`/flow-runs/${id}`);
95
+ }
96
+ /**
97
+ * List flow runs with optional filters
98
+ */
99
+ async listFlowRuns(options = {}) {
100
+ const params = new URLSearchParams();
101
+ if (options.flowId)
102
+ params.append('flow_id', options.flowId.toString());
103
+ if (options.status)
104
+ params.append('status', options.status);
105
+ if (options.page)
106
+ params.append('page', options.page.toString());
107
+ if (options.perPage)
108
+ params.append('per_page', options.perPage.toString());
109
+ const queryString = params.toString();
110
+ const path = queryString ? `/flow-runs?${queryString}` : '/flow-runs';
111
+ return this.request(path);
112
+ }
113
+ /**
114
+ * Search chat message history using semantic search
115
+ */
116
+ async searchChatHistory(options) {
117
+ const params = new URLSearchParams();
118
+ params.append('q', options.query);
119
+ if (options.limit)
120
+ params.append('limit', options.limit.toString());
121
+ if (options.role)
122
+ params.append('role', options.role);
123
+ if (options.source)
124
+ params.append('source', options.source);
125
+ if (options.from)
126
+ params.append('from', options.from);
127
+ if (options.to)
128
+ params.append('to', options.to);
129
+ return this.request(`/chat/search?${params.toString()}`);
130
+ }
131
+ }
132
+ export class NodeStormApiError extends Error {
133
+ code;
134
+ statusCode;
135
+ errors;
136
+ constructor(message, code, statusCode, errors) {
137
+ super(message);
138
+ this.name = 'NodeStormApiError';
139
+ this.code = code;
140
+ this.statusCode = statusCode;
141
+ this.errors = errors;
142
+ }
143
+ }
@@ -0,0 +1,11 @@
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
+ export {};