@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/.github/workflows/ci.yml +38 -0
- package/README.md +155 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +192 -0
- package/dist/n8n-client.d.ts +54 -0
- package/dist/n8n-client.js +275 -0
- package/dist/n8n-client.test.d.ts +1 -0
- package/dist/n8n-client.test.js +184 -0
- package/dist/tools.d.ts +6 -0
- package/dist/tools.js +260 -0
- package/dist/types.d.ts +132 -0
- package/dist/types.js +5 -0
- package/dist/validators.d.ts +10 -0
- package/dist/validators.js +171 -0
- package/dist/validators.test.d.ts +1 -0
- package/dist/validators.test.js +148 -0
- package/package.json +42 -0
- package/server.json +58 -0
- package/src/index.ts +243 -0
- package/src/n8n-client.test.ts +227 -0
- package/src/n8n-client.ts +361 -0
- package/src/tools.ts +273 -0
- package/src/types.ts +107 -0
- package/src/validators.test.ts +180 -0
- package/src/validators.ts +208 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +8 -0
|
@@ -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
|
package/dist/index.d.ts
ADDED
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
|
+
}
|