@pagelines/n8n-mcp 0.2.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 +11 -0
- package/README.md +48 -54
- package/docs/best-practices.md +78 -159
- 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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
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
|
+
|
|
5
16
|
## [0.2.0] - 2025-01-12
|
|
6
17
|
|
|
7
18
|
### Added
|
package/README.md
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
# n8n MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Workflow validation, version control, and patch-based updates for n8n.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
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.
|
|
8
|
+
|
|
9
|
+
## This MCP
|
|
10
|
+
|
|
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 |
|
|
7
18
|
|
|
8
19
|
## Install
|
|
9
20
|
|
|
@@ -11,7 +22,7 @@
|
|
|
11
22
|
npx @pagelines/n8n-mcp
|
|
12
23
|
```
|
|
13
24
|
|
|
14
|
-
Add to
|
|
25
|
+
Add to `~/.claude/mcp.json`:
|
|
15
26
|
|
|
16
27
|
```json
|
|
17
28
|
{
|
|
@@ -30,56 +41,39 @@ Add to MCP config (`~/.claude/mcp.json`):
|
|
|
30
41
|
|
|
31
42
|
## Tools
|
|
32
43
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
|
36
|
-
|
|
37
|
-
|
|
|
38
|
-
| `
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
44
|
-
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
|
49
|
-
|
|
50
|
-
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
|
57
|
-
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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 |
|
|
74
|
-
|
|
75
|
-
## Environment Variables
|
|
76
|
-
|
|
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) |
|
|
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` |
|
|
50
|
+
|
|
51
|
+
## Validation
|
|
52
|
+
|
|
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 |
|
|
62
|
+
|
|
63
|
+
## Config
|
|
64
|
+
|
|
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 |
|
|
71
|
+
|
|
72
|
+
## Docs
|
|
73
|
+
|
|
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
|
|
83
77
|
|
|
84
78
|
## License
|
|
85
79
|
|
package/docs/best-practices.md
CHANGED
|
@@ -1,107 +1,84 @@
|
|
|
1
1
|
# n8n Best Practices
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Quick Reference
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```javascript
|
|
6
|
+
// Explicit reference (always use this)
|
|
7
|
+
{{ $('node_name').item.json.field }}
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
// Environment variable
|
|
10
|
+
{{ $env.API_KEY }}
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
| Minimize nodes | Fewer failure points, easier debugging |
|
|
12
|
-
| YAGNI | Build only what's needed now |
|
|
13
|
-
| Explicit references | `$('node_name')` not `$json` - traceable, stable |
|
|
14
|
-
| snake_case | `node_name` not `NodeName` - consistent, readable |
|
|
12
|
+
// Config node reference
|
|
13
|
+
{{ $('config').item.json.setting }}
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
// Fallback
|
|
16
|
+
{{ $('source').item.json.text || 'default' }}
|
|
17
|
+
|
|
18
|
+
// Date
|
|
19
|
+
{{ $now.format('yyyy-MM-dd') }}
|
|
20
|
+
```
|
|
17
21
|
|
|
18
|
-
##
|
|
22
|
+
## The Rules
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
### 1. snake_case
|
|
21
25
|
|
|
22
26
|
```
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
Good: fetch_articles, check_approved, generate_content
|
|
28
|
+
Bad: FetchArticles, Check Approved, generate-content
|
|
25
29
|
```
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Expression References
|
|
31
|
+
Why: Consistency, readability, auto-fixable.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
### 2. Explicit References
|
|
34
34
|
|
|
35
35
|
```javascript
|
|
36
36
|
// Bad - breaks when flow changes
|
|
37
37
|
{{ $json.field }}
|
|
38
38
|
|
|
39
|
-
// Good - traceable
|
|
39
|
+
// Good - traceable, stable
|
|
40
40
|
{{ $('node_name').item.json.field }}
|
|
41
|
-
|
|
42
|
-
// Parallel branch (lookup nodes)
|
|
43
|
-
{{ $('lookup_node').all().length > 0 }}
|
|
44
|
-
|
|
45
|
-
// Environment variable
|
|
46
|
-
{{ $env.API_KEY }}
|
|
47
|
-
|
|
48
|
-
// Fallback pattern
|
|
49
|
-
{{ $('source').item.json.text || $('source').item.json.media_url }}
|
|
50
41
|
```
|
|
51
42
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
## Secrets and Configuration
|
|
55
|
-
|
|
56
|
-
**Secrets in environment variables. Always.**
|
|
57
|
-
|
|
58
|
-
```javascript
|
|
59
|
-
// Bad
|
|
60
|
-
{ "apiKey": "sk_live_abc123" }
|
|
43
|
+
Why: `$json` references "previous node" implicitly. Reorder nodes, it breaks.
|
|
61
44
|
|
|
62
|
-
|
|
63
|
-
{{ $env.API_KEY }}
|
|
64
|
-
```
|
|
45
|
+
### 3. Config Node
|
|
65
46
|
|
|
66
|
-
|
|
47
|
+
Single source for workflow settings:
|
|
67
48
|
|
|
68
49
|
```
|
|
69
50
|
[trigger] → [config] → [rest of workflow]
|
|
70
51
|
```
|
|
71
52
|
|
|
72
|
-
Config node
|
|
53
|
+
Config node (JSON mode):
|
|
73
54
|
```javascript
|
|
74
55
|
={
|
|
75
|
-
"channel_id": "{{ $json.body.channelId || '
|
|
56
|
+
"channel_id": "{{ $json.body.channelId || '123456' }}",
|
|
76
57
|
"max_items": 10,
|
|
77
58
|
"ai_model": "gpt-4.1-mini"
|
|
78
59
|
}
|
|
79
60
|
```
|
|
80
61
|
|
|
81
|
-
|
|
62
|
+
Reference everywhere: `{{ $('config').item.json.channel_id }}`
|
|
82
63
|
|
|
83
|
-
|
|
64
|
+
Why: Change once, not in 5 nodes.
|
|
84
65
|
|
|
85
|
-
|
|
66
|
+
### 4. Secrets in Environment
|
|
86
67
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
### Pre-Edit Checklist
|
|
68
|
+
```javascript
|
|
69
|
+
// Bad
|
|
70
|
+
{ "apiKey": "sk_live_abc123" }
|
|
92
71
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
4. **Make targeted change** - Use patch operations only
|
|
97
|
-
5. **Verify** - Confirm expected state
|
|
72
|
+
// Good
|
|
73
|
+
{{ $env.API_KEY }}
|
|
74
|
+
```
|
|
98
75
|
|
|
99
|
-
|
|
76
|
+
## Parameter Preservation
|
|
100
77
|
|
|
101
|
-
**
|
|
78
|
+
**Critical:** Partial updates REPLACE the entire `parameters` object.
|
|
102
79
|
|
|
103
80
|
```javascript
|
|
104
|
-
// Bad
|
|
81
|
+
// Bad - loses operation and labelIds
|
|
105
82
|
{
|
|
106
83
|
"type": "updateNode",
|
|
107
84
|
"nodeName": "archive_email",
|
|
@@ -112,7 +89,7 @@ Then reference: `{{ $('config').item.json.channel_id }}`
|
|
|
112
89
|
}
|
|
113
90
|
}
|
|
114
91
|
|
|
115
|
-
// Good
|
|
92
|
+
// Good - include ALL parameters
|
|
116
93
|
{
|
|
117
94
|
"type": "updateNode",
|
|
118
95
|
"nodeName": "archive_email",
|
|
@@ -120,122 +97,64 @@ Then reference: `{{ $('config').item.json.channel_id }}`
|
|
|
120
97
|
"parameters": {
|
|
121
98
|
"operation": "addLabels",
|
|
122
99
|
"messageId": "={{ $json.message_id }}",
|
|
123
|
-
"labelIds": ["Label_123"
|
|
100
|
+
"labelIds": ["Label_123"]
|
|
124
101
|
}
|
|
125
102
|
}
|
|
126
103
|
}
|
|
127
104
|
```
|
|
128
105
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
## Code Nodes
|
|
132
|
-
|
|
133
|
-
**Code nodes are a last resort.** Exhaust built-in options first:
|
|
134
|
-
|
|
135
|
-
| Need | Use Instead |
|
|
136
|
-
|------|-------------|
|
|
137
|
-
| Transform fields | Set node with expressions |
|
|
138
|
-
| Filter items | Filter node or If/Switch |
|
|
139
|
-
| Merge data | Merge node |
|
|
140
|
-
| Loop processing | n8n processes arrays natively |
|
|
141
|
-
| Date formatting | `{{ $now.format('yyyy-MM-dd') }}` |
|
|
142
|
-
|
|
143
|
-
**When code IS necessary:**
|
|
144
|
-
- Re-establishing `pairedItem` after chain breaks
|
|
145
|
-
- Complex conditional logic
|
|
146
|
-
- API response parsing expressions can't handle
|
|
147
|
-
|
|
148
|
-
**Code node rules:**
|
|
149
|
-
- Single responsibility (one clear purpose)
|
|
150
|
-
- Name it for what it does: `merge_context`, `parse_response`
|
|
151
|
-
- No side effects - pure data transformation
|
|
152
|
-
|
|
153
|
-
---
|
|
106
|
+
Before updating: read current state with `workflow_get`.
|
|
154
107
|
|
|
155
|
-
## AI
|
|
108
|
+
## AI Nodes
|
|
156
109
|
|
|
157
110
|
### Structured Output
|
|
158
111
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
```javascript
|
|
162
|
-
{
|
|
163
|
-
"promptType": "define",
|
|
164
|
-
"hasOutputParser": true,
|
|
165
|
-
"schemaType": "manual" // Required for nullable fields
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Without these, AI outputs are unpredictable.
|
|
112
|
+
Always set for predictable JSON:
|
|
170
113
|
|
|
171
|
-
|
|
114
|
+
| Setting | Value |
|
|
115
|
+
|---------|-------|
|
|
116
|
+
| `promptType` | `"define"` |
|
|
117
|
+
| `hasOutputParser` | `true` |
|
|
118
|
+
| `schemaType` | `"manual"` (for nullable fields) |
|
|
172
119
|
|
|
173
|
-
|
|
120
|
+
### Memory
|
|
174
121
|
|
|
175
122
|
| Don't Use | Use Instead |
|
|
176
123
|
|-----------|-------------|
|
|
177
124
|
| Windowed Buffer Memory | Postgres Chat Memory |
|
|
178
125
|
| In-Memory Vector Store | Postgres pgvector |
|
|
179
126
|
|
|
180
|
-
In-memory dies
|
|
181
|
-
|
|
182
|
-
---
|
|
183
|
-
|
|
184
|
-
## Architecture Patterns
|
|
185
|
-
|
|
186
|
-
### Single Responsibility
|
|
187
|
-
|
|
188
|
-
Don't build monolith workflows:
|
|
189
|
-
|
|
190
|
-
```
|
|
191
|
-
Bad: One workflow doing signup → email → CRM → calendar → reports
|
|
192
|
-
|
|
193
|
-
Good: Five focused workflows that communicate via webhooks
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### Switch > If Node
|
|
197
|
-
|
|
198
|
-
Always use Switch instead of If:
|
|
199
|
-
- Named outputs (not just true/false)
|
|
200
|
-
- Unlimited conditional branches
|
|
201
|
-
- Send to all matching option
|
|
202
|
-
|
|
203
|
-
---
|
|
127
|
+
In-memory dies on restart, doesn't scale.
|
|
204
128
|
|
|
205
|
-
##
|
|
129
|
+
## Code Nodes: Last Resort
|
|
206
130
|
|
|
207
|
-
|
|
|
208
|
-
|
|
209
|
-
|
|
|
210
|
-
|
|
|
211
|
-
|
|
|
212
|
-
|
|
|
213
|
-
|
|
|
214
|
-
| `parameter_preservation` | error | Update would remove parameters |
|
|
215
|
-
| `code_node_usage` | info | Code node detected |
|
|
216
|
-
| `ai_structured_output` | warning | AI node missing structured output |
|
|
217
|
-
| `in_memory_storage` | warning | Using non-persistent storage |
|
|
218
|
-
|
|
219
|
-
---
|
|
220
|
-
|
|
221
|
-
## Quick Reference
|
|
222
|
-
|
|
223
|
-
```javascript
|
|
224
|
-
// Explicit reference
|
|
225
|
-
{{ $('node_name').item.json.field }}
|
|
226
|
-
|
|
227
|
-
// Environment variable
|
|
228
|
-
{{ $env.VAR_NAME }}
|
|
229
|
-
|
|
230
|
-
// Config node reference
|
|
231
|
-
{{ $('config').item.json.setting }}
|
|
131
|
+
| Need | Use Instead |
|
|
132
|
+
|------|-------------|
|
|
133
|
+
| Transform fields | Set node with expressions |
|
|
134
|
+
| Filter items | Filter node or Switch |
|
|
135
|
+
| Merge data | Merge node |
|
|
136
|
+
| Loop | n8n processes arrays natively |
|
|
137
|
+
| Date formatting | `{{ $now.format('yyyy-MM-dd') }}` |
|
|
232
138
|
|
|
233
|
-
|
|
234
|
-
|
|
139
|
+
When code IS necessary:
|
|
140
|
+
- Re-establishing `pairedItem` after chain breaks
|
|
141
|
+
- Complex conditional logic
|
|
142
|
+
- API parsing expressions can't handle
|
|
235
143
|
|
|
236
|
-
|
|
237
|
-
{{ $now.format('yyyy-MM-dd') }}
|
|
144
|
+
## Pre-Edit Checklist
|
|
238
145
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
146
|
+
| Step | Why |
|
|
147
|
+
|------|-----|
|
|
148
|
+
| 1. Get explicit user approval | Don't surprise |
|
|
149
|
+
| 2. List versions | Know rollback point |
|
|
150
|
+
| 3. Read full workflow | Understand current state |
|
|
151
|
+
| 4. Make targeted change | Minimal surface area |
|
|
152
|
+
| 5. Validate after | Catch issues immediately |
|
|
153
|
+
|
|
154
|
+
## Node-Specific Settings
|
|
155
|
+
|
|
156
|
+
See [Node Config](node-config.md) for:
|
|
157
|
+
- Resource locator (`__rl`) format with `cachedResultName`
|
|
158
|
+
- Google Sheets, Gmail, Discord required fields
|
|
159
|
+
- Set node JSON mode vs manual mapping
|
|
160
|
+
- Error handling options
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Node Configuration Guidelines
|
|
2
|
+
|
|
3
|
+
> Settings that make AI-created nodes human-editable
|
|
4
|
+
|
|
5
|
+
## Resource Locator Pattern
|
|
6
|
+
|
|
7
|
+
n8n uses `__rl` (resource locator) format for dropdown fields. Always include `cachedResultName` so humans see friendly names.
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// Human-editable: shows "My Spreadsheet" in UI
|
|
11
|
+
"documentId": {
|
|
12
|
+
"__rl": true,
|
|
13
|
+
"value": "1abc123...",
|
|
14
|
+
"mode": "list",
|
|
15
|
+
"cachedResultName": "My Spreadsheet",
|
|
16
|
+
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1abc123"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Hard to edit: shows raw ID only
|
|
20
|
+
"documentId": {
|
|
21
|
+
"__rl": true,
|
|
22
|
+
"value": "1abc123...",
|
|
23
|
+
"mode": "id"
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Mode | When to Use |
|
|
28
|
+
|------|-------------|
|
|
29
|
+
| `list` | Default. Shows dropdown with cached name |
|
|
30
|
+
| `id` | Only when ID is dynamic (from expression) |
|
|
31
|
+
|
|
32
|
+
## Set Node
|
|
33
|
+
|
|
34
|
+
**Use JSON mode** for MCP-created nodes. Manual mapping is error-prone via API.
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// JSON mode (reliable)
|
|
38
|
+
{
|
|
39
|
+
"mode": "raw",
|
|
40
|
+
"jsonOutput": "={ \"channel_id\": \"{{ $json.channelId || '123' }}\" }"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Manual mapping (fragile via API)
|
|
44
|
+
{
|
|
45
|
+
"mode": "manual",
|
|
46
|
+
"assignments": { ... } // Complex structure, easy to break
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Google Sheets
|
|
51
|
+
|
|
52
|
+
Required fields (partial updates lose these):
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
{
|
|
56
|
+
"operation": "append",
|
|
57
|
+
"documentId": {
|
|
58
|
+
"__rl": true,
|
|
59
|
+
"value": "1abc...",
|
|
60
|
+
"mode": "list",
|
|
61
|
+
"cachedResultName": "Sheet Name"
|
|
62
|
+
},
|
|
63
|
+
"sheetName": {
|
|
64
|
+
"__rl": true,
|
|
65
|
+
"value": "gid=0",
|
|
66
|
+
"mode": "list",
|
|
67
|
+
"cachedResultName": "Sheet1"
|
|
68
|
+
},
|
|
69
|
+
"columns": { /* mappings */ }
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Gmail
|
|
74
|
+
|
|
75
|
+
Required fields:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
{
|
|
79
|
+
"operation": "addLabels", // or send, get, etc.
|
|
80
|
+
"messageId": "={{ $('source').item.json.id }}",
|
|
81
|
+
"labelIds": ["Label_123", "Label_456"]
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Labels need full IDs (`Label_xxx`), not display names.
|
|
86
|
+
|
|
87
|
+
## Discord
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
{
|
|
91
|
+
"authentication": "webhook",
|
|
92
|
+
"webhookUri": {
|
|
93
|
+
"__rl": true,
|
|
94
|
+
"value": "={{ $env.DISCORD_WEBHOOK }}",
|
|
95
|
+
"mode": "id"
|
|
96
|
+
},
|
|
97
|
+
"content": "Message text"
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For bot mode with guild/channel selection:
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
{
|
|
105
|
+
"guildId": {
|
|
106
|
+
"__rl": true,
|
|
107
|
+
"value": "123456789",
|
|
108
|
+
"mode": "list",
|
|
109
|
+
"cachedResultName": "My Server"
|
|
110
|
+
},
|
|
111
|
+
"channelId": {
|
|
112
|
+
"__rl": true,
|
|
113
|
+
"value": "987654321",
|
|
114
|
+
"mode": "list",
|
|
115
|
+
"cachedResultName": "#general"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## AI Nodes (Agent, Chain)
|
|
121
|
+
|
|
122
|
+
Always include structured output settings:
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
{
|
|
126
|
+
"promptType": "define",
|
|
127
|
+
"hasOutputParser": true,
|
|
128
|
+
"schemaType": "manual", // Required for nullable fields
|
|
129
|
+
"schema": { /* JSON Schema */ }
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Without these, AI outputs are unpredictable strings.
|
|
134
|
+
|
|
135
|
+
## HTTP Request
|
|
136
|
+
|
|
137
|
+
Include authentication method explicitly:
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
{
|
|
141
|
+
"method": "POST",
|
|
142
|
+
"url": "https://api.example.com",
|
|
143
|
+
"authentication": "predefinedCredentialType",
|
|
144
|
+
"nodeCredentialType": "anthropicApi",
|
|
145
|
+
"sendBody": true,
|
|
146
|
+
"bodyParameters": { ... }
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Switch vs If
|
|
151
|
+
|
|
152
|
+
Always use Switch (not If):
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
// Switch: named outputs, extensible
|
|
156
|
+
{
|
|
157
|
+
"type": "n8n-nodes-base.switch",
|
|
158
|
+
"parameters": {
|
|
159
|
+
"rules": {
|
|
160
|
+
"rules": [
|
|
161
|
+
{ "output": 0, "conditions": { ... } },
|
|
162
|
+
{ "output": 1, "conditions": { ... } }
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
If node only has true/false outputs - Switch scales better.
|
|
170
|
+
|
|
171
|
+
## Expression Patterns
|
|
172
|
+
|
|
173
|
+
Always explicit references:
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
// Good
|
|
177
|
+
"={{ $('config').item.json.channel_id }}"
|
|
178
|
+
|
|
179
|
+
// Bad - breaks on node reorder
|
|
180
|
+
"={{ $json.channel_id }}"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Environment variables for secrets:
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
"={{ $env.API_KEY }}"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Error Handling
|
|
190
|
+
|
|
191
|
+
Set `onError` for API nodes:
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
{
|
|
195
|
+
"parameters": { ... },
|
|
196
|
+
"onError": "continueRegularOutput" // or "continueErrorOutput"
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Options:
|
|
201
|
+
- `stopWorkflow` - default, halts on error
|
|
202
|
+
- `continueRegularOutput` - ignore errors, continue
|
|
203
|
+
- `continueErrorOutput` - route errors to second output
|
package/package.json
CHANGED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# AI Guidelines for @pagelines/n8n-mcp
|
|
2
|
+
|
|
3
|
+
Entry point for AI assistants working on this codebase.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev # Watch mode
|
|
9
|
+
npm run test # Run tests
|
|
10
|
+
npm run build # Compile TypeScript
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Codebase Map
|
|
14
|
+
|
|
15
|
+
| File | Purpose | When to modify |
|
|
16
|
+
|------|---------|----------------|
|
|
17
|
+
| `src/index.ts` | MCP server, tool dispatch | Adding new tools |
|
|
18
|
+
| `src/types.ts` | Type definitions | New data structures |
|
|
19
|
+
| `src/tools.ts` | Tool JSON schemas | New tool definitions |
|
|
20
|
+
| `src/n8n-client.ts` | n8n REST API calls | New API operations |
|
|
21
|
+
| `src/validators.ts` | Validation rules | New lint rules |
|
|
22
|
+
| `src/expressions.ts` | Expression parsing | Expression handling |
|
|
23
|
+
| `src/autofix.ts` | Auto-fix transforms | New auto-fixes |
|
|
24
|
+
| `src/versions.ts` | Version control | Version features |
|
|
25
|
+
|
|
26
|
+
## Coding Standards
|
|
27
|
+
|
|
28
|
+
### Do
|
|
29
|
+
- Write pure functions that return values
|
|
30
|
+
- Use async/await, never callbacks
|
|
31
|
+
- Add types for all parameters and returns
|
|
32
|
+
- Keep functions under 50 lines
|
|
33
|
+
- Name variables descriptively
|
|
34
|
+
- Write tests for new validators
|
|
35
|
+
|
|
36
|
+
### Don't
|
|
37
|
+
- Use `any` type
|
|
38
|
+
- Mutate input parameters
|
|
39
|
+
- Add external dependencies without strong justification
|
|
40
|
+
- Create abstraction before duplication (rule of 3)
|
|
41
|
+
- Add features not explicitly requested
|
|
42
|
+
|
|
43
|
+
## Adding a New Tool
|
|
44
|
+
|
|
45
|
+
1. **Define type** in `types.ts` (if needed)
|
|
46
|
+
2. **Add schema** in `tools.ts`:
|
|
47
|
+
```typescript
|
|
48
|
+
{
|
|
49
|
+
name: 'my_tool',
|
|
50
|
+
description: 'What it does',
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: { /* ... */ },
|
|
54
|
+
required: ['param1']
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
3. **Add handler** in `index.ts`:
|
|
59
|
+
```typescript
|
|
60
|
+
case 'my_tool':
|
|
61
|
+
return await handleMyTool(args);
|
|
62
|
+
```
|
|
63
|
+
4. **Implement logic** in appropriate module
|
|
64
|
+
5. **Add test** in `*.test.ts`
|
|
65
|
+
|
|
66
|
+
## Adding a Validation Rule
|
|
67
|
+
|
|
68
|
+
1. **Add rule function** in `validators.ts`:
|
|
69
|
+
```typescript
|
|
70
|
+
function checkMyRule(workflow: N8nWorkflow): ValidationWarning[] {
|
|
71
|
+
const warnings: ValidationWarning[] = [];
|
|
72
|
+
// validation logic
|
|
73
|
+
if (problem) {
|
|
74
|
+
warnings.push({
|
|
75
|
+
rule: 'my_rule',
|
|
76
|
+
severity: 'warning', // or 'error' | 'info'
|
|
77
|
+
message: 'Description of issue',
|
|
78
|
+
nodeId: node.id,
|
|
79
|
+
nodeName: node.name,
|
|
80
|
+
suggestion: 'How to fix'
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return warnings;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
2. **Add to composition** in `validateWorkflow()`:
|
|
88
|
+
```typescript
|
|
89
|
+
...checkMyRule(workflow),
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
3. **Add test** in `validators.test.ts`:
|
|
93
|
+
```typescript
|
|
94
|
+
it('detects my_rule violations', () => {
|
|
95
|
+
const workflow = createWorkflow({
|
|
96
|
+
// problematic config
|
|
97
|
+
});
|
|
98
|
+
const warnings = validateWorkflow(workflow);
|
|
99
|
+
expect(warnings).toContainEqual(
|
|
100
|
+
expect.objectContaining({ rule: 'my_rule' })
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Adding an Auto-Fix
|
|
106
|
+
|
|
107
|
+
1. **Check if fixable** - some issues require human judgment
|
|
108
|
+
2. **Add fix logic** in `autofix.ts`:
|
|
109
|
+
```typescript
|
|
110
|
+
if (warning.rule === 'my_rule') {
|
|
111
|
+
// transform workflow
|
|
112
|
+
fixed.push(warning);
|
|
113
|
+
} else {
|
|
114
|
+
unfixed.push(warning);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
3. **Update references** if renaming anything
|
|
118
|
+
4. **Test both dry-run and apply modes**
|
|
119
|
+
|
|
120
|
+
## Patterns to Follow
|
|
121
|
+
|
|
122
|
+
### Safe Mutations
|
|
123
|
+
```typescript
|
|
124
|
+
// Always clone before mutating
|
|
125
|
+
const copy = JSON.parse(JSON.stringify(original));
|
|
126
|
+
// Modify copy, return copy
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Async Operations
|
|
130
|
+
```typescript
|
|
131
|
+
// All file/network operations are async
|
|
132
|
+
async function doThing(): Promise<Result> {
|
|
133
|
+
const data = await fetchData();
|
|
134
|
+
return transform(data);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Error Responses
|
|
139
|
+
```typescript
|
|
140
|
+
// MCP error format
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: 'text', text: 'Error message' }],
|
|
143
|
+
isError: true
|
|
144
|
+
};
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Validation Returns
|
|
148
|
+
```typescript
|
|
149
|
+
// Always return array (empty if valid)
|
|
150
|
+
function validate(): ValidationWarning[] {
|
|
151
|
+
const warnings: ValidationWarning[] = [];
|
|
152
|
+
// ... checks ...
|
|
153
|
+
return warnings;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Testing Patterns
|
|
158
|
+
|
|
159
|
+
### Fixture Factory
|
|
160
|
+
```typescript
|
|
161
|
+
function createWorkflow(overrides: Partial<N8nWorkflow> = {}): N8nWorkflow {
|
|
162
|
+
return {
|
|
163
|
+
id: 'test-id',
|
|
164
|
+
name: 'test_workflow',
|
|
165
|
+
nodes: [],
|
|
166
|
+
connections: {},
|
|
167
|
+
...overrides
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Assertion Patterns
|
|
173
|
+
```typescript
|
|
174
|
+
// Check warning exists
|
|
175
|
+
expect(warnings).toContainEqual(
|
|
176
|
+
expect.objectContaining({ rule: 'rule_name' })
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Check no warnings
|
|
180
|
+
expect(warnings).toHaveLength(0);
|
|
181
|
+
|
|
182
|
+
// Check severity
|
|
183
|
+
expect(warnings[0].severity).toBe('error');
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Out of Scope
|
|
187
|
+
|
|
188
|
+
Do not implement:
|
|
189
|
+
- Node documentation/search (use web search)
|
|
190
|
+
- Template marketplace features
|
|
191
|
+
- Credential management
|
|
192
|
+
- Multi-instance sync
|
|
193
|
+
- Real-time execution monitoring
|
|
194
|
+
|
|
195
|
+
These are either security risks or solved problems (Google, n8n UI).
|
|
196
|
+
|
|
197
|
+
## Questions to Ask
|
|
198
|
+
|
|
199
|
+
Before adding a feature:
|
|
200
|
+
1. Does this serve workflow editing/validation/versioning?
|
|
201
|
+
2. Can this be solved with existing web search?
|
|
202
|
+
3. Does this add runtime dependencies?
|
|
203
|
+
4. Is there duplication that justifies abstraction?
|
|
204
|
+
|
|
205
|
+
If any answer suggests bloat, reconsider.
|
|
206
|
+
|
|
207
|
+
## File Locations
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
~/.n8n-mcp/versions/ # Version control storage
|
|
211
|
+
{workflowId}/
|
|
212
|
+
{timestamp}_{hash}.json
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Environment Variables
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
N8N_API_URL=http://localhost:5678 # n8n instance URL
|
|
219
|
+
N8N_API_KEY=your-api-key # n8n API key
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Debugging
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# Run with verbose output
|
|
226
|
+
DEBUG=* npm run dev
|
|
227
|
+
|
|
228
|
+
# Test single file
|
|
229
|
+
npx vitest run src/validators.test.ts
|
|
230
|
+
|
|
231
|
+
# Watch single test
|
|
232
|
+
npx vitest watch src/validators.test.ts
|
|
233
|
+
```
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## vs czlonkowski/n8n-mcp
|
|
4
|
+
|
|
5
|
+
| Concern | czlonkowski | @pagelines |
|
|
6
|
+
|---------|-------------|------------|
|
|
7
|
+
| Node docs | 70MB SQLite, 1084 nodes | None (use Google) |
|
|
8
|
+
| Templates | 2,709 indexed | None (use n8n.io) |
|
|
9
|
+
| Update mode | Full replace | Patch (preserves params) |
|
|
10
|
+
| Version control | Limited | Auto-snapshot, diff, rollback |
|
|
11
|
+
| Validation | Basic | Rules + expressions + circular refs |
|
|
12
|
+
| Auto-fix | No | Yes |
|
|
13
|
+
| Dependencies | SQLite, heavy | Zero runtime |
|
|
14
|
+
| Lines of code | ~10k+ | ~1,200 |
|
|
15
|
+
|
|
16
|
+
## Modules
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
src/
|
|
20
|
+
├── index.ts # MCP server, tool dispatch
|
|
21
|
+
├── types.ts # Type definitions
|
|
22
|
+
├── tools.ts # Tool schemas (JSON Schema)
|
|
23
|
+
├── n8n-client.ts # n8n REST API client
|
|
24
|
+
├── validators.ts # Validation rules
|
|
25
|
+
├── expressions.ts # Expression parsing ({{ }})
|
|
26
|
+
├── autofix.ts # Auto-fix transforms
|
|
27
|
+
└── versions.ts # Version control (local fs)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Data Flow
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Claude Request
|
|
34
|
+
↓
|
|
35
|
+
MCP Server (stdio)
|
|
36
|
+
↓
|
|
37
|
+
Tool Handler
|
|
38
|
+
↓
|
|
39
|
+
┌─────────────────────────────────┐
|
|
40
|
+
│ n8n-client validators │
|
|
41
|
+
│ expressions autofix │
|
|
42
|
+
│ versions │
|
|
43
|
+
└─────────────────────────────────┘
|
|
44
|
+
↓
|
|
45
|
+
JSON Response → Claude
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Tools (19 total)
|
|
49
|
+
|
|
50
|
+
### Workflow (8)
|
|
51
|
+
| Tool | Description |
|
|
52
|
+
|------|-------------|
|
|
53
|
+
| `workflow_list` | List workflows, filter by active |
|
|
54
|
+
| `workflow_get` | Get full workflow |
|
|
55
|
+
| `workflow_create` | Create with nodes/connections |
|
|
56
|
+
| `workflow_update` | Patch operations |
|
|
57
|
+
| `workflow_delete` | Delete workflow |
|
|
58
|
+
| `workflow_activate` | Enable triggers |
|
|
59
|
+
| `workflow_deactivate` | Disable triggers |
|
|
60
|
+
| `workflow_execute` | Trigger via webhook |
|
|
61
|
+
|
|
62
|
+
### Execution (2)
|
|
63
|
+
| Tool | Description |
|
|
64
|
+
|------|-------------|
|
|
65
|
+
| `execution_list` | List executions, filter by status |
|
|
66
|
+
| `execution_get` | Get execution with run data |
|
|
67
|
+
|
|
68
|
+
### Validation (3)
|
|
69
|
+
| Tool | Description |
|
|
70
|
+
|------|-------------|
|
|
71
|
+
| `workflow_validate` | Check rules + expressions + circular refs |
|
|
72
|
+
| `workflow_autofix` | Fix auto-fixable issues (dry-run default) |
|
|
73
|
+
| `workflow_format` | Sort nodes, clean nulls |
|
|
74
|
+
|
|
75
|
+
### Version Control (6)
|
|
76
|
+
| Tool | Description |
|
|
77
|
+
|------|-------------|
|
|
78
|
+
| `version_list` | List snapshots for workflow |
|
|
79
|
+
| `version_get` | Get specific version |
|
|
80
|
+
| `version_save` | Manual snapshot with reason |
|
|
81
|
+
| `version_rollback` | Restore previous (auto-saves current first) |
|
|
82
|
+
| `version_diff` | Compare versions |
|
|
83
|
+
| `version_stats` | Version control statistics |
|
|
84
|
+
|
|
85
|
+
## Patch Operations
|
|
86
|
+
|
|
87
|
+
`workflow_update` accepts these operation types:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
addNode, removeNode, updateNode
|
|
91
|
+
addConnection, removeConnection
|
|
92
|
+
updateSettings, updateName
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Key: Preserves unmodified parameters.
|
|
96
|
+
|
|
97
|
+
## Validation Rules
|
|
98
|
+
|
|
99
|
+
| Rule | Severity | Description |
|
|
100
|
+
|------|----------|-------------|
|
|
101
|
+
| `snake_case` | warning | Names should be snake_case |
|
|
102
|
+
| `explicit_reference` | warning | Use `$('node')` not `$json` |
|
|
103
|
+
| `no_hardcoded_ids` | info | Avoid hardcoded IDs |
|
|
104
|
+
| `no_hardcoded_secrets` | error | Never hardcode secrets |
|
|
105
|
+
| `code_node_usage` | info | Code node detected |
|
|
106
|
+
| `ai_structured_output` | warning | AI node missing structured output |
|
|
107
|
+
| `in_memory_storage` | warning | Non-persistent storage |
|
|
108
|
+
| `orphan_node` | warning | Node has no connections |
|
|
109
|
+
| `node_exists` | error | Node doesn't exist (for updates) |
|
|
110
|
+
| `parameter_preservation` | error | Update would lose parameters |
|
|
111
|
+
|
|
112
|
+
## Expression Validation
|
|
113
|
+
|
|
114
|
+
| Issue | Severity | Pattern |
|
|
115
|
+
|-------|----------|---------|
|
|
116
|
+
| Uses `$json` | warning | `$json.` without `$('` prefix |
|
|
117
|
+
| Uses `$input` | info | `$input.` usage |
|
|
118
|
+
| Missing node reference | error | `$('node')` where node doesn't exist |
|
|
119
|
+
| Unclosed `}}` | error | Unmatched delimiters |
|
|
120
|
+
| Unbalanced parens/brackets | error | `(` vs `)`, `[` vs `]` |
|
|
121
|
+
| Deprecated `$node` | warning | `$node.` pattern |
|
|
122
|
+
| Deep property without `?.` | info | Missing optional chaining |
|
|
123
|
+
|
|
124
|
+
Plus: **Circular reference detection** across all expressions.
|
|
125
|
+
|
|
126
|
+
## Auto-Fix
|
|
127
|
+
|
|
128
|
+
| Rule | Transform |
|
|
129
|
+
|------|-----------|
|
|
130
|
+
| `snake_case` | Rename + update all references |
|
|
131
|
+
| `explicit_reference` | `$json.x` → `$('prev_node').item.json.x` |
|
|
132
|
+
| `ai_structured_output` | Add `promptType: "define"`, `hasOutputParser: true` |
|
|
133
|
+
|
|
134
|
+
**Not fixable** (require judgment): secrets, hardcoded IDs, orphan nodes, code nodes, memory storage.
|
|
135
|
+
|
|
136
|
+
## Version Control
|
|
137
|
+
|
|
138
|
+
Storage: `~/.n8n-mcp/versions/{workflowId}/{timestamp}_{hash}.json`
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"id": "1704067200000_abc123",
|
|
143
|
+
"workflowId": "123",
|
|
144
|
+
"timestamp": "2024-01-01T00:00:00.000Z",
|
|
145
|
+
"reason": "before update",
|
|
146
|
+
"metadata": {
|
|
147
|
+
"name": "my_workflow",
|
|
148
|
+
"nodeCount": 5,
|
|
149
|
+
"contentHash": "abc123"
|
|
150
|
+
},
|
|
151
|
+
"workflow": { /* full snapshot */ }
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Features:
|
|
156
|
+
- Hash-based deduplication (no duplicate saves)
|
|
157
|
+
- Max versions pruning (default: 20)
|
|
158
|
+
- Auto-save before `workflow_update` and `version_rollback`
|
|
159
|
+
|
|
160
|
+
## Extension
|
|
161
|
+
|
|
162
|
+
Add validation rule:
|
|
163
|
+
```typescript
|
|
164
|
+
// validators.ts
|
|
165
|
+
function checkMyRule(workflow: N8nWorkflow): ValidationWarning[] {
|
|
166
|
+
// return warnings
|
|
167
|
+
}
|
|
168
|
+
// Add to validateWorkflow()
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Add auto-fix:
|
|
172
|
+
```typescript
|
|
173
|
+
// autofix.ts
|
|
174
|
+
if (warning.rule === 'my_rule' && fixable) {
|
|
175
|
+
// transform workflow
|
|
176
|
+
}
|
|
177
|
+
```
|