@pagelines/n8n-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/src/tools.ts ADDED
@@ -0,0 +1,273 @@
1
+ /**
2
+ * MCP Tool Definitions
3
+ * Minimal, focused toolset for workflow management
4
+ */
5
+
6
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
7
+
8
+ export const tools: Tool[] = [
9
+ // ─────────────────────────────────────────────────────────────
10
+ // Workflow Operations
11
+ // ─────────────────────────────────────────────────────────────
12
+ {
13
+ name: 'workflow_list',
14
+ description: 'List all workflows. Returns id, name, active status.',
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ active: {
19
+ type: 'boolean',
20
+ description: 'Filter by active status',
21
+ },
22
+ limit: {
23
+ type: 'number',
24
+ description: 'Max results (default 100)',
25
+ },
26
+ },
27
+ },
28
+ },
29
+
30
+ {
31
+ name: 'workflow_get',
32
+ description: 'Get a workflow by ID. Returns full workflow with nodes, connections, settings.',
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {
36
+ id: {
37
+ type: 'string',
38
+ description: 'Workflow ID',
39
+ },
40
+ },
41
+ required: ['id'],
42
+ },
43
+ },
44
+
45
+ {
46
+ name: 'workflow_create',
47
+ description: 'Create a new workflow. Returns the created workflow.',
48
+ inputSchema: {
49
+ type: 'object',
50
+ properties: {
51
+ name: {
52
+ type: 'string',
53
+ description: 'Workflow name (use snake_case)',
54
+ },
55
+ nodes: {
56
+ type: 'array',
57
+ description: 'Array of node definitions',
58
+ items: {
59
+ type: 'object',
60
+ properties: {
61
+ name: { type: 'string' },
62
+ type: { type: 'string' },
63
+ typeVersion: { type: 'number' },
64
+ position: {
65
+ type: 'array',
66
+ items: { type: 'number' },
67
+ minItems: 2,
68
+ maxItems: 2,
69
+ },
70
+ parameters: { type: 'object' },
71
+ credentials: { type: 'object' },
72
+ },
73
+ required: ['name', 'type', 'typeVersion', 'position', 'parameters'],
74
+ },
75
+ },
76
+ connections: {
77
+ type: 'object',
78
+ description: 'Connection map: { "NodeName": { "main": [[{ "node": "TargetNode", "type": "main", "index": 0 }]] } }',
79
+ },
80
+ settings: {
81
+ type: 'object',
82
+ description: 'Workflow settings',
83
+ },
84
+ },
85
+ required: ['name', 'nodes', 'connections'],
86
+ },
87
+ },
88
+
89
+ {
90
+ name: 'workflow_update',
91
+ description: `Update a workflow using patch operations. ALWAYS preserves existing data.
92
+
93
+ Operations:
94
+ - addNode: Add a new node
95
+ - removeNode: Remove a node and its connections
96
+ - updateNode: Update node properties (INCLUDE ALL existing parameters to preserve them)
97
+ - addConnection: Connect two nodes
98
+ - removeConnection: Disconnect two nodes
99
+ - updateSettings: Update workflow settings
100
+ - updateName: Rename the workflow
101
+
102
+ Example: { "operations": [{ "type": "updateNode", "nodeName": "my_node", "properties": { "parameters": { ...allExistingParams, "newParam": "value" } } }] }`,
103
+ inputSchema: {
104
+ type: 'object',
105
+ properties: {
106
+ id: {
107
+ type: 'string',
108
+ description: 'Workflow ID',
109
+ },
110
+ operations: {
111
+ type: 'array',
112
+ description: 'Array of patch operations',
113
+ items: {
114
+ type: 'object',
115
+ properties: {
116
+ type: {
117
+ type: 'string',
118
+ enum: ['addNode', 'removeNode', 'updateNode', 'addConnection', 'removeConnection', 'updateSettings', 'updateName'],
119
+ },
120
+ // For addNode
121
+ node: { type: 'object' },
122
+ // For removeNode, updateNode
123
+ nodeName: { type: 'string' },
124
+ // For updateNode
125
+ properties: { type: 'object' },
126
+ // For connections
127
+ from: { type: 'string' },
128
+ to: { type: 'string' },
129
+ fromOutput: { type: 'number' },
130
+ toInput: { type: 'number' },
131
+ outputType: { type: 'string' },
132
+ inputType: { type: 'string' },
133
+ // For updateSettings
134
+ settings: { type: 'object' },
135
+ // For updateName
136
+ name: { type: 'string' },
137
+ },
138
+ required: ['type'],
139
+ },
140
+ },
141
+ },
142
+ required: ['id', 'operations'],
143
+ },
144
+ },
145
+
146
+ {
147
+ name: 'workflow_delete',
148
+ description: 'Delete a workflow permanently.',
149
+ inputSchema: {
150
+ type: 'object',
151
+ properties: {
152
+ id: {
153
+ type: 'string',
154
+ description: 'Workflow ID',
155
+ },
156
+ },
157
+ required: ['id'],
158
+ },
159
+ },
160
+
161
+ {
162
+ name: 'workflow_activate',
163
+ description: 'Activate a workflow (enable triggers).',
164
+ inputSchema: {
165
+ type: 'object',
166
+ properties: {
167
+ id: {
168
+ type: 'string',
169
+ description: 'Workflow ID',
170
+ },
171
+ },
172
+ required: ['id'],
173
+ },
174
+ },
175
+
176
+ {
177
+ name: 'workflow_deactivate',
178
+ description: 'Deactivate a workflow (disable triggers).',
179
+ inputSchema: {
180
+ type: 'object',
181
+ properties: {
182
+ id: {
183
+ type: 'string',
184
+ description: 'Workflow ID',
185
+ },
186
+ },
187
+ required: ['id'],
188
+ },
189
+ },
190
+
191
+ {
192
+ name: 'workflow_execute',
193
+ description: 'Execute a workflow via its webhook trigger. Workflow must have a webhook node.',
194
+ inputSchema: {
195
+ type: 'object',
196
+ properties: {
197
+ id: {
198
+ type: 'string',
199
+ description: 'Workflow ID',
200
+ },
201
+ data: {
202
+ type: 'object',
203
+ description: 'Data to send to the webhook',
204
+ },
205
+ },
206
+ required: ['id'],
207
+ },
208
+ },
209
+
210
+ // ─────────────────────────────────────────────────────────────
211
+ // Execution Operations
212
+ // ─────────────────────────────────────────────────────────────
213
+ {
214
+ name: 'execution_list',
215
+ description: 'List workflow executions. Filter by workflow or status.',
216
+ inputSchema: {
217
+ type: 'object',
218
+ properties: {
219
+ workflowId: {
220
+ type: 'string',
221
+ description: 'Filter by workflow ID',
222
+ },
223
+ status: {
224
+ type: 'string',
225
+ enum: ['success', 'error', 'waiting'],
226
+ description: 'Filter by status',
227
+ },
228
+ limit: {
229
+ type: 'number',
230
+ description: 'Max results (default 20)',
231
+ },
232
+ },
233
+ },
234
+ },
235
+
236
+ {
237
+ name: 'execution_get',
238
+ description: 'Get execution details including run data and errors.',
239
+ inputSchema: {
240
+ type: 'object',
241
+ properties: {
242
+ id: {
243
+ type: 'string',
244
+ description: 'Execution ID',
245
+ },
246
+ },
247
+ required: ['id'],
248
+ },
249
+ },
250
+
251
+ // ─────────────────────────────────────────────────────────────
252
+ // Validation
253
+ // ─────────────────────────────────────────────────────────────
254
+ {
255
+ name: 'workflow_validate',
256
+ description: `Validate a workflow against best practices:
257
+ - snake_case naming
258
+ - Explicit node references (no $json)
259
+ - No hardcoded IDs
260
+ - No hardcoded secrets
261
+ - No orphan nodes`,
262
+ inputSchema: {
263
+ type: 'object',
264
+ properties: {
265
+ id: {
266
+ type: 'string',
267
+ description: 'Workflow ID to validate',
268
+ },
269
+ },
270
+ required: ['id'],
271
+ },
272
+ },
273
+ ];
package/src/types.ts ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * n8n API Types
3
+ * Minimal types for workflow management
4
+ */
5
+
6
+ export interface N8nNode {
7
+ id: string;
8
+ name: string;
9
+ type: string;
10
+ typeVersion: number;
11
+ position: [number, number];
12
+ parameters: Record<string, unknown>;
13
+ credentials?: Record<string, { id: string; name: string }>;
14
+ disabled?: boolean;
15
+ notes?: string;
16
+ notesInFlow?: boolean;
17
+ }
18
+
19
+ export interface N8nConnection {
20
+ node: string;
21
+ type: string;
22
+ index: number;
23
+ }
24
+
25
+ export interface N8nConnections {
26
+ [nodeName: string]: {
27
+ [outputType: string]: N8nConnection[][];
28
+ };
29
+ }
30
+
31
+ export interface N8nWorkflow {
32
+ id: string;
33
+ name: string;
34
+ active: boolean;
35
+ nodes: N8nNode[];
36
+ connections: N8nConnections;
37
+ settings?: Record<string, unknown>;
38
+ staticData?: Record<string, unknown>;
39
+ tags?: { id: string; name: string }[];
40
+ createdAt: string;
41
+ updatedAt: string;
42
+ }
43
+
44
+ export interface N8nWorkflowListItem {
45
+ id: string;
46
+ name: string;
47
+ active: boolean;
48
+ createdAt: string;
49
+ updatedAt: string;
50
+ tags?: { id: string; name: string }[];
51
+ }
52
+
53
+ export interface N8nExecution {
54
+ id: string;
55
+ workflowId: string;
56
+ finished: boolean;
57
+ mode: string;
58
+ startedAt: string;
59
+ stoppedAt?: string;
60
+ status: 'success' | 'error' | 'waiting' | 'running';
61
+ data?: {
62
+ resultData?: {
63
+ runData?: Record<string, unknown>;
64
+ error?: { message: string };
65
+ };
66
+ };
67
+ }
68
+
69
+ export interface N8nExecutionListItem {
70
+ id: string;
71
+ workflowId: string;
72
+ status: string;
73
+ startedAt: string;
74
+ stoppedAt?: string;
75
+ mode: string;
76
+ }
77
+
78
+ // Patch operations for partial updates
79
+ export type PatchOperation =
80
+ | { type: 'addNode'; node: Omit<N8nNode, 'id'> & { id?: string } }
81
+ | { type: 'removeNode'; nodeName: string }
82
+ | { type: 'updateNode'; nodeName: string; properties: Partial<N8nNode> }
83
+ | { type: 'addConnection'; from: string; to: string; fromOutput?: number; toInput?: number; outputType?: string; inputType?: string }
84
+ | { type: 'removeConnection'; from: string; to: string; fromOutput?: number; toInput?: number; outputType?: string }
85
+ | { type: 'updateSettings'; settings: Record<string, unknown> }
86
+ | { type: 'updateName'; name: string }
87
+ | { type: 'activate' }
88
+ | { type: 'deactivate' };
89
+
90
+ // Validation
91
+ export interface ValidationWarning {
92
+ node?: string;
93
+ rule: string;
94
+ message: string;
95
+ severity: 'error' | 'warning' | 'info';
96
+ }
97
+
98
+ export interface ValidationResult {
99
+ valid: boolean;
100
+ warnings: ValidationWarning[];
101
+ }
102
+
103
+ // API response wrappers
104
+ export interface N8nListResponse<T> {
105
+ data: T[];
106
+ nextCursor?: string;
107
+ }
@@ -0,0 +1,180 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateWorkflow, validatePartialUpdate } from './validators.js';
3
+ import type { N8nWorkflow } from './types.js';
4
+
5
+ const createWorkflow = (overrides: Partial<N8nWorkflow> = {}): N8nWorkflow => ({
6
+ id: '1',
7
+ name: 'test_workflow',
8
+ active: false,
9
+ nodes: [],
10
+ connections: {},
11
+ createdAt: '2024-01-01',
12
+ updatedAt: '2024-01-01',
13
+ ...overrides,
14
+ });
15
+
16
+ describe('validateWorkflow', () => {
17
+ it('passes for valid snake_case workflow', () => {
18
+ const workflow = createWorkflow({
19
+ name: 'my_workflow',
20
+ nodes: [
21
+ {
22
+ id: '1',
23
+ name: 'webhook_trigger',
24
+ type: 'n8n-nodes-base.webhook',
25
+ typeVersion: 1,
26
+ position: [0, 0],
27
+ parameters: { path: 'test' },
28
+ },
29
+ ],
30
+ });
31
+
32
+ const result = validateWorkflow(workflow);
33
+ expect(result.valid).toBe(true);
34
+ });
35
+
36
+ it('warns on non-snake_case workflow name', () => {
37
+ const workflow = createWorkflow({ name: 'My-Workflow-Name' });
38
+ const result = validateWorkflow(workflow);
39
+
40
+ expect(result.warnings).toContainEqual(
41
+ expect.objectContaining({
42
+ rule: 'snake_case',
43
+ severity: 'warning',
44
+ })
45
+ );
46
+ });
47
+
48
+ it('warns on $json usage', () => {
49
+ const workflow = createWorkflow({
50
+ nodes: [
51
+ {
52
+ id: '1',
53
+ name: 'set_node',
54
+ type: 'n8n-nodes-base.set',
55
+ typeVersion: 1,
56
+ position: [0, 0],
57
+ parameters: { value: '={{ $json.field }}' },
58
+ },
59
+ ],
60
+ });
61
+
62
+ const result = validateWorkflow(workflow);
63
+ expect(result.warnings).toContainEqual(
64
+ expect.objectContaining({
65
+ rule: 'explicit_reference',
66
+ severity: 'warning',
67
+ })
68
+ );
69
+ });
70
+
71
+ it('errors on hardcoded secrets', () => {
72
+ const workflow = createWorkflow({
73
+ nodes: [
74
+ {
75
+ id: '1',
76
+ name: 'http_node',
77
+ type: 'n8n-nodes-base.httpRequest',
78
+ typeVersion: 1,
79
+ position: [0, 0],
80
+ parameters: { apiKey: 'sk_live_abc123def456' },
81
+ },
82
+ ],
83
+ });
84
+
85
+ const result = validateWorkflow(workflow);
86
+ expect(result.valid).toBe(false);
87
+ expect(result.warnings).toContainEqual(
88
+ expect.objectContaining({
89
+ rule: 'no_hardcoded_secrets',
90
+ severity: 'error',
91
+ })
92
+ );
93
+ });
94
+
95
+ it('warns on orphan nodes', () => {
96
+ const workflow = createWorkflow({
97
+ nodes: [
98
+ {
99
+ id: '1',
100
+ name: 'orphan_node',
101
+ type: 'n8n-nodes-base.set',
102
+ typeVersion: 1,
103
+ position: [0, 0],
104
+ parameters: {},
105
+ },
106
+ ],
107
+ connections: {},
108
+ });
109
+
110
+ const result = validateWorkflow(workflow);
111
+ expect(result.warnings).toContainEqual(
112
+ expect.objectContaining({
113
+ rule: 'orphan_node',
114
+ severity: 'warning',
115
+ })
116
+ );
117
+ });
118
+ });
119
+
120
+ describe('validatePartialUpdate', () => {
121
+ it('errors when node not found', () => {
122
+ const workflow = createWorkflow();
123
+ const warnings = validatePartialUpdate(workflow, 'nonexistent', {});
124
+
125
+ expect(warnings).toContainEqual(
126
+ expect.objectContaining({
127
+ rule: 'node_exists',
128
+ severity: 'error',
129
+ })
130
+ );
131
+ });
132
+
133
+ it('errors on parameter loss', () => {
134
+ const workflow = createWorkflow({
135
+ nodes: [
136
+ {
137
+ id: '1',
138
+ name: 'my_node',
139
+ type: 'n8n-nodes-base.set',
140
+ typeVersion: 1,
141
+ position: [0, 0],
142
+ parameters: { existingParam: 'value', anotherParam: 'value2' },
143
+ },
144
+ ],
145
+ });
146
+
147
+ const warnings = validatePartialUpdate(workflow, 'my_node', {
148
+ newParam: 'value', // Missing existingParam and anotherParam
149
+ });
150
+
151
+ expect(warnings).toContainEqual(
152
+ expect.objectContaining({
153
+ rule: 'parameter_preservation',
154
+ severity: 'error',
155
+ })
156
+ );
157
+ });
158
+
159
+ it('passes when all parameters preserved', () => {
160
+ const workflow = createWorkflow({
161
+ nodes: [
162
+ {
163
+ id: '1',
164
+ name: 'my_node',
165
+ type: 'n8n-nodes-base.set',
166
+ typeVersion: 1,
167
+ position: [0, 0],
168
+ parameters: { existingParam: 'value' },
169
+ },
170
+ ],
171
+ });
172
+
173
+ const warnings = validatePartialUpdate(workflow, 'my_node', {
174
+ existingParam: 'value',
175
+ newParam: 'new',
176
+ });
177
+
178
+ expect(warnings).toHaveLength(0);
179
+ });
180
+ });