@gmag11/nodered-mcp-server 1.0.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/LICENSE +201 -0
- package/README.md +162 -0
- package/index.js +133 -0
- package/package.json +58 -0
- package/resources/skills/nodered-flow-builder/SKILL.md +659 -0
- package/resources/skills/nodered-flow-layout/SKILL.md +395 -0
- package/resources/skills/nodered-flowfuse-dashboard/SKILL.md +941 -0
- package/resources/skills/nodered-fundamentals/SKILL.md +323 -0
- package/resources/skills/nodered-jsonata/SKILL.md +1039 -0
- package/resources/skills/nodered-mustache/SKILL.md +588 -0
- package/resources/skills/nodered-node-reference/SKILL.md +1020 -0
- package/resources/skills/nodered-node-reference/examples/common.json +113 -0
- package/resources/skills/nodered-node-reference/examples/network.json +107 -0
- package/resources/skills/nodered-node-reference/examples/parser.json +147 -0
- package/resources/skills/nodered-node-reference/examples/sequence.json +141 -0
- package/resources/skills/nodered-node-reference/examples/storage.json +104 -0
- package/resources/skills/nodered-patterns/SKILL.md +414 -0
- package/resources/skills/nodered-patterns/examples/error-handler.json +72 -0
- package/resources/skills/nodered-patterns/examples/http-endpoint.json +42 -0
- package/resources/skills/nodered-patterns/examples/mqtt-subscriber.json +47 -0
- package/resources/skills/nodered-patterns/examples/timer-flow.json +50 -0
- package/resources/skills/nodered-subflows/SKILL.md +261 -0
- package/resources/skills/nodered-uibuilder/SKILL.md +500 -0
- package/src/auth/api-key-verifier.js +36 -0
- package/src/auth/composite-verifier.js +59 -0
- package/src/auth/config.js +106 -0
- package/src/auth/oauth-clients-store.js +107 -0
- package/src/auth/oauth-provider.js +149 -0
- package/src/auth/oauth-token-store.js +312 -0
- package/src/nodered/auth.js +158 -0
- package/src/nodered/client.js +199 -0
- package/src/nodered/comms-client.js +500 -0
- package/src/renderer/colors.js +161 -0
- package/src/renderer/geometry.js +115 -0
- package/src/renderer/html-builder.js +571 -0
- package/src/renderer/index.js +51 -0
- package/src/renderer/ir-builder.js +161 -0
- package/src/renderer/layout.js +126 -0
- package/src/renderer/mermaid-builder.js +109 -0
- package/src/renderer/svg-builder.js +228 -0
- package/src/schemas/responses.js +283 -0
- package/src/server.js +844 -0
- package/src/skills/loader.js +84 -0
- package/src/staging-store.js +258 -0
- package/src/tools/add-nodes-to-group.js +216 -0
- package/src/tools/connect-nodes.js +115 -0
- package/src/tools/constants.js +45 -0
- package/src/tools/create-flow.js +87 -0
- package/src/tools/create-node.js +126 -0
- package/src/tools/create-subflow-instance.js +123 -0
- package/src/tools/create-subflow.js +101 -0
- package/src/tools/delete-context.js +60 -0
- package/src/tools/delete-flow.js +81 -0
- package/src/tools/delete-group.js +116 -0
- package/src/tools/delete-node.js +73 -0
- package/src/tools/delete-subflow.js +103 -0
- package/src/tools/deploy.js +94 -0
- package/src/tools/disconnect-nodes.js +158 -0
- package/src/tools/export-flow.js +161 -0
- package/src/tools/export-subflow.js +78 -0
- package/src/tools/flow-utils.js +376 -0
- package/src/tools/get-config-nodes.js +86 -0
- package/src/tools/get-context.js +76 -0
- package/src/tools/get-flow-diagram.js +99 -0
- package/src/tools/get-flow-nodes.js +116 -0
- package/src/tools/get-flows.js +74 -0
- package/src/tools/get-node-detail.js +77 -0
- package/src/tools/get-node-type-detail.js +92 -0
- package/src/tools/get-palette-nodes.js +63 -0
- package/src/tools/get-staging-status.js +34 -0
- package/src/tools/get-subflow-detail.js +110 -0
- package/src/tools/get-subflows.js +105 -0
- package/src/tools/import-flow.js +310 -0
- package/src/tools/inject-message.js +117 -0
- package/src/tools/install-node.js +31 -0
- package/src/tools/read-debug-messages.js +155 -0
- package/src/tools/refresh-staging.js +62 -0
- package/src/tools/remove-nodes-from-group.js +162 -0
- package/src/tools/render-staging.js +69 -0
- package/src/tools/response-utils.js +42 -0
- package/src/tools/search-nodes.js +134 -0
- package/src/tools/uninstall-node.js +31 -0
- package/src/tools/update-flow.js +95 -0
- package/src/tools/update-group.js +77 -0
- package/src/tools/update-node.js +132 -0
- package/src/tools/update-subflow.js +84 -0
- package/src/transport/http.js +252 -0
- package/src/transport/stdio.js +16 -0
- package/src/transport/ws-server.js +223 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nodered-flow-builder
|
|
3
|
+
description: >-
|
|
4
|
+
Step-by-step operational guide for building, editing, testing, and debugging Node-RED flows using MCP tools.
|
|
5
|
+
tools:
|
|
6
|
+
- refresh-staging
|
|
7
|
+
- deploy
|
|
8
|
+
- get-staging-status
|
|
9
|
+
- get-flows
|
|
10
|
+
- get-config-nodes
|
|
11
|
+
- get-subflows
|
|
12
|
+
- get-flow-nodes
|
|
13
|
+
- get-flow-diagram
|
|
14
|
+
- get-node-detail
|
|
15
|
+
- create-flow
|
|
16
|
+
- update-flow
|
|
17
|
+
- delete-flow
|
|
18
|
+
- create-node
|
|
19
|
+
- update-node
|
|
20
|
+
- delete-node
|
|
21
|
+
- create-subflow
|
|
22
|
+
- update-subflow
|
|
23
|
+
- delete-subflow
|
|
24
|
+
- create-subflow-instance
|
|
25
|
+
- add-nodes-to-group
|
|
26
|
+
- remove-nodes-from-group
|
|
27
|
+
- update-group
|
|
28
|
+
- connect-nodes
|
|
29
|
+
- disconnect-nodes
|
|
30
|
+
- export-flow
|
|
31
|
+
- import-flow
|
|
32
|
+
- inject-message
|
|
33
|
+
- read-debug-messages
|
|
34
|
+
- get-context
|
|
35
|
+
- search-nodes
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
# Node-RED Flow Builder
|
|
39
|
+
|
|
40
|
+
Step-by-step operational guide for building, editing, testing, and debugging Node-RED flows via MCP tools. Follow the numbered sequences exactly.
|
|
41
|
+
|
|
42
|
+
> **Prerequisites:** Read `nodered-fundamentals` first for core vocabulary. Use `nodered-node-reference` for node type properties.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 🔄 ALWAYS Sync Before Editing
|
|
47
|
+
|
|
48
|
+
**Before starting ANY workflow** (create, edit, delete, import), you MUST sync the staging state with the server. Follow this two-step sequence in order:
|
|
49
|
+
|
|
50
|
+
### Step 0a: Refresh staging (MANDATORY — do this FIRST)
|
|
51
|
+
```
|
|
52
|
+
refresh-staging()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
⚠️ **CRITICAL:** This is the very first operation before ANY editing session. It discards any stale un-deployed changes and re-fetches the latest flow state from Node-RED. This prevents version mismatch errors (HTTP 409) when deploying. If you skip this step and the Node-RED editor has been used to modify flows, your deploy will fail and ALL your staged changes will be lost.
|
|
56
|
+
|
|
57
|
+
> **When to refresh:** Always call `refresh-staging` at the start of a new editing session. After deploy, staging is automatically synced — no manual refresh needed.
|
|
58
|
+
|
|
59
|
+
### Step 0b: Read current state
|
|
60
|
+
```
|
|
61
|
+
get-flows()
|
|
62
|
+
get-config-nodes()
|
|
63
|
+
get-subflows()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This returns the current list of flow tabs, their IDs, labels, node counts, lock status, config nodes, and subflows. Use this information to:
|
|
67
|
+
- Confirm the target flow exists and get its correct `flowId`
|
|
68
|
+
- Check if a flow is **locked** before attempting edits (locked flows reject modifications)
|
|
69
|
+
- Identify which flow to work on when the user refers to it by name
|
|
70
|
+
- Discover existing config nodes (groups, brokers, etc.) and subflows
|
|
71
|
+
|
|
72
|
+
**After every `deploy`**, the staging store automatically re-fetches flows from the server — you do NOT need to call `refresh-staging` or `get-flows` manually after deploy. The internal state is already synced.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Workflow A — Build a Flow from Scratch
|
|
77
|
+
|
|
78
|
+
The primary workflow for creating a new flow. Follow these steps in order:
|
|
79
|
+
|
|
80
|
+
### Step 1: Create a flow tab
|
|
81
|
+
```
|
|
82
|
+
create-flow(label: "My Flow", info: "Description of what this flow does")
|
|
83
|
+
```
|
|
84
|
+
Save the returned `flowId` — you need it for every subsequent step.
|
|
85
|
+
|
|
86
|
+
### Step 2: Create nodes (one by one)
|
|
87
|
+
```
|
|
88
|
+
create-node(type: "inject", flowId: "<flowId>", properties: { name: "Trigger", payload: "hello", payloadType: "str" }, x: 100, y: 100)
|
|
89
|
+
create-node(type: "function", flowId: "<flowId>", properties: { name: "Transform", func: "msg.payload = msg.payload.toUpperCase();\nreturn msg;", outputs: 1 }, x: 300, y: 100)
|
|
90
|
+
create-node(type: "debug", flowId: "<flowId>", properties: { name: "Output", complete: "payload", targetType: "msg" }, x: 500, y: 100)
|
|
91
|
+
```
|
|
92
|
+
Save all returned `nodeId` values.
|
|
93
|
+
|
|
94
|
+
**💡 Setting a node description:** Add `info` to the `properties` object:
|
|
95
|
+
```
|
|
96
|
+
create-node(type: "ping", flowId: "<flowId>", properties: { name: "Ping", host: "192.168.1.1", info: "Pings the main server" }, x: 300, y: 100)
|
|
97
|
+
```
|
|
98
|
+
The `info` field corresponds to the **Description** shown in the Node-RED editor UI. When a user asks to "add a description" or "describe the node", they mean setting `info`.
|
|
99
|
+
|
|
100
|
+
**💡 Creating comment nodes:** Use `type: "comment"` to annotate flows. The `name` field is a **short label** (1-3 words, visible on canvas). Use `info` for detailed documentation (tooltip on hover):
|
|
101
|
+
```
|
|
102
|
+
create-node(type: "comment", flowId: "<flowId>", properties: { name: "My Section", info: "Detailed notes about this section of the flow" }, x: 100, y: 300)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**💡 Switch nodes — always set `outputs` explicitly:**
|
|
106
|
+
- **switch:** Set `outputs` to `rules.length`. Without it, the editor defaults to 1 visible port even if wires are connected. Also set `repair: false` and `checkall: "false"` (string, not boolean).
|
|
107
|
+
|
|
108
|
+
### Step 3: Wire nodes together
|
|
109
|
+
```
|
|
110
|
+
connect-nodes(fromNodeId: "<injectId>", outputPort: 0, toNodeId: "<functionId>")
|
|
111
|
+
connect-nodes(fromNodeId: "<functionId>", outputPort: 0, toNodeId: "<debugId>")
|
|
112
|
+
```
|
|
113
|
+
**Critical:** Always wire after creating nodes. An unwired node is isolated and will never receive messages.
|
|
114
|
+
|
|
115
|
+
### Step 4: Verify the flow
|
|
116
|
+
```
|
|
117
|
+
get-flow-diagram(flowId: "<flowId>")
|
|
118
|
+
```
|
|
119
|
+
Review the Mermaid diagram. Confirm all nodes appear and wires connect as expected.
|
|
120
|
+
|
|
121
|
+
### Step 5: Deploy changes
|
|
122
|
+
**CRITICAL — Changes are staged, not live!** All create-node, connect-nodes, update-node, etc. operations stage changes in a local workspace. They are NOT active until you deploy:
|
|
123
|
+
```
|
|
124
|
+
deploy(deployType: "nodes")
|
|
125
|
+
```
|
|
126
|
+
Default deploy type is `"nodes"` (least disruptive — only modified nodes restart). Use `"flows"` to restart modified flow tabs, or `"full"` for a complete restart.
|
|
127
|
+
|
|
128
|
+
Check what's pending before deploying:
|
|
129
|
+
```
|
|
130
|
+
get-staging-status()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**🔄 Post-deploy sync:** The deploy tool automatically refreshes all flows from the server after a successful deploy. The staging store is always in sync with Node-RED after deploy completes — no manual refresh needed.
|
|
134
|
+
|
|
135
|
+
### Step 6: Test the flow
|
|
136
|
+
```
|
|
137
|
+
inject-message(nodeId: "<injectId>")
|
|
138
|
+
read-debug-messages(nodeName: "<debugNodeName>", last: 5)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**⚠️ Important:** You MUST deploy before testing. `inject-message` will error if there are undeployed changes.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Port Numbering
|
|
146
|
+
|
|
147
|
+
All output ports are **0-indexed**. Port 0 is the leftmost output on the node.
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
┌──────────┐
|
|
151
|
+
│ switch │── port 0 (output 1)
|
|
152
|
+
│ │── port 1 (output 2)
|
|
153
|
+
│ │── port 2 (output 3)
|
|
154
|
+
└──────────┘
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Example — switch node with 3 routes:**
|
|
158
|
+
```
|
|
159
|
+
connect-nodes(fromNodeId: "<switchId>", outputPort: 0, toNodeId: "<route1Id>")
|
|
160
|
+
connect-nodes(fromNodeId: "<switchId>", outputPort: 1, toNodeId: "<route2Id>")
|
|
161
|
+
connect-nodes(fromNodeId: "<switchId>", outputPort: 2, toNodeId: "<route3Id>")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Batch wiring** (wire multiple ports in one call):
|
|
165
|
+
```
|
|
166
|
+
connect-nodes(fromNodeId: "<switchId>", connections: [
|
|
167
|
+
{ outputPort: 0, toNodeId: "<route1Id>" },
|
|
168
|
+
{ outputPort: 1, toNodeId: "<route2Id>" },
|
|
169
|
+
{ outputPort: 2, toNodeId: "<route3Id>" }
|
|
170
|
+
])
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
> ⚠️ **Never set `wires` in `update-node` properties.** Wiring is managed exclusively through `connect-nodes` and `disconnect-nodes`.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Coordinate Grid & Node Dimensions
|
|
178
|
+
|
|
179
|
+
Node positions on the canvas follow a grid pattern to keep flows readable and avoid overlapping nodes. Understanding node dimensions is critical for calculating proper coordinates.
|
|
180
|
+
|
|
181
|
+
### Standard Node Dimensions
|
|
182
|
+
|
|
183
|
+
Based on empirical measurements from Node-RED editor:
|
|
184
|
+
|
|
185
|
+
| Node Type | Width (px) | Height (px) | Notes |
|
|
186
|
+
|-----------|------------|-------------|-------|
|
|
187
|
+
| **Standard function/debug/inject** | ~160 | ~40 | Short names (≤20 chars) |
|
|
188
|
+
| **Long name function** | ~320+ | ~40 | Names >30 chars expand width |
|
|
189
|
+
| **Config nodes** | ~180 | ~40 | Slightly wider than standard |
|
|
190
|
+
| **Function w/ 1-3 outputs** | ~160 | ~40 | Height stays at ~40px |
|
|
191
|
+
| **Function w/ 4-5 outputs** | ~160 | ~60 | Height jumps ~20px at 4+ outputs |
|
|
192
|
+
| **Higher outputs (6+)** | ~160 | ~80+ | Extrapolated; verify empirically |
|
|
193
|
+
|
|
194
|
+
**Key observations:**
|
|
195
|
+
- All nodes have consistent **width** (~160px for standard names) regardless of output count
|
|
196
|
+
- Width varies based on node name length, NOT number of outputs
|
|
197
|
+
- Multiple output ports stack vertically on the right side — they do NOT expand width
|
|
198
|
+
- Height is stable (~40px) for 1-3 outputs, then jumps to ~60px at 4+ outputs
|
|
199
|
+
- Ports are positioned ~15px from left/right edges
|
|
200
|
+
- Minimum safe horizontal spacing between connected nodes: **120px center-to-center** (160px recommended)
|
|
201
|
+
- Minimum safe vertical spacing between rows: **40px center-to-center** for 1-3 outputs, **60px** for 4+ outputs
|
|
202
|
+
|
|
203
|
+
**Important:** When placing nodes with many outputs (4+), increase vertical spacing to avoid port overlap with adjacent rows. Use +60px Y delta instead of +40px.
|
|
204
|
+
|
|
205
|
+
### 📐 Height Formula (by output count)
|
|
206
|
+
|
|
207
|
+
Node height is **fixed at ~40px** for up to 3 outputs, then increases in ~20px steps for every additional group of 3 outputs:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
/**
|
|
211
|
+
* Estimate a function node's height based on output count.
|
|
212
|
+
* @param {number} outputs - Number of output ports (1-based)
|
|
213
|
+
* @returns {number} Estimated height in pixels
|
|
214
|
+
*/
|
|
215
|
+
function estimateNodeHeight(outputs) {
|
|
216
|
+
const baseHeight = 40; // minimum height for 1-3 outputs
|
|
217
|
+
const stepHeight = 20; // extra height per group of 3 additional outputs
|
|
218
|
+
const groupSize = 3; // outputs per height group
|
|
219
|
+
const extra = Math.max(0, Math.floor((outputs - 1) / groupSize));
|
|
220
|
+
return baseHeight + extra * stepHeight;
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Quick reference:**
|
|
225
|
+
| Outputs | Height | ΔY to next row |
|
|
226
|
+
|---------|--------|----------------|
|
|
227
|
+
| 1–3 | 40px | +40px |
|
|
228
|
+
| 4–6 | 60px | +60px |
|
|
229
|
+
| 7–9 | 80px | +80px |
|
|
230
|
+
| 10–12 | 100px | +100px |
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
// When placing the next row, use the taller node's height:
|
|
234
|
+
const nextY = currentY + estimateNodeHeight(currentOutputs);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 🎯 Positioning Guidelines
|
|
238
|
+
|
|
239
|
+
**📍 CRITICAL: First Node Position**
|
|
240
|
+
|
|
241
|
+
The first node in ANY flow MUST start at coordinates **(x=120, y=80)**. This is the absolute top-left starting point — nothing should be placed above y=80 or to the left of x=120. (When using groups with comments, the first node may shift down by ~30-50px to accommodate the group header.)
|
|
242
|
+
|
|
243
|
+
| Convention | X | Y | Use Case |
|
|
244
|
+
|------------|---|---|----------|
|
|
245
|
+
| **FIRST NODE (mandatory)** | **120** | **80** | **Absolute starting point** |
|
|
246
|
+
| Inline next node | +**180** | same Y | Sequential processing nodes (170-190px range) |
|
|
247
|
+
| Debug adjacent | +**10** | ±**40** | Debug alongside predecessor, above or below |
|
|
248
|
+
| Branch down (1-3 outs) | same X | +**60** | Alternative path from switch |
|
|
249
|
+
| Branch down (4+ outs) | same X | +**80** | Extra space for taller nodes |
|
|
250
|
+
| Branch row | +180 | +60 | Alternative row for complex branches |
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
─────────────────────────────────────────────────────────┐
|
|
254
|
+
│ CANVAS BOUNDARY │
|
|
255
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
256
|
+
│ │ │ │
|
|
257
|
+
│ │ [120,80] ← FIRST NODE STARTS HERE │ │
|
|
258
|
+
│ │ ↓ │ │
|
|
259
|
+
│ │ Row 1: [inject]──(+200)──[function]──(+200)──[debug] │
|
|
260
|
+
│ │ │ │ │
|
|
261
|
+
│ │ Row 2: └─(+200, +60)──[debug:error] │
|
|
262
|
+
│ │ │ │
|
|
263
|
+
│ │ ⚠️ Nothing above y=80 or left of x=120 │ │
|
|
264
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
265
|
+
─────────────────────────────────────────────────────────┘
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Why (120, 80)?**
|
|
269
|
+
- Provides adequate margin from canvas edges
|
|
270
|
+
- Matches Node-RED's default grid alignment
|
|
271
|
+
- Ensures consistent positioning across all flows
|
|
272
|
+
- Leaves room for node icons and ports without clipping
|
|
273
|
+
|
|
274
|
+
> 📐 **For advanced layout rules** (debug placement, branch-point centering, group spacing, comment positioning, bounding-box calculations), see **`nodered-flow-layout`**.
|
|
275
|
+
|
|
276
|
+
### 📐 Calculating Coordinates for Long Names
|
|
277
|
+
|
|
278
|
+
When placing nodes with long names, calculate width dynamically:
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
// Estimate node width based on name length
|
|
282
|
+
function estimateNodeWidth(nodeName) {
|
|
283
|
+
const baseWidth = 160; // minimum width for short names
|
|
284
|
+
const charWidth = 8; // approximate pixels per character
|
|
285
|
+
const padding = 40; // icon + ports padding
|
|
286
|
+
|
|
287
|
+
const textWidth = nodeName.length * charWidth;
|
|
288
|
+
return Math.max(baseWidth, textWidth + padding);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Example calculations:
|
|
292
|
+
estimateNodeWidth("function 1") // → 160px
|
|
293
|
+
estimateNodeWidth("This is a very long function name") // → ~320px
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Placement strategy for long-name nodes:**
|
|
297
|
+
1. Calculate estimated width using formula above
|
|
298
|
+
2. Add extra horizontal spacing: `nextX = currentX + estimatedWidth + 40`
|
|
299
|
+
3. This ensures cables remain visible and nodes don't overlap
|
|
300
|
+
|
|
301
|
+
### ️ Important Rules
|
|
302
|
+
|
|
303
|
+
- **Never place two nodes at the same (x, y)** — they visually overlap
|
|
304
|
+
- **Keep connected nodes within ~400px horizontally** for readability
|
|
305
|
+
- **Account for name length** when positioning — long names need more space
|
|
306
|
+
- **Locked flows**: `create-node`, `update-node`, `delete-node`, `connect-nodes`, and `disconnect-nodes` all refuse to modify nodes in a locked flow. Check flow lock status with `get-flows`.
|
|
307
|
+
|
|
308
|
+
### 🔍 Real-World Example
|
|
309
|
+
|
|
310
|
+
From actual Node-RED deployment analysis:
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
Flow layout with 8 function nodes:
|
|
314
|
+
|
|
315
|
+
Row 1 (y=80):
|
|
316
|
+
• function 1: x=120, y=80
|
|
317
|
+
• function 2: x=240, y=80 ← Δx = 120px (standard spacing)
|
|
318
|
+
|
|
319
|
+
Row 2 (y=120):
|
|
320
|
+
• function 3: x=120, y=120
|
|
321
|
+
• function 4: x=240, y=120 ← Δx = 120px
|
|
322
|
+
|
|
323
|
+
Row 3 (y=160):
|
|
324
|
+
• function 5: x=120, y=160
|
|
325
|
+
• function 6: x=240, y=160 ← Δx = 120px
|
|
326
|
+
|
|
327
|
+
Row 4 (y=200):
|
|
328
|
+
• Long name node: x=200, y=200
|
|
329
|
+
• function 7: x=400, y=200 ← Δx = 200px (extra space for long name)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Takeaway:** Standard nodes use 120px spacing, but long-name nodes require 200px+ to prevent overlap.
|
|
333
|
+
|
|
334
|
+
### 🧪 Multi-Output Node Height Test
|
|
335
|
+
|
|
336
|
+
Empirical test with function nodes (1-5 outputs) placed at y=80 with debug reference nodes below:
|
|
337
|
+
|
|
338
|
+
```
|
|
339
|
+
Test flow: "Node Dimensions Test"
|
|
340
|
+
|
|
341
|
+
Row 1 (y=80): [1 output] [2 outputs] [3 outputs] [4 outputs] [5 outputs]
|
|
342
|
+
x=120 x=280 x=440 x=600 x=760
|
|
343
|
+
|
|
344
|
+
Row 2 (debug): [Debug 1] [Debug 2] [Debug 3] [Debug 4] [Debug 5]
|
|
345
|
+
y=120 y=120 y=120 y=140 y=140
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Results:**
|
|
349
|
+
| Outputs | Function Y | Debug Y | ΔY (height) | Node Height |
|
|
350
|
+
|---------|-----------|---------|-------------|-------------|
|
|
351
|
+
| 1 | 80 | 120 | 40px | ~40px |
|
|
352
|
+
| 2 | 80 | 120 | 40px | ~40px |
|
|
353
|
+
| 3 | 80 | 120 | 40px | ~40px |
|
|
354
|
+
| 4 | 80 | 140 | 60px | ~60px |
|
|
355
|
+
| 5 | 80 | 140 | 60px | ~60px |
|
|
356
|
+
|
|
357
|
+
**Key findings:**
|
|
358
|
+
- **Width is constant** (~160px) regardless of output count — extra ports stack vertically
|
|
359
|
+
- **Height is stable** (~40px) for 1-3 outputs
|
|
360
|
+
- **Height jumps** to ~60px at 4+ outputs
|
|
361
|
+
- **Horizontal spacing**: 160px between centers works for all output counts
|
|
362
|
+
- **Vertical spacing**: Use +40px for 1-3 outputs, +60px for 4+ outputs
|
|
363
|
+
|
|
364
|
+
**Placement rule for multi-output nodes:**
|
|
365
|
+
```javascript
|
|
366
|
+
// Use the estimateNodeHeight() formula from the Height section above
|
|
367
|
+
const nextY = currentY + estimateNodeHeight(currentOutputs);
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Workflow B — Import from JSON
|
|
373
|
+
|
|
374
|
+
Use `import-flow` when you have a pre-built flow JSON (from `export-flow` or an example file):
|
|
375
|
+
|
|
376
|
+
### Step 1: Choose a conflict strategy
|
|
377
|
+
- **`regenerate`** (default, safest): Remaps all IDs to new UUIDs. Always creates a duplicate — safe, no overwrites.
|
|
378
|
+
- **`overwrite`**: Replaces nodes with matching IDs. Use to update an existing flow with new configuration.
|
|
379
|
+
|
|
380
|
+
### Step 2: Import
|
|
381
|
+
```
|
|
382
|
+
import-flow(flowJson: "<json>", conflictStrategy: "regenerate")
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Step 3: (Optional) Target a specific tab
|
|
386
|
+
```
|
|
387
|
+
import-flow(flowJson: "<json>", targetFlowId: "<existingFlowId>")
|
|
388
|
+
```
|
|
389
|
+
This injects all non-tab nodes into the target tab. The tab node in the JSON is discarded.
|
|
390
|
+
|
|
391
|
+
**When to prefer import over build-from-scratch:**
|
|
392
|
+
- Duplicating or migrating an existing flow between instances
|
|
393
|
+
- Applying a known-good template from examples
|
|
394
|
+
- Restoring a flow from a backup (exported with `export-flow`)
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Workflow C — Edit an Existing Node
|
|
399
|
+
|
|
400
|
+
### Step 1: Read current state
|
|
401
|
+
```
|
|
402
|
+
get-node-detail(nodeId: "<nodeId>")
|
|
403
|
+
```
|
|
404
|
+
Review the returned node object to understand current configuration.
|
|
405
|
+
|
|
406
|
+
### Step 2: Apply changes
|
|
407
|
+
```
|
|
408
|
+
update-node(nodeId: "<nodeId>", properties: { name: "New Name", func: "return msg;" })
|
|
409
|
+
```
|
|
410
|
+
Only include fields you want to change. Omitted fields are preserved.
|
|
411
|
+
|
|
412
|
+
**💡 Adding or updating a description:** Set the `info` property — the **Description** field in the Node-RED editor UI:
|
|
413
|
+
```
|
|
414
|
+
update-node(nodeId: "<nodeId>", properties: { info: "This node filters messages with temperature > 30°C" })
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Step 3: Verify
|
|
418
|
+
```
|
|
419
|
+
get-node-detail(nodeId: "<nodeId>")
|
|
420
|
+
```
|
|
421
|
+
Confirm the changes took effect.
|
|
422
|
+
|
|
423
|
+
**🛑 Never include `wires`, `id`, or `z` in `properties`.** The tool silently ignores them.
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Workflow D — Delete
|
|
428
|
+
|
|
429
|
+
### Delete a single node
|
|
430
|
+
```
|
|
431
|
+
delete-node(nodeId: "<nodeId>")
|
|
432
|
+
```
|
|
433
|
+
Node-RED automatically cleans up dangling wire references on deploy. The `previousState` in the response contains the full node object — save it if you need to undo.
|
|
434
|
+
|
|
435
|
+
### Delete an entire flow tab
|
|
436
|
+
```
|
|
437
|
+
delete-flow(flowId: "<flowId>")
|
|
438
|
+
```
|
|
439
|
+
Returns `previousState` with ALL nodes in the tab. Refuses to delete locked flows.
|
|
440
|
+
|
|
441
|
+
**Undo a deletion:** Use `import-flow` with the `previousState` from the delete response and strategy `"regenerate"`.
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## 🐛 Build & Debug Step-by-Step (RECOMMENDED)
|
|
446
|
+
|
|
447
|
+
**When building a new flow, validate each node's output before adding the next node.** This catches format errors, type mismatches, and missing properties early — before you've built 10 nodes on top of a broken foundation.
|
|
448
|
+
|
|
449
|
+
### The Golden Rule
|
|
450
|
+
> After EVERY processing node you add, wire it to a `debug` node, deploy, inject, and verify the output. Only then add the next node.
|
|
451
|
+
|
|
452
|
+
### Step-by-step pattern
|
|
453
|
+
|
|
454
|
+
```
|
|
455
|
+
// 1. Create first processing node + debug after it
|
|
456
|
+
create-node(type: "inject", flowId: "<fid>", properties: { name: "Start", payload: "test", payloadType: "str" }, x: 100, y: 100)
|
|
457
|
+
create-node(type: "debug", flowId: "<fid>", properties: { name: "Debug1", complete: "true", targetType: "full" }, x: 300, y: 100)
|
|
458
|
+
connect-nodes(fromNodeId: "<injectId>", toNodeId: "<debug1Id>")
|
|
459
|
+
|
|
460
|
+
// 2. Deploy — changes are NOT live until you do this
|
|
461
|
+
deploy()
|
|
462
|
+
|
|
463
|
+
// 3. Inject and check output format
|
|
464
|
+
inject-message(nodeId: "<injectId>")
|
|
465
|
+
read-debug-messages(last: 1)
|
|
466
|
+
|
|
467
|
+
// 4. If output looks good, remove the debug node (or keep it) and add next node
|
|
468
|
+
delete-node(nodeId: "<debug1Id>")
|
|
469
|
+
create-node(type: "function", flowId: "<fid>", properties: { name: "Process", func: "msg.payload = msg.payload * 2;\nreturn msg;", outputs: 1 }, x: 300, y: 100)
|
|
470
|
+
connect-nodes(fromNodeId: "<injectId>", toNodeId: "<functionId>")
|
|
471
|
+
create-node(type: "debug", flowId: "<fid>", properties: { name: "Debug2", complete: "true", targetType: "full" }, x: 500, y: 100)
|
|
472
|
+
connect-nodes(fromNodeId: "<functionId>", toNodeId: "<debug2Id>")
|
|
473
|
+
|
|
474
|
+
// 5. Deploy again and verify
|
|
475
|
+
deploy()
|
|
476
|
+
inject-message(nodeId: "<injectId>")
|
|
477
|
+
read-debug-messages(last: 1)
|
|
478
|
+
|
|
479
|
+
// 6. Repeat until flow is complete
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Why this matters:**
|
|
483
|
+
- A `function` node might return `undefined` or the wrong type — you catch it immediately
|
|
484
|
+
- A `change` node might not set the right property — you see it in debug output
|
|
485
|
+
- An `http request` might return unexpected JSON structure — you can adjust before building downstream nodes
|
|
486
|
+
- You avoid debugging a 10-node chain where the error is in node #2
|
|
487
|
+
|
|
488
|
+
**⚠️ NEVER FORGET TO DEPLOY.** Every edit is staged. If `inject-message` errors with "undeployed changes", call `deploy` first.
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Debug Workflow
|
|
493
|
+
|
|
494
|
+
Follow this numbered sequence whenever a flow doesn't behave as expected:
|
|
495
|
+
|
|
496
|
+
### 1️⃣ Ensure debug nodes are active
|
|
497
|
+
```
|
|
498
|
+
get-flow-nodes(flowId: "<flowId>", nodeType: "debug")
|
|
499
|
+
```
|
|
500
|
+
Check that each debug node has `active: true`. If not:
|
|
501
|
+
```
|
|
502
|
+
update-node(nodeId: "<debugId>", properties: { active: true })
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### 2️⃣ Record a timestamp
|
|
506
|
+
Note the current time (e.g., `Date.now()`) before triggering. This gives you an `after` filter anchor for `read-debug-messages`.
|
|
507
|
+
|
|
508
|
+
### 3️⃣ Trigger the flow
|
|
509
|
+
```
|
|
510
|
+
inject-message(nodeId: "<injectId>")
|
|
511
|
+
```
|
|
512
|
+
Use `nodeId` (not name) when possible — it's unambiguous.
|
|
513
|
+
|
|
514
|
+
### 4️⃣ Wait briefly then read
|
|
515
|
+
Node-RED processes messages asynchronously. Wait ~500ms-1s, then:
|
|
516
|
+
```
|
|
517
|
+
read-debug-messages(nodeName: "<debugNodeName>", after: <timestamp>, limit: 10)
|
|
518
|
+
```
|
|
519
|
+
The `after` filter ensures you only see messages from this test run.
|
|
520
|
+
|
|
521
|
+
### 5️⃣ Analyze the output
|
|
522
|
+
Check `payload`, `topic`, and any custom properties. Is the value what you expected? Is the type correct?
|
|
523
|
+
|
|
524
|
+
### 6️⃣ Fix and repeat
|
|
525
|
+
Make the needed changes (via `update-node`, function `func` edit, or wiring fix), then repeat from step 2.
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## Debug Node Configuration
|
|
530
|
+
|
|
531
|
+
| Property | Values | Description |
|
|
532
|
+
|----------|--------|-------------|
|
|
533
|
+
| `active` | `true/false` | Enable/disable output. Disabled nodes collect nothing. |
|
|
534
|
+
| `complete` | `"true"` (full msg), `"false"` (payload only), or property path like `"payload.temperature"` | What to display |
|
|
535
|
+
| `targetType` | `"full"` or `"msg"` | Use `"msg"` when `complete` is a property path |
|
|
536
|
+
| `console` | `true/false` | Also log to Node-RED server console |
|
|
537
|
+
| `tosidebar` | `true/false` | Display in debug sidebar (required for `read-debug-messages`) |
|
|
538
|
+
|
|
539
|
+
**Recommended debug node for flow validation:**
|
|
540
|
+
```
|
|
541
|
+
{ active: true, complete: "true", targetType: "full", tosidebar: true, console: false }
|
|
542
|
+
```
|
|
543
|
+
This captures the entire message — you can inspect all properties during debugging.
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## inject-message Usage
|
|
548
|
+
|
|
549
|
+
Trigger an inject node to start a message through the flow:
|
|
550
|
+
|
|
551
|
+
```
|
|
552
|
+
// By nodeId (preferred — always unique)
|
|
553
|
+
inject-message(nodeId: "<injectId>")
|
|
554
|
+
|
|
555
|
+
// By name (convenient, but fails if duplicate names exist)
|
|
556
|
+
inject-message(name: "Trigger")
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
**Important notes:**
|
|
560
|
+
- The target node MUST be of type `"inject"`. Other node types will error.
|
|
561
|
+
- Injection is asynchronous — the command returns immediately, the message flows through Node-RED in the background.
|
|
562
|
+
- Use `flowId` to scope name-based lookups: `inject-message(name: "Trigger", flowId: "<flowId>")`.
|
|
563
|
+
- If multiple inject nodes share the same name without a `flowId`, the tool returns an error listing the matching IDs — pick one.
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## read-debug-messages Filters
|
|
568
|
+
|
|
569
|
+
| Filter | Type | Use Case |
|
|
570
|
+
|--------|------|----------|
|
|
571
|
+
| `nodeId` | string | Pinpoint output from a specific debug node |
|
|
572
|
+
| `nodeName` | string | Match by name (case-insensitive substring) |
|
|
573
|
+
| `keyword` | string | Search within stringified message payload |
|
|
574
|
+
| `after` | timestamp (ms) | Only messages after this time (use `Date.now()` before trigger) |
|
|
575
|
+
| `before` | timestamp (ms) | Only messages before this time |
|
|
576
|
+
| `last` | number | Return the N most recent matching messages |
|
|
577
|
+
| `limit` | number | Return the first N matching messages (default 50) |
|
|
578
|
+
|
|
579
|
+
`last` and `limit` are **mutually exclusive** — use one or the other, not both.
|
|
580
|
+
|
|
581
|
+
**Typical debug read:**
|
|
582
|
+
```
|
|
583
|
+
read-debug-messages(nodeName: "Output", after: 1717526400000, last: 5)
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## Common Debug Patterns
|
|
589
|
+
|
|
590
|
+
### "Is my flow even triggered?"
|
|
591
|
+
```
|
|
592
|
+
inject-message(nodeId: "<injectId>")
|
|
593
|
+
read-debug-messages(nodeName: "<firstDebugName>", last: 1)
|
|
594
|
+
```
|
|
595
|
+
If no message appears: check wiring (especially `connect-nodes`), verify the inject node fires, and confirm the debug node is on the same tab.
|
|
596
|
+
|
|
597
|
+
### "What is the payload at this point?"
|
|
598
|
+
Place a temporary debug node after the suspect node:
|
|
599
|
+
```
|
|
600
|
+
create-node(type: "debug", flowId: "<flowId>", properties: { name: "Checkpoint", complete: "true", targetType: "full" }, x: <afterNodeX>, y: <sameY>)
|
|
601
|
+
connect-nodes(fromNodeId: "<suspectNodeId>", outputPort: 0, toNodeId: "<checkpointId>")
|
|
602
|
+
```
|
|
603
|
+
Trigger, read, diagnose, then delete the checkpoint node.
|
|
604
|
+
|
|
605
|
+
### "Is my context value set?"
|
|
606
|
+
```
|
|
607
|
+
get-context(scope: "flow", id: "<flowId>", key: "myKey")
|
|
608
|
+
```
|
|
609
|
+
If `null`: your function node's `context.set()` or `flow.set()` isn't executing. Check that the function node is actually receiving messages.
|
|
610
|
+
|
|
611
|
+
### "Why is my function node not working?"
|
|
612
|
+
```
|
|
613
|
+
get-node-detail(nodeId: "<functionId>")
|
|
614
|
+
```
|
|
615
|
+
Review the `func` property. Common issues: missing `return msg;`, syntax errors, wrong property names.
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
## Verification
|
|
620
|
+
|
|
621
|
+
After building or modifying a flow, always verify:
|
|
622
|
+
|
|
623
|
+
### 1. Topology check
|
|
624
|
+
```
|
|
625
|
+
get-flow-diagram(flowId: "<flowId>")
|
|
626
|
+
```
|
|
627
|
+
Confirm the Mermaid diagram shows correct wiring — every node connected as intended, no dangling branches.
|
|
628
|
+
|
|
629
|
+
### 2. Inventory check
|
|
630
|
+
```
|
|
631
|
+
get-flow-nodes(flowId: "<flowId>")
|
|
632
|
+
```
|
|
633
|
+
Verify node count, types, and names. Look for:
|
|
634
|
+
- Unnamed nodes (`"name": ""`) — give them descriptive names
|
|
635
|
+
- Wrong node types — did you accidentally pick the wrong type?
|
|
636
|
+
- Missing nodes — did all your `create-node` calls succeed?
|
|
637
|
+
|
|
638
|
+
### 3. Wire check
|
|
639
|
+
Look at the `wires` arrays in `get-flow-nodes` output. Each source node should have its expected targets listed.
|
|
640
|
+
|
|
641
|
+
### 4. Functional test
|
|
642
|
+
Inject a message and read debug output. Confirm the payload transforms as expected at each step.
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## Common Mistakes
|
|
647
|
+
|
|
648
|
+
| Mistake | Symptom | Fix |
|
|
649
|
+
|---------|---------|-----|
|
|
650
|
+
| **Not wiring after create** | New node never receives messages | Always call `connect-nodes` after `create-node`. Use the INSERT pattern: disconnect A→B, connect A→new, connect new→B |
|
|
651
|
+
| **Wrong port index** | Message goes to wrong output | Ports are 0-indexed. Switch output 1 = port 0, output 2 = port 1, output 3 = port 2 |
|
|
652
|
+
| **Setting wires in update-node** | Wires silently ignored | Use `connect-nodes`/`disconnect-nodes` exclusively for wiring |
|
|
653
|
+
| **Overlapping coordinates** | Nodes visually stack in editor | Use the grid: first at (100,100), inline +200 X, branch +100 Y |
|
|
654
|
+
| **Forgetting `return msg;`** in function | Message stops at function node | Every function must `return msg;` (or `return [msg1, msg2]` for multi-output). `return null;` explicitly stops the message |
|
|
655
|
+
| **`node.send()` without `return`** | Unpredictable behavior, duplicate messages | After calling `node.send()` in async code, ALWAYS `return;` immediately |
|
|
656
|
+
| **Debug node with `active: false`** | No output visible | Set `active: true` on all debug nodes used for testing |
|
|
657
|
+
| **Reading debug too soon** | `read-debug-messages` returns empty | Wait ~500ms after `inject-message` before reading; Node-RED processes asynchronously |
|
|
658
|
+
| **Deleting without backup** | Cannot undo accidental deletion | Use `export-flow` before destructive operations; `delete-*` responses include `previousState` for recovery |
|
|
659
|
+
| **Modifying locked flow** | Tool returns error | Check `get-flows` for `locked: true`; locked flows must be unlocked in the editor first |
|