@pagelines/n8n-mcp 0.1.0 → 0.2.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/CHANGELOG.md +52 -0
- package/README.md +37 -106
- package/dist/autofix.d.ts +28 -0
- package/dist/autofix.js +222 -0
- package/dist/expressions.d.ts +25 -0
- package/dist/expressions.js +209 -0
- package/dist/index.js +124 -1
- package/dist/tools.js +147 -4
- package/dist/validators.js +67 -0
- package/dist/validators.test.js +83 -0
- package/dist/versions.d.ts +71 -0
- package/dist/versions.js +239 -0
- package/docs/best-practices.md +241 -0
- package/package.json +1 -1
- package/server.json +10 -2
- package/src/autofix.ts +275 -0
- package/src/expressions.ts +254 -0
- package/src/index.ts +169 -1
- package/src/tools.ts +155 -4
- package/src/validators.test.ts +97 -0
- package/src/validators.ts +77 -0
- package/src/versions.ts +320 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.2.0] - 2025-01-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
#### Version Control System
|
|
10
|
+
- `version_list` - List saved versions of a workflow (stored locally in `~/.n8n-mcp/versions/`)
|
|
11
|
+
- `version_get` - Get a specific saved version
|
|
12
|
+
- `version_save` - Manually save a version snapshot
|
|
13
|
+
- `version_rollback` - Restore a workflow to a previous version
|
|
14
|
+
- `version_diff` - Compare two versions or current state vs a version
|
|
15
|
+
- `version_stats` - Get version control statistics
|
|
16
|
+
- Auto-save versions before workflow updates, rollbacks, and auto-fixes
|
|
17
|
+
- Configurable via `N8N_MCP_VERSIONS` and `N8N_MCP_MAX_VERSIONS` env vars
|
|
18
|
+
|
|
19
|
+
#### Auto-fix System
|
|
20
|
+
- `workflow_autofix` - Auto-fix common validation issues:
|
|
21
|
+
- Convert names to snake_case
|
|
22
|
+
- Replace `$json` with explicit `$('node_name')` references
|
|
23
|
+
- Add AI structured output settings
|
|
24
|
+
- `workflow_format` - Format workflows: sort nodes by position, clean up null values
|
|
25
|
+
|
|
26
|
+
#### Expression Validation
|
|
27
|
+
- Parse and validate n8n expressions in workflow parameters
|
|
28
|
+
- Detect references to non-existent nodes
|
|
29
|
+
- Check for syntax errors (unmatched parentheses, brackets)
|
|
30
|
+
- Warn about deprecated patterns (`$node.` usage)
|
|
31
|
+
- Detect circular references in expressions
|
|
32
|
+
- Suggest optional chaining for deep property access
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- `workflow_validate` now includes expression validation and circular reference detection
|
|
36
|
+
- `workflow_update` now auto-saves a version before applying changes
|
|
37
|
+
|
|
38
|
+
## [0.1.0] - 2025-01-11
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- Initial release
|
|
42
|
+
- Workflow operations: list, get, create, update, delete, activate, deactivate, execute
|
|
43
|
+
- Execution operations: list, get
|
|
44
|
+
- Workflow validation with best practices enforcement:
|
|
45
|
+
- snake_case naming
|
|
46
|
+
- Explicit node references
|
|
47
|
+
- No hardcoded IDs or secrets
|
|
48
|
+
- Orphan node detection
|
|
49
|
+
- Code node usage detection
|
|
50
|
+
- AI node structured output settings
|
|
51
|
+
- In-memory storage detection
|
|
52
|
+
- Patch-based workflow updates with parameter preservation warnings
|
package/README.md
CHANGED
|
@@ -1,47 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# n8n MCP Server
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Version control, validation, and patch-based updates for n8n workflows.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@pagelines/n8n-mcp)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
7
|
-
|
|
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:
|
|
8
|
+
## Install
|
|
26
9
|
|
|
27
10
|
```bash
|
|
28
11
|
npx @pagelines/n8n-mcp
|
|
29
12
|
```
|
|
30
13
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
### Claude Code / Cursor
|
|
34
|
-
|
|
35
|
-
Add to your MCP settings (`~/.claude/mcp.json` or IDE config):
|
|
14
|
+
Add to MCP config (`~/.claude/mcp.json`):
|
|
36
15
|
|
|
37
16
|
```json
|
|
38
17
|
{
|
|
39
18
|
"mcpServers": {
|
|
40
|
-
"
|
|
19
|
+
"n8n": {
|
|
41
20
|
"command": "npx",
|
|
42
21
|
"args": ["-y", "@pagelines/n8n-mcp"],
|
|
43
22
|
"env": {
|
|
44
|
-
"N8N_API_URL": "https://your-n8n
|
|
23
|
+
"N8N_API_URL": "https://your-n8n.com",
|
|
45
24
|
"N8N_API_KEY": "your-api-key"
|
|
46
25
|
}
|
|
47
26
|
}
|
|
@@ -49,13 +28,6 @@ Add to your MCP settings (`~/.claude/mcp.json` or IDE config):
|
|
|
49
28
|
}
|
|
50
29
|
```
|
|
51
30
|
|
|
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
31
|
## Tools
|
|
60
32
|
|
|
61
33
|
### Workflow Operations
|
|
@@ -65,91 +37,50 @@ Add to your MCP settings (`~/.claude/mcp.json` or IDE config):
|
|
|
65
37
|
| `workflow_list` | List all workflows |
|
|
66
38
|
| `workflow_get` | Get workflow by ID |
|
|
67
39
|
| `workflow_create` | Create new workflow |
|
|
68
|
-
| `workflow_update` |
|
|
40
|
+
| `workflow_update` | Patch-based updates (preserves parameters) |
|
|
69
41
|
| `workflow_delete` | Delete workflow |
|
|
70
42
|
| `workflow_activate` | Enable triggers |
|
|
71
43
|
| `workflow_deactivate` | Disable triggers |
|
|
72
44
|
| `workflow_execute` | Execute via webhook |
|
|
73
|
-
| `workflow_validate` | Validate against best practices |
|
|
74
45
|
|
|
75
|
-
###
|
|
46
|
+
### Quality & Validation
|
|
76
47
|
|
|
77
48
|
| Tool | Description |
|
|
78
49
|
|------|-------------|
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
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" }
|
|
50
|
+
| `workflow_validate` | Check best practices, expressions, circular refs |
|
|
51
|
+
| `workflow_autofix` | Auto-fix snake_case, explicit refs, AI settings |
|
|
52
|
+
| `workflow_format` | Sort nodes, clean nulls |
|
|
101
53
|
|
|
102
|
-
|
|
103
|
-
{ "type": "updateSettings", "settings": { "executionOrder": "v1" } }
|
|
54
|
+
### Version Control
|
|
104
55
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
56
|
+
| Tool | Description |
|
|
57
|
+
|------|-------------|
|
|
58
|
+
| `version_list` | List saved versions |
|
|
59
|
+
| `version_get` | Get specific version |
|
|
60
|
+
| `version_save` | Manual snapshot |
|
|
61
|
+
| `version_rollback` | Restore previous version |
|
|
62
|
+
| `version_diff` | Compare versions |
|
|
108
63
|
|
|
109
64
|
## Validation Rules
|
|
110
65
|
|
|
111
|
-
| Rule | Severity |
|
|
112
|
-
|
|
113
|
-
| `snake_case` | warning |
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
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
|
|
66
|
+
| Rule | Severity |
|
|
67
|
+
|------|----------|
|
|
68
|
+
| `snake_case` naming | warning |
|
|
69
|
+
| Explicit refs (`$('node')` not `$json`) | warning |
|
|
70
|
+
| No hardcoded secrets | error |
|
|
71
|
+
| No orphan nodes | warning |
|
|
72
|
+
| AI structured output | warning |
|
|
73
|
+
| Expression syntax | error |
|
|
137
74
|
|
|
138
|
-
|
|
75
|
+
## Environment Variables
|
|
139
76
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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.
|
|
77
|
+
| Variable | Description |
|
|
78
|
+
|----------|-------------|
|
|
79
|
+
| `N8N_API_URL` | Your n8n instance URL |
|
|
80
|
+
| `N8N_API_KEY` | API key from n8n settings |
|
|
81
|
+
| `N8N_MCP_VERSIONS` | Enable version control (default: true) |
|
|
82
|
+
| `N8N_MCP_MAX_VERSIONS` | Max versions per workflow (default: 20) |
|
|
152
83
|
|
|
153
84
|
## License
|
|
154
85
|
|
|
155
|
-
MIT
|
|
86
|
+
MIT - [PageLines](https://github.com/pagelines)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-fix common n8n workflow issues
|
|
3
|
+
* Transforms workflows to follow best practices
|
|
4
|
+
*/
|
|
5
|
+
import type { N8nWorkflow, ValidationWarning } from './types.js';
|
|
6
|
+
export interface AutofixResult {
|
|
7
|
+
workflow: N8nWorkflow;
|
|
8
|
+
fixes: AutofixAction[];
|
|
9
|
+
unfixable: ValidationWarning[];
|
|
10
|
+
}
|
|
11
|
+
export interface AutofixAction {
|
|
12
|
+
type: string;
|
|
13
|
+
target: string;
|
|
14
|
+
description: string;
|
|
15
|
+
before?: string;
|
|
16
|
+
after?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Auto-fix a workflow based on validation warnings
|
|
20
|
+
*/
|
|
21
|
+
export declare function autofixWorkflow(workflow: N8nWorkflow, warnings: ValidationWarning[]): AutofixResult;
|
|
22
|
+
/**
|
|
23
|
+
* Format a workflow for consistency
|
|
24
|
+
* - Sorts nodes by position
|
|
25
|
+
* - Normalizes connection format
|
|
26
|
+
* - Removes empty/null values
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatWorkflow(workflow: N8nWorkflow): N8nWorkflow;
|
package/dist/autofix.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-fix common n8n workflow issues
|
|
3
|
+
* Transforms workflows to follow best practices
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Auto-fix a workflow based on validation warnings
|
|
7
|
+
*/
|
|
8
|
+
export function autofixWorkflow(workflow, warnings) {
|
|
9
|
+
// Deep clone to avoid mutation
|
|
10
|
+
const fixed = JSON.parse(JSON.stringify(workflow));
|
|
11
|
+
const fixes = [];
|
|
12
|
+
const unfixable = [];
|
|
13
|
+
for (const warning of warnings) {
|
|
14
|
+
const result = attemptFix(fixed, warning);
|
|
15
|
+
if (result) {
|
|
16
|
+
fixes.push(result);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
unfixable.push(warning);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { workflow: fixed, fixes, unfixable };
|
|
23
|
+
}
|
|
24
|
+
function attemptFix(workflow, warning) {
|
|
25
|
+
switch (warning.rule) {
|
|
26
|
+
case 'snake_case':
|
|
27
|
+
return fixSnakeCase(workflow, warning);
|
|
28
|
+
case 'explicit_reference':
|
|
29
|
+
return fixExplicitReference(workflow, warning);
|
|
30
|
+
case 'ai_structured_output':
|
|
31
|
+
return fixAIStructuredOutput(workflow, warning);
|
|
32
|
+
default:
|
|
33
|
+
// Rules that can't be auto-fixed:
|
|
34
|
+
// - no_hardcoded_secrets (need manual review)
|
|
35
|
+
// - no_hardcoded_ids (need manual review)
|
|
36
|
+
// - orphan_node (need context to know what to connect)
|
|
37
|
+
// - code_node_usage (info only)
|
|
38
|
+
// - in_memory_storage (architectural decision)
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Fix snake_case naming
|
|
44
|
+
*/
|
|
45
|
+
function fixSnakeCase(workflow, warning) {
|
|
46
|
+
const target = warning.node || 'workflow';
|
|
47
|
+
if (!warning.node) {
|
|
48
|
+
// Fix workflow name
|
|
49
|
+
const oldName = workflow.name;
|
|
50
|
+
const newName = toSnakeCase(oldName);
|
|
51
|
+
if (oldName === newName)
|
|
52
|
+
return null;
|
|
53
|
+
workflow.name = newName;
|
|
54
|
+
return {
|
|
55
|
+
type: 'rename',
|
|
56
|
+
target: 'workflow',
|
|
57
|
+
description: `Renamed workflow to snake_case`,
|
|
58
|
+
before: oldName,
|
|
59
|
+
after: newName,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// Fix node name
|
|
63
|
+
const node = workflow.nodes.find((n) => n.name === warning.node);
|
|
64
|
+
if (!node)
|
|
65
|
+
return null;
|
|
66
|
+
const oldName = node.name;
|
|
67
|
+
const newName = toSnakeCase(oldName);
|
|
68
|
+
if (oldName === newName)
|
|
69
|
+
return null;
|
|
70
|
+
// Update node name
|
|
71
|
+
node.name = newName;
|
|
72
|
+
// Update connections that reference this node
|
|
73
|
+
if (workflow.connections[oldName]) {
|
|
74
|
+
workflow.connections[newName] = workflow.connections[oldName];
|
|
75
|
+
delete workflow.connections[oldName];
|
|
76
|
+
}
|
|
77
|
+
// Update connections that target this node
|
|
78
|
+
for (const outputs of Object.values(workflow.connections)) {
|
|
79
|
+
for (const outputType of Object.values(outputs)) {
|
|
80
|
+
for (const connections of outputType) {
|
|
81
|
+
for (const conn of connections) {
|
|
82
|
+
if (conn.node === oldName) {
|
|
83
|
+
conn.node = newName;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
type: 'rename',
|
|
91
|
+
target: `node:${oldName}`,
|
|
92
|
+
description: `Renamed node to snake_case`,
|
|
93
|
+
before: oldName,
|
|
94
|
+
after: newName,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Convert to snake_case
|
|
99
|
+
*/
|
|
100
|
+
function toSnakeCase(name) {
|
|
101
|
+
return name
|
|
102
|
+
.replace(/([A-Z])/g, '_$1')
|
|
103
|
+
.replace(/[-\s]+/g, '_')
|
|
104
|
+
.replace(/^_/, '')
|
|
105
|
+
.replace(/_+/g, '_')
|
|
106
|
+
.toLowerCase();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Fix $json to explicit references
|
|
110
|
+
* This is a best-effort fix - may need manual review
|
|
111
|
+
*/
|
|
112
|
+
function fixExplicitReference(workflow, warning) {
|
|
113
|
+
if (!warning.node)
|
|
114
|
+
return null;
|
|
115
|
+
const node = workflow.nodes.find((n) => n.name === warning.node);
|
|
116
|
+
if (!node)
|
|
117
|
+
return null;
|
|
118
|
+
// Find the previous node in the connection chain
|
|
119
|
+
const previousNode = findPreviousNode(workflow, node.name);
|
|
120
|
+
if (!previousNode) {
|
|
121
|
+
// Can't auto-fix without knowing the source
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
// Replace $json with explicit reference
|
|
125
|
+
const params = JSON.stringify(node.parameters);
|
|
126
|
+
const fixedParams = params
|
|
127
|
+
.replace(/\$json\./g, `$('${previousNode}').item.json.`)
|
|
128
|
+
.replace(/\{\{\s*\$json\./g, `{{ $('${previousNode}').item.json.`);
|
|
129
|
+
if (params === fixedParams)
|
|
130
|
+
return null;
|
|
131
|
+
node.parameters = JSON.parse(fixedParams);
|
|
132
|
+
return {
|
|
133
|
+
type: 'expression_fix',
|
|
134
|
+
target: `node:${node.name}`,
|
|
135
|
+
description: `Changed $json to explicit $('${previousNode}') reference`,
|
|
136
|
+
before: '$json.',
|
|
137
|
+
after: `$('${previousNode}').item.json.`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Find the previous node in the connection chain
|
|
142
|
+
*/
|
|
143
|
+
function findPreviousNode(workflow, nodeName) {
|
|
144
|
+
for (const [sourceName, outputs] of Object.entries(workflow.connections)) {
|
|
145
|
+
for (const outputType of Object.values(outputs)) {
|
|
146
|
+
for (const connections of outputType) {
|
|
147
|
+
for (const conn of connections) {
|
|
148
|
+
if (conn.node === nodeName) {
|
|
149
|
+
return sourceName;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Fix AI structured output settings
|
|
159
|
+
*/
|
|
160
|
+
function fixAIStructuredOutput(workflow, warning) {
|
|
161
|
+
if (!warning.node)
|
|
162
|
+
return null;
|
|
163
|
+
const node = workflow.nodes.find((n) => n.name === warning.node);
|
|
164
|
+
if (!node)
|
|
165
|
+
return null;
|
|
166
|
+
const params = node.parameters;
|
|
167
|
+
const changes = [];
|
|
168
|
+
if (params.promptType !== 'define') {
|
|
169
|
+
params.promptType = 'define';
|
|
170
|
+
changes.push('promptType: "define"');
|
|
171
|
+
}
|
|
172
|
+
if (params.hasOutputParser !== true) {
|
|
173
|
+
params.hasOutputParser = true;
|
|
174
|
+
changes.push('hasOutputParser: true');
|
|
175
|
+
}
|
|
176
|
+
if (changes.length === 0)
|
|
177
|
+
return null;
|
|
178
|
+
return {
|
|
179
|
+
type: 'parameter_fix',
|
|
180
|
+
target: `node:${node.name}`,
|
|
181
|
+
description: `Added AI structured output settings: ${changes.join(', ')}`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Format a workflow for consistency
|
|
186
|
+
* - Sorts nodes by position
|
|
187
|
+
* - Normalizes connection format
|
|
188
|
+
* - Removes empty/null values
|
|
189
|
+
*/
|
|
190
|
+
export function formatWorkflow(workflow) {
|
|
191
|
+
const formatted = JSON.parse(JSON.stringify(workflow));
|
|
192
|
+
// Sort nodes by Y position, then X
|
|
193
|
+
formatted.nodes.sort((a, b) => {
|
|
194
|
+
const [ax, ay] = a.position;
|
|
195
|
+
const [bx, by] = b.position;
|
|
196
|
+
if (ay !== by)
|
|
197
|
+
return ay - by;
|
|
198
|
+
return ax - bx;
|
|
199
|
+
});
|
|
200
|
+
// Clean up parameters - remove undefined/null
|
|
201
|
+
for (const node of formatted.nodes) {
|
|
202
|
+
node.parameters = cleanObject(node.parameters);
|
|
203
|
+
}
|
|
204
|
+
return formatted;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Remove null/undefined values from object
|
|
208
|
+
*/
|
|
209
|
+
function cleanObject(obj) {
|
|
210
|
+
const cleaned = {};
|
|
211
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
212
|
+
if (value === null || value === undefined)
|
|
213
|
+
continue;
|
|
214
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
215
|
+
cleaned[key] = cleanObject(value);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
cleaned[key] = value;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return cleaned;
|
|
222
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* n8n expression validation
|
|
3
|
+
* Parses and validates expressions in workflow parameters
|
|
4
|
+
*/
|
|
5
|
+
import type { N8nWorkflow } from './types.js';
|
|
6
|
+
export interface ExpressionIssue {
|
|
7
|
+
node: string;
|
|
8
|
+
parameter: string;
|
|
9
|
+
expression: string;
|
|
10
|
+
issue: string;
|
|
11
|
+
severity: 'error' | 'warning' | 'info';
|
|
12
|
+
suggestion?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Validate all expressions in a workflow
|
|
16
|
+
*/
|
|
17
|
+
export declare function validateExpressions(workflow: N8nWorkflow): ExpressionIssue[];
|
|
18
|
+
/**
|
|
19
|
+
* Get all referenced nodes from expressions
|
|
20
|
+
*/
|
|
21
|
+
export declare function getReferencedNodes(workflow: N8nWorkflow): Map<string, string[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Check for circular references in expressions
|
|
24
|
+
*/
|
|
25
|
+
export declare function checkCircularReferences(workflow: N8nWorkflow): string[][];
|