@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.
@@ -0,0 +1,38 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-node@v4
15
+ with:
16
+ node-version: '20'
17
+ cache: 'npm'
18
+ - run: npm ci
19
+ - run: npm run build
20
+ - run: npm test -- --run
21
+
22
+ publish:
23
+ needs: test
24
+ runs-on: ubuntu-latest
25
+ if: github.ref == 'refs/heads/main' && github.event_name == 'push'
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - uses: actions/setup-node@v4
29
+ with:
30
+ node-version: '20'
31
+ cache: 'npm'
32
+ registry-url: 'https://registry.npmjs.org'
33
+ - run: npm ci
34
+ - run: npm run build
35
+ - run: npm publish --access public
36
+ env:
37
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
38
+ continue-on-error: true # Won't fail if version already exists
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # pl-n8n-mcp
2
+
3
+ > **@pagelines/n8n-mcp** - Opinionated MCP server for n8n workflow automation by [PageLines](https://github.com/pagelines)
4
+
5
+ ## Features
6
+
7
+ - **Minimal footprint** - ~1,200 lines total, no database, no bloat
8
+ - **Patch-based updates** - Never lose parameters, always preserves existing data
9
+ - **Built-in validation** - Enforces best practices automatically
10
+ - **Safety warnings** - Alerts when updates might cause issues
11
+
12
+ ## Best Practices Enforced
13
+
14
+ - `snake_case` naming for workflows and nodes
15
+ - Explicit node references (`$('node_name').item.json.field` not `$json`)
16
+ - No hardcoded IDs or secrets
17
+ - No orphan nodes
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @pagelines/n8n-mcp
23
+ ```
24
+
25
+ Or run directly:
26
+
27
+ ```bash
28
+ npx @pagelines/n8n-mcp
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ ### Claude Code / Cursor
34
+
35
+ Add to your MCP settings (`~/.claude/mcp.json` or IDE config):
36
+
37
+ ```json
38
+ {
39
+ "mcpServers": {
40
+ "pl-n8n": {
41
+ "command": "npx",
42
+ "args": ["-y", "@pagelines/n8n-mcp"],
43
+ "env": {
44
+ "N8N_API_URL": "https://your-n8n-instance.com",
45
+ "N8N_API_KEY": "your-api-key"
46
+ }
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### Environment Variables
53
+
54
+ | Variable | Description |
55
+ |----------|-------------|
56
+ | `N8N_API_URL` | Your n8n instance URL |
57
+ | `N8N_API_KEY` | API key from n8n settings |
58
+
59
+ ## Tools
60
+
61
+ ### Workflow Operations
62
+
63
+ | Tool | Description |
64
+ |------|-------------|
65
+ | `workflow_list` | List all workflows |
66
+ | `workflow_get` | Get workflow by ID |
67
+ | `workflow_create` | Create new workflow |
68
+ | `workflow_update` | Update workflow with patch operations |
69
+ | `workflow_delete` | Delete workflow |
70
+ | `workflow_activate` | Enable triggers |
71
+ | `workflow_deactivate` | Disable triggers |
72
+ | `workflow_execute` | Execute via webhook |
73
+ | `workflow_validate` | Validate against best practices |
74
+
75
+ ### Execution Operations
76
+
77
+ | Tool | Description |
78
+ |------|-------------|
79
+ | `execution_list` | List executions |
80
+ | `execution_get` | Get execution details |
81
+
82
+ ## Patch Operations
83
+
84
+ The `workflow_update` tool uses patch operations to safely modify workflows:
85
+
86
+ ```javascript
87
+ // Add a node
88
+ { "type": "addNode", "node": { "name": "my_node", "type": "n8n-nodes-base.set", ... } }
89
+
90
+ // Update a node (INCLUDE ALL existing parameters)
91
+ { "type": "updateNode", "nodeName": "my_node", "properties": { "parameters": { ...existing, "newParam": "value" } } }
92
+
93
+ // Remove a node
94
+ { "type": "removeNode", "nodeName": "my_node" }
95
+
96
+ // Add connection
97
+ { "type": "addConnection", "from": "node_a", "to": "node_b" }
98
+
99
+ // Remove connection
100
+ { "type": "removeConnection", "from": "node_a", "to": "node_b" }
101
+
102
+ // Update settings
103
+ { "type": "updateSettings", "settings": { "executionOrder": "v1" } }
104
+
105
+ // Rename workflow
106
+ { "type": "updateName", "name": "new_name" }
107
+ ```
108
+
109
+ ## Validation Rules
110
+
111
+ | Rule | Severity | Description |
112
+ |------|----------|-------------|
113
+ | `snake_case` | warning | Names should be snake_case |
114
+ | `explicit_reference` | warning | Use `$('node')` not `$json` |
115
+ | `no_hardcoded_ids` | info | Avoid hardcoded IDs |
116
+ | `no_hardcoded_secrets` | error | Never hardcode secrets |
117
+ | `orphan_node` | warning | Node has no connections |
118
+ | `parameter_preservation` | error | Update would remove parameters |
119
+
120
+ ## Development
121
+
122
+ ```bash
123
+ # Install dependencies
124
+ npm install
125
+
126
+ # Build
127
+ npm run build
128
+
129
+ # Watch mode
130
+ npm run dev
131
+
132
+ # Run tests
133
+ npm test
134
+ ```
135
+
136
+ ## Deployment
137
+
138
+ ### npm
139
+
140
+ Published automatically on push to `main` via GitHub Actions.
141
+
142
+ Manual publish:
143
+ ```bash
144
+ npm publish --access public
145
+ ```
146
+
147
+ ### MCP Registry
148
+
149
+ This server is registered at [registry.modelcontextprotocol.io](https://registry.modelcontextprotocol.io) as `io.github.pagelines/n8n-mcp`.
150
+
151
+ The `server.json` file contains the registry metadata.
152
+
153
+ ## License
154
+
155
+ MIT
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @pagelines/n8n-mcp
4
+ * Opinionated MCP server for n8n workflow automation
5
+ */
6
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @pagelines/n8n-mcp
4
+ * Opinionated MCP server for n8n workflow automation
5
+ */
6
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
9
+ import { N8nClient } from './n8n-client.js';
10
+ import { tools } from './tools.js';
11
+ import { validateWorkflow } from './validators.js';
12
+ // ─────────────────────────────────────────────────────────────
13
+ // Configuration
14
+ // ─────────────────────────────────────────────────────────────
15
+ const N8N_API_URL = process.env.N8N_API_URL || process.env.N8N_HOST || '';
16
+ const N8N_API_KEY = process.env.N8N_API_KEY || '';
17
+ if (!N8N_API_URL || !N8N_API_KEY) {
18
+ console.error('Error: N8N_API_URL and N8N_API_KEY environment variables are required');
19
+ console.error('Set them in your MCP server configuration or environment');
20
+ process.exit(1);
21
+ }
22
+ const client = new N8nClient({
23
+ apiUrl: N8N_API_URL,
24
+ apiKey: N8N_API_KEY,
25
+ });
26
+ // ─────────────────────────────────────────────────────────────
27
+ // MCP Server
28
+ // ─────────────────────────────────────────────────────────────
29
+ const server = new Server({
30
+ name: '@pagelines/n8n-mcp',
31
+ version: '0.1.0',
32
+ }, {
33
+ capabilities: {
34
+ tools: {},
35
+ },
36
+ });
37
+ // List available tools
38
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
39
+ tools,
40
+ }));
41
+ // Handle tool calls
42
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
43
+ const { name, arguments: args } = request.params;
44
+ try {
45
+ const result = await handleTool(name, args || {});
46
+ return {
47
+ content: [
48
+ {
49
+ type: 'text',
50
+ text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
51
+ },
52
+ ],
53
+ };
54
+ }
55
+ catch (error) {
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ return {
58
+ content: [
59
+ {
60
+ type: 'text',
61
+ text: `Error: ${message}`,
62
+ },
63
+ ],
64
+ isError: true,
65
+ };
66
+ }
67
+ });
68
+ // ─────────────────────────────────────────────────────────────
69
+ // Tool Handlers
70
+ // ─────────────────────────────────────────────────────────────
71
+ async function handleTool(name, args) {
72
+ switch (name) {
73
+ // Workflow operations
74
+ case 'workflow_list': {
75
+ const response = await client.listWorkflows({
76
+ active: args.active,
77
+ limit: args.limit || 100,
78
+ });
79
+ return {
80
+ workflows: response.data.map((w) => ({
81
+ id: w.id,
82
+ name: w.name,
83
+ active: w.active,
84
+ updatedAt: w.updatedAt,
85
+ })),
86
+ total: response.data.length,
87
+ };
88
+ }
89
+ case 'workflow_get': {
90
+ const workflow = await client.getWorkflow(args.id);
91
+ return workflow;
92
+ }
93
+ case 'workflow_create': {
94
+ const nodes = args.nodes.map((n, i) => ({
95
+ id: crypto.randomUUID(),
96
+ name: n.name,
97
+ type: n.type,
98
+ typeVersion: n.typeVersion,
99
+ position: n.position || [250, 250 + i * 100],
100
+ parameters: n.parameters || {},
101
+ ...(n.credentials && { credentials: n.credentials }),
102
+ }));
103
+ const workflow = await client.createWorkflow({
104
+ name: args.name,
105
+ nodes,
106
+ connections: args.connections || {},
107
+ settings: args.settings,
108
+ });
109
+ // Validate the new workflow
110
+ const validation = validateWorkflow(workflow);
111
+ return {
112
+ workflow,
113
+ validation,
114
+ };
115
+ }
116
+ case 'workflow_update': {
117
+ const operations = args.operations;
118
+ const { workflow, warnings } = await client.patchWorkflow(args.id, operations);
119
+ // Also run validation
120
+ const validation = validateWorkflow(workflow);
121
+ return {
122
+ workflow,
123
+ patchWarnings: warnings,
124
+ validation,
125
+ };
126
+ }
127
+ case 'workflow_delete': {
128
+ await client.deleteWorkflow(args.id);
129
+ return { success: true, message: `Workflow ${args.id} deleted` };
130
+ }
131
+ case 'workflow_activate': {
132
+ const workflow = await client.activateWorkflow(args.id);
133
+ return {
134
+ id: workflow.id,
135
+ name: workflow.name,
136
+ active: workflow.active,
137
+ };
138
+ }
139
+ case 'workflow_deactivate': {
140
+ const workflow = await client.deactivateWorkflow(args.id);
141
+ return {
142
+ id: workflow.id,
143
+ name: workflow.name,
144
+ active: workflow.active,
145
+ };
146
+ }
147
+ case 'workflow_execute': {
148
+ const result = await client.executeWorkflow(args.id, args.data);
149
+ return result;
150
+ }
151
+ // Execution operations
152
+ case 'execution_list': {
153
+ const response = await client.listExecutions({
154
+ workflowId: args.workflowId,
155
+ status: args.status,
156
+ limit: args.limit || 20,
157
+ });
158
+ return {
159
+ executions: response.data,
160
+ total: response.data.length,
161
+ };
162
+ }
163
+ case 'execution_get': {
164
+ const execution = await client.getExecution(args.id);
165
+ return execution;
166
+ }
167
+ // Validation
168
+ case 'workflow_validate': {
169
+ const workflow = await client.getWorkflow(args.id);
170
+ const validation = validateWorkflow(workflow);
171
+ return {
172
+ workflowId: workflow.id,
173
+ workflowName: workflow.name,
174
+ ...validation,
175
+ };
176
+ }
177
+ default:
178
+ throw new Error(`Unknown tool: ${name}`);
179
+ }
180
+ }
181
+ // ─────────────────────────────────────────────────────────────
182
+ // Start Server
183
+ // ─────────────────────────────────────────────────────────────
184
+ async function main() {
185
+ const transport = new StdioServerTransport();
186
+ await server.connect(transport);
187
+ console.error('@pagelines/n8n-mcp server started');
188
+ }
189
+ main().catch((error) => {
190
+ console.error('Fatal error:', error);
191
+ process.exit(1);
192
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * n8n REST API Client
3
+ * Clean, minimal implementation with built-in safety checks
4
+ */
5
+ import type { N8nWorkflow, N8nWorkflowListItem, N8nExecution, N8nExecutionListItem, N8nListResponse, N8nNode, PatchOperation } from './types.js';
6
+ export interface N8nClientConfig {
7
+ apiUrl: string;
8
+ apiKey: string;
9
+ }
10
+ export declare class N8nClient {
11
+ private baseUrl;
12
+ private headers;
13
+ constructor(config: N8nClientConfig);
14
+ private request;
15
+ listWorkflows(options?: {
16
+ limit?: number;
17
+ cursor?: string;
18
+ active?: boolean;
19
+ tags?: string[];
20
+ }): Promise<N8nListResponse<N8nWorkflowListItem>>;
21
+ getWorkflow(id: string): Promise<N8nWorkflow>;
22
+ createWorkflow(workflow: {
23
+ name: string;
24
+ nodes: N8nNode[];
25
+ connections: N8nWorkflow['connections'];
26
+ settings?: Record<string, unknown>;
27
+ }): Promise<N8nWorkflow>;
28
+ updateWorkflow(id: string, workflow: Partial<Omit<N8nWorkflow, 'id' | 'createdAt' | 'updatedAt'>>): Promise<N8nWorkflow>;
29
+ deleteWorkflow(id: string): Promise<void>;
30
+ activateWorkflow(id: string): Promise<N8nWorkflow>;
31
+ deactivateWorkflow(id: string): Promise<N8nWorkflow>;
32
+ patchWorkflow(id: string, operations: PatchOperation[]): Promise<{
33
+ workflow: N8nWorkflow;
34
+ warnings: string[];
35
+ }>;
36
+ private applyOperations;
37
+ listExecutions(options?: {
38
+ workflowId?: string;
39
+ status?: 'success' | 'error' | 'waiting';
40
+ limit?: number;
41
+ cursor?: string;
42
+ }): Promise<N8nListResponse<N8nExecutionListItem>>;
43
+ getExecution(id: string): Promise<N8nExecution>;
44
+ deleteExecution(id: string): Promise<void>;
45
+ executeWorkflow(id: string, data?: Record<string, unknown>): Promise<{
46
+ executionId?: string;
47
+ data?: unknown;
48
+ }>;
49
+ healthCheck(): Promise<{
50
+ healthy: boolean;
51
+ version?: string;
52
+ error?: string;
53
+ }>;
54
+ }