@pagelines/n8n-mcp 0.1.0 → 0.2.1
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 +63 -0
- package/README.md +44 -119
- 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 +160 -0
- package/docs/node-config.md +203 -0
- package/package.json +1 -1
- package/plans/ai-guidelines.md +233 -0
- package/plans/architecture.md +177 -0
- 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,63 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.2.1] - 2025-01-13
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- [Node Config Guide](docs/node-config.md) - Human-editable node settings (`__rl` resource locator, Set node JSON mode, AI structured output)
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Sharpened documentation (31% line reduction, higher data-ink ratio)
|
|
12
|
+
- README: Lead with differentiation, compact tool/validation tables
|
|
13
|
+
- Best Practices: Quick reference at top, focused on MCP-validated patterns
|
|
14
|
+
- Architecture: Technical reference, removed redundant philosophy sections
|
|
15
|
+
|
|
16
|
+
## [0.2.0] - 2025-01-12
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
#### Version Control System
|
|
21
|
+
- `version_list` - List saved versions of a workflow (stored locally in `~/.n8n-mcp/versions/`)
|
|
22
|
+
- `version_get` - Get a specific saved version
|
|
23
|
+
- `version_save` - Manually save a version snapshot
|
|
24
|
+
- `version_rollback` - Restore a workflow to a previous version
|
|
25
|
+
- `version_diff` - Compare two versions or current state vs a version
|
|
26
|
+
- `version_stats` - Get version control statistics
|
|
27
|
+
- Auto-save versions before workflow updates, rollbacks, and auto-fixes
|
|
28
|
+
- Configurable via `N8N_MCP_VERSIONS` and `N8N_MCP_MAX_VERSIONS` env vars
|
|
29
|
+
|
|
30
|
+
#### Auto-fix System
|
|
31
|
+
- `workflow_autofix` - Auto-fix common validation issues:
|
|
32
|
+
- Convert names to snake_case
|
|
33
|
+
- Replace `$json` with explicit `$('node_name')` references
|
|
34
|
+
- Add AI structured output settings
|
|
35
|
+
- `workflow_format` - Format workflows: sort nodes by position, clean up null values
|
|
36
|
+
|
|
37
|
+
#### Expression Validation
|
|
38
|
+
- Parse and validate n8n expressions in workflow parameters
|
|
39
|
+
- Detect references to non-existent nodes
|
|
40
|
+
- Check for syntax errors (unmatched parentheses, brackets)
|
|
41
|
+
- Warn about deprecated patterns (`$node.` usage)
|
|
42
|
+
- Detect circular references in expressions
|
|
43
|
+
- Suggest optional chaining for deep property access
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- `workflow_validate` now includes expression validation and circular reference detection
|
|
47
|
+
- `workflow_update` now auto-saves a version before applying changes
|
|
48
|
+
|
|
49
|
+
## [0.1.0] - 2025-01-11
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
- Initial release
|
|
53
|
+
- Workflow operations: list, get, create, update, delete, activate, deactivate, execute
|
|
54
|
+
- Execution operations: list, get
|
|
55
|
+
- Workflow validation with best practices enforcement:
|
|
56
|
+
- snake_case naming
|
|
57
|
+
- Explicit node references
|
|
58
|
+
- No hardcoded IDs or secrets
|
|
59
|
+
- Orphan node detection
|
|
60
|
+
- Code node usage detection
|
|
61
|
+
- AI node structured output settings
|
|
62
|
+
- In-memory storage detection
|
|
63
|
+
- Patch-based workflow updates with parameter preservation warnings
|
package/README.md
CHANGED
|
@@ -1,47 +1,37 @@
|
|
|
1
|
-
#
|
|
1
|
+
# n8n MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Workflow validation, version control, and patch-based updates for n8n.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## The Problem
|
|
6
6
|
|
|
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
|
|
7
|
+
Other n8n MCPs replace entire nodes on update—losing parameters you didn't touch. No rollback. 70MB of SQLite for node docs you can Google.
|
|
11
8
|
|
|
12
|
-
##
|
|
9
|
+
## This MCP
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
11
|
+
| Feature | This | Others |
|
|
12
|
+
|---------|------|--------|
|
|
13
|
+
| Update approach | Patch (preserves params) | Replace (loses params) |
|
|
14
|
+
| Version control | Auto-snapshot before mutations | Manual/none |
|
|
15
|
+
| Validation | Expression syntax, circular refs, secrets | Basic |
|
|
16
|
+
| Auto-fix | snake_case, $json→$('node'), AI settings | None |
|
|
17
|
+
| Size | ~1,200 LOC, zero deps | 10k+ LOC, 70MB SQLite |
|
|
18
18
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npm install @pagelines/n8n-mcp
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Or run directly:
|
|
19
|
+
## Install
|
|
26
20
|
|
|
27
21
|
```bash
|
|
28
22
|
npx @pagelines/n8n-mcp
|
|
29
23
|
```
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
### Claude Code / Cursor
|
|
34
|
-
|
|
35
|
-
Add to your MCP settings (`~/.claude/mcp.json` or IDE config):
|
|
25
|
+
Add to `~/.claude/mcp.json`:
|
|
36
26
|
|
|
37
27
|
```json
|
|
38
28
|
{
|
|
39
29
|
"mcpServers": {
|
|
40
|
-
"
|
|
30
|
+
"n8n": {
|
|
41
31
|
"command": "npx",
|
|
42
32
|
"args": ["-y", "@pagelines/n8n-mcp"],
|
|
43
33
|
"env": {
|
|
44
|
-
"N8N_API_URL": "https://your-n8n
|
|
34
|
+
"N8N_API_URL": "https://your-n8n.com",
|
|
45
35
|
"N8N_API_KEY": "your-api-key"
|
|
46
36
|
}
|
|
47
37
|
}
|
|
@@ -49,107 +39,42 @@ Add to your MCP settings (`~/.claude/mcp.json` or IDE config):
|
|
|
49
39
|
}
|
|
50
40
|
```
|
|
51
41
|
|
|
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
42
|
## Tools
|
|
60
43
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
|
64
|
-
|
|
65
|
-
|
|
|
66
|
-
| `
|
|
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" }
|
|
44
|
+
| Category | Tools |
|
|
45
|
+
|----------|-------|
|
|
46
|
+
| Workflow | `list` `get` `create` `update` `delete` `activate` `deactivate` `execute` |
|
|
47
|
+
| Execution | `list` `get` |
|
|
48
|
+
| Validation | `validate` `autofix` `format` |
|
|
49
|
+
| Versions | `list` `get` `save` `rollback` `diff` `stats` |
|
|
98
50
|
|
|
99
|
-
|
|
100
|
-
{ "type": "removeConnection", "from": "node_a", "to": "node_b" }
|
|
51
|
+
## Validation
|
|
101
52
|
|
|
102
|
-
|
|
103
|
-
|
|
53
|
+
| Rule | Severity | Auto-fix |
|
|
54
|
+
|------|----------|----------|
|
|
55
|
+
| snake_case naming | warning | Yes |
|
|
56
|
+
| Explicit refs (`$('node')` not `$json`) | warning | Yes |
|
|
57
|
+
| AI structured output | warning | Yes |
|
|
58
|
+
| Hardcoded secrets | error | No |
|
|
59
|
+
| Orphan nodes | warning | No |
|
|
60
|
+
| Expression syntax | error | No |
|
|
61
|
+
| Circular references | error | No |
|
|
104
62
|
|
|
105
|
-
|
|
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
|
-
```
|
|
63
|
+
## Config
|
|
146
64
|
|
|
147
|
-
|
|
65
|
+
| Variable | Default | Description |
|
|
66
|
+
|----------|---------|-------------|
|
|
67
|
+
| `N8N_API_URL` | required | n8n instance URL |
|
|
68
|
+
| `N8N_API_KEY` | required | API key |
|
|
69
|
+
| `N8N_MCP_VERSIONS` | `true` | Enable version control |
|
|
70
|
+
| `N8N_MCP_MAX_VERSIONS` | `20` | Max snapshots per workflow |
|
|
148
71
|
|
|
149
|
-
|
|
72
|
+
## Docs
|
|
150
73
|
|
|
151
|
-
|
|
74
|
+
- [Best Practices](docs/best-practices.md) - Expression patterns, config nodes, AI settings
|
|
75
|
+
- [Node Config](docs/node-config.md) - Human-editable node settings (`__rl`, Set node, etc.)
|
|
76
|
+
- [Architecture](plans/architecture.md) - Technical reference
|
|
152
77
|
|
|
153
78
|
## License
|
|
154
79
|
|
|
155
|
-
MIT
|
|
80
|
+
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[][];
|