@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.
Files changed (89) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +162 -0
  3. package/index.js +133 -0
  4. package/package.json +58 -0
  5. package/resources/skills/nodered-flow-builder/SKILL.md +659 -0
  6. package/resources/skills/nodered-flow-layout/SKILL.md +395 -0
  7. package/resources/skills/nodered-flowfuse-dashboard/SKILL.md +941 -0
  8. package/resources/skills/nodered-fundamentals/SKILL.md +323 -0
  9. package/resources/skills/nodered-jsonata/SKILL.md +1039 -0
  10. package/resources/skills/nodered-mustache/SKILL.md +588 -0
  11. package/resources/skills/nodered-node-reference/SKILL.md +1020 -0
  12. package/resources/skills/nodered-node-reference/examples/common.json +113 -0
  13. package/resources/skills/nodered-node-reference/examples/network.json +107 -0
  14. package/resources/skills/nodered-node-reference/examples/parser.json +147 -0
  15. package/resources/skills/nodered-node-reference/examples/sequence.json +141 -0
  16. package/resources/skills/nodered-node-reference/examples/storage.json +104 -0
  17. package/resources/skills/nodered-patterns/SKILL.md +414 -0
  18. package/resources/skills/nodered-patterns/examples/error-handler.json +72 -0
  19. package/resources/skills/nodered-patterns/examples/http-endpoint.json +42 -0
  20. package/resources/skills/nodered-patterns/examples/mqtt-subscriber.json +47 -0
  21. package/resources/skills/nodered-patterns/examples/timer-flow.json +50 -0
  22. package/resources/skills/nodered-subflows/SKILL.md +261 -0
  23. package/resources/skills/nodered-uibuilder/SKILL.md +500 -0
  24. package/src/auth/api-key-verifier.js +36 -0
  25. package/src/auth/composite-verifier.js +59 -0
  26. package/src/auth/config.js +106 -0
  27. package/src/auth/oauth-clients-store.js +107 -0
  28. package/src/auth/oauth-provider.js +149 -0
  29. package/src/auth/oauth-token-store.js +312 -0
  30. package/src/nodered/auth.js +158 -0
  31. package/src/nodered/client.js +199 -0
  32. package/src/nodered/comms-client.js +500 -0
  33. package/src/renderer/colors.js +161 -0
  34. package/src/renderer/geometry.js +115 -0
  35. package/src/renderer/html-builder.js +571 -0
  36. package/src/renderer/index.js +51 -0
  37. package/src/renderer/ir-builder.js +161 -0
  38. package/src/renderer/layout.js +126 -0
  39. package/src/renderer/mermaid-builder.js +109 -0
  40. package/src/renderer/svg-builder.js +228 -0
  41. package/src/schemas/responses.js +283 -0
  42. package/src/server.js +844 -0
  43. package/src/skills/loader.js +84 -0
  44. package/src/staging-store.js +258 -0
  45. package/src/tools/add-nodes-to-group.js +216 -0
  46. package/src/tools/connect-nodes.js +115 -0
  47. package/src/tools/constants.js +45 -0
  48. package/src/tools/create-flow.js +87 -0
  49. package/src/tools/create-node.js +126 -0
  50. package/src/tools/create-subflow-instance.js +123 -0
  51. package/src/tools/create-subflow.js +101 -0
  52. package/src/tools/delete-context.js +60 -0
  53. package/src/tools/delete-flow.js +81 -0
  54. package/src/tools/delete-group.js +116 -0
  55. package/src/tools/delete-node.js +73 -0
  56. package/src/tools/delete-subflow.js +103 -0
  57. package/src/tools/deploy.js +94 -0
  58. package/src/tools/disconnect-nodes.js +158 -0
  59. package/src/tools/export-flow.js +161 -0
  60. package/src/tools/export-subflow.js +78 -0
  61. package/src/tools/flow-utils.js +376 -0
  62. package/src/tools/get-config-nodes.js +86 -0
  63. package/src/tools/get-context.js +76 -0
  64. package/src/tools/get-flow-diagram.js +99 -0
  65. package/src/tools/get-flow-nodes.js +116 -0
  66. package/src/tools/get-flows.js +74 -0
  67. package/src/tools/get-node-detail.js +77 -0
  68. package/src/tools/get-node-type-detail.js +92 -0
  69. package/src/tools/get-palette-nodes.js +63 -0
  70. package/src/tools/get-staging-status.js +34 -0
  71. package/src/tools/get-subflow-detail.js +110 -0
  72. package/src/tools/get-subflows.js +105 -0
  73. package/src/tools/import-flow.js +310 -0
  74. package/src/tools/inject-message.js +117 -0
  75. package/src/tools/install-node.js +31 -0
  76. package/src/tools/read-debug-messages.js +155 -0
  77. package/src/tools/refresh-staging.js +62 -0
  78. package/src/tools/remove-nodes-from-group.js +162 -0
  79. package/src/tools/render-staging.js +69 -0
  80. package/src/tools/response-utils.js +42 -0
  81. package/src/tools/search-nodes.js +134 -0
  82. package/src/tools/uninstall-node.js +31 -0
  83. package/src/tools/update-flow.js +95 -0
  84. package/src/tools/update-group.js +77 -0
  85. package/src/tools/update-node.js +132 -0
  86. package/src/tools/update-subflow.js +84 -0
  87. package/src/transport/http.js +252 -0
  88. package/src/transport/stdio.js +16 -0
  89. package/src/transport/ws-server.js +223 -0
@@ -0,0 +1,1020 @@
1
+ ---
2
+ name: nodered-node-reference
3
+ description: >-
4
+ Categorized reference of Node-RED built-in core node types, key properties,
5
+ and a deep-dive into the function node's APIs, return semantics, and async patterns.
6
+ tools:
7
+ - create-node
8
+ - update-node
9
+ - get-node-detail
10
+ - get-node-type-detail
11
+ - get-palette-nodes
12
+ ---
13
+
14
+ # Node-RED Node Reference
15
+
16
+ Categorized catalog of built-in core node types with key properties for `create-node` / `update-node`, plus a deep-dive into the **function node**.
17
+
18
+ **How to use this reference:**
19
+ - Look up a node type by category to find its `type` string and required/optional `properties`.
20
+ - For node types NOT listed here (third-party, custom), use `get-node-type-detail` to retrieve the full parameter schema before calling `create-node`.
21
+ - JSON example files in `examples/` show working node configurations you can import with `import-flow`.
22
+
23
+ **⚠️ CREDENTIAL PRIVACY — read before working with config nodes:**
24
+ - **Credential values (passwords, API keys) are NEVER exposed** by the API. `get-flow-nodes` and `get-config-nodes` will NOT include credential fields at all.
25
+ - **A missing `credentials` field does NOT mean no credentials are set** — it means they are hidden for security. This is by design.
26
+ - **To check what credential fields exist on a config node**: Use `get-node-detail`. It returns a `_credentials` object like `{ user: "test67", has_password: true }` — showing field names and whether password-type fields are set. Password VALUES are never shown, only their presence/absence.
27
+ - **To set/update credentials on config nodes** (like `mqtt-broker`, `http-proxy`, `tls-config`), place them inside a `credentials` object: `create-node`/`update-node` with `properties: { credentials: { user: "u", password: "p" } }`. The tools auto-detect and nest credential fields correctly.
28
+ - **Partial updates work**: send only the credential fields you want to change — e.g., `credentials: { password: "newpass" }` changes only the password, leaving `user` unchanged.
29
+
30
+ ---
31
+
32
+ ## Common Node Properties
33
+
34
+ All node types share these universal fields. Use `update-node` to modify them and `get-node-detail` to read them.
35
+
36
+ | Property | Type | Description |
37
+ |----------|------|-------------|
38
+ | `name` | string | Display label shown on the node in the editor |
39
+ | `info` | string | **Description** field in the Node-RED editor UI. Rich text describing what the node does. When a user says "add a description to this node" or "describe what this node does", they mean setting `info`. Read with `get-node-detail`, set with `update-node` or `create-node` via `properties: { info: "my description" }`. |
40
+
41
+ Most node types also have type-specific properties documented in the sections below.
42
+
43
+ ---
44
+
45
+ ## Common Nodes
46
+
47
+ ### inject
48
+ `type: "inject"` — Manually or periodically triggers a message into a flow.
49
+
50
+ > **⚠️ CRITICAL — ALL fields below are mandatory in `create-node`.**
51
+ > Omitting any of these fields causes **red triangle errors** in the Node-RED editor.
52
+ > Even fields with empty defaults (`""`, `false`, `[]`) MUST be explicitly set.
53
+
54
+ | Property | Type | Default | Description |
55
+ |----------|------|---------|-------------|
56
+ | `name` | string | `""` | Label on the node |
57
+ | `payload` | string | `""` | Value to inject (string, number, JSON, timestamp, etc.) |
58
+ | `payloadType` | string | `"str"` | `"str"`, `"num"`, `"json"`, `"bool"`, `"date"`, `"null"`, `"flow"`, `"global"` |
59
+ | `topic` | string | `""` | msg.topic value |
60
+ | `repeat` | string | `""` | Cron or interval string (empty = manual only) |
61
+ | `once` | boolean | `false` | Inject once on start (only with repeat) |
62
+ | `onceDelay` | string | `""` | Delay in seconds before first inject |
63
+ | `crontab` | string | `""` | Cron expression (alternative to repeat interval) |
64
+ | `props` | array | `[{p:"payload"},{p:"topic",vt:"str"}]` | Property schema for the inject dialog. Must be present. |
65
+
66
+ ### Full Node Configuration Examples
67
+
68
+ These are the `properties` objects you pass to `create-node` or `update-node`. Extracted from real Node-RED nodes.
69
+
70
+ **Minimal example — manual trigger with string payload, all required defaults:**
71
+
72
+ ```json
73
+ {
74
+ "type": "inject",
75
+ "name": "",
76
+ "payload": "",
77
+ "payloadType": "str",
78
+ "topic": "",
79
+ "repeat": "",
80
+ "once": false,
81
+ "onceDelay": "",
82
+ "crontab": "",
83
+ "props": [
84
+ { "p": "payload" },
85
+ { "p": "topic", "vt": "str" }
86
+ ]
87
+ }
88
+ ```
89
+
90
+ > **⚠️ Note:** The 6 fields after `payloadType` (`topic`, `repeat`, `once`, `onceDelay`, `crontab`, `props`) are NOT optional. Every one of them must be present, even with empty defaults. Omitting any causes a red triangle error in the editor.
91
+
92
+ **Full example — JSON payload, scheduled every 5 minutes, with docs:**
93
+
94
+ ```json
95
+ {
96
+ "type": "inject",
97
+ "name": "Sensor trigger",
98
+ "payload": "{\"sensor\":\"temp\",\"value\":22.5}",
99
+ "payloadType": "json",
100
+ "topic": "sensors/temperature",
101
+ "repeat": "300",
102
+ "once": true,
103
+ "onceDelay": "0.5",
104
+ "crontab": "",
105
+ "props": [
106
+ { "p": "payload" },
107
+ { "p": "topic", "vt": "str" },
108
+ { "p": "payloadType" },
109
+ { "p": "repeat" },
110
+ { "p": "crontab" },
111
+ { "p": "once" },
112
+ { "p": "onceDelay" }
113
+ ],
114
+ "info": "Injects a temperature reading every 5 minutes. Fires once on deploy after 0.5s delay."
115
+ }
116
+ ```
117
+
118
+ **What each field does in this example:**
119
+
120
+ | Field | Value | Effect |
121
+ |-------|-------|--------|
122
+ | `payload` | `'{"sensor":"temp",...}'` | JSON object string sent on each inject |
123
+ | `payloadType` | `"json"` | Node-RED parses payload into a JS object before sending |
124
+ | `repeat` | `"300"` | Triggers every 300 seconds (5 min) |
125
+ | `once` | `true` | Triggers once on deploy, then starts the repeat cycle |
126
+ | `onceDelay` | `"0.5"` | First inject fires 0.5 seconds after deploy |
127
+ | `props` | (array of 7 objects) | Defines the fields shown in the inject dialog in the editor |
128
+
129
+ > **`props` explained:** Each `{ p: "fieldname" }` entry adds a row to the inject node's edit dialog. The entry `{ p: "topic", vt: "str" }` sets the value type selector for that field. For `create-node`, you only need the minimum set `[{p:"payload"},{p:"topic",vt:"str"}]` — the Node-RED editor may expand it on save.
130
+
131
+ ### debug
132
+ `type: "debug"` — Outputs messages to the debug sidebar (readable via `read-debug-messages`).
133
+
134
+ | Property | Type | Description |
135
+ |----------|------|-------------|
136
+ | `name` | string | Label |
137
+ | `active` | boolean | Enable/disable debug output |
138
+ | `complete` | string | `"false"` = full message object (default). Or a property path like `"payload"` to show only that property. |
139
+ | `tosidebar` | boolean | Also send to debug sidebar |
140
+ | `console` | boolean | Also log to Node-RED console |
141
+ | `tostatus` | boolean | Also show on node status (under the node) |
142
+ | `statusVal` | string | Property to show in status (when `tostatus` is true) |
143
+ | `statusType` | string | Status type: `"auto"` (auto-detect type), `"msg"` (message property) |
144
+
145
+ > **Note:** `complete: "false"` (the string `"false"`, not boolean `false`) means "show full message object". Set it to `"payload"` or any other property path to show only that property.
146
+
147
+ ### complete
148
+ `type: "complete"` — Triggers when another node completes (error-free). Used for sequential flows.
149
+
150
+ | Property | Type | Description |
151
+ |----------|------|-------------|
152
+ | `name` | string | Label |
153
+ | `scope` | array | Array of node IDs to watch (empty = all on tab) |
154
+
155
+ ### catch
156
+ `type: "catch"` — Catches errors thrown by nodes on the same tab.
157
+
158
+ | Property | Type | Description |
159
+ |----------|------|-------------|
160
+ | `name` | string | Label |
161
+ | `scope` | array | Array of node IDs to catch errors from (empty = all on tab) |
162
+ | `uncaught` | boolean | If true, only catch errors not handled by other catch nodes |
163
+
164
+ ### status
165
+ `type: "status"` — Triggers when a node's status changes (e.g., connected/disconnected).
166
+
167
+ | Property | Type | Description |
168
+ |----------|------|-------------|
169
+ | `name` | string | Label |
170
+ | `scope` | array | Array of node IDs to watch (empty = all on tab) |
171
+
172
+ ### link in / link out / link call
173
+ `type: "link in"` / `type: "link out"` / `type: "link call"` — Cross-tab message passing.
174
+
175
+ | Property | Type | Description |
176
+ |----------|------|-------------|
177
+ | `name` | string | Label |
178
+ | `links` | array | Link group names (all link nodes with matching name connect) |
179
+ | `linkType` | string | For link call: determines response behavior |
180
+
181
+ ### comment
182
+ `type: "comment"` — Text annotation on the canvas. Not a functional node.
183
+
184
+ **⚠️ CRITICAL — `name` vs `info`:**
185
+ - **`name`** = **short LABEL** visible on the canvas (1-3 words max, e.g. `"🧪 Demo MCP"`). NEVER put multi-line explanations here — it renders poorly in the editor.
186
+ - **`info`** = **detailed DESCRIPTION** (tooltip on hover). Put pipeline documentation, notes, and full explanations here. This is the correct field for long-form content. Supports html or markdown formatting.
187
+
188
+ | Property | Type | Description |
189
+ |----------|------|-------------|
190
+ | `name` | string | Short label shown on canvas. Keep it concise (1-3 words). |
191
+ | `info` | string | Detailed description (tooltip). Use for documentation and explanations. |
192
+
193
+ ---
194
+
195
+ ## Logic & Transform Nodes
196
+
197
+ ### switch
198
+ `type: "switch"` — Routes messages to different outputs based on rules.
199
+
200
+ **⚠️ CRITICAL — Mandatory fields for correct rendering:**
201
+ - **`outputs`** (number): MUST be set and MUST equal the number of rules. If omitted, the Node-RED editor defaults to 1 visible output port, hiding any wires connected to ports 2+. This causes the node to appear disconnected from downstream nodes even though `wires` contains the connections. Set `outputs` to `rules.length`.
202
+ - **`repair`** (boolean): Should be explicitly set to `false` (or `true` to forward unmatched messages to a catch node). Omitting this field can cause rendering inconsistencies.
203
+ - **`checkall`** (string, NOT boolean): Node-RED expects `"true"` or `"false"` as strings. Despite being conceptually boolean, the value must be a JSON string.
204
+
205
+ | Property | Type | Description |
206
+ |----------|------|-------------|
207
+ | `name` | string | Label |
208
+ | `property` | string | Property to test (e.g., `"payload"`, `"topic"`) |
209
+ | `propertyType` | string | How to interpret property: `"msg"`, `"flow"`, `"global"`, `"env"` |
210
+ | `rules` | array | Array of `{ t: <operator>, v: <value>, vt: <valueType> }`. Each rule maps to output port `index`. See operator reference below. |
211
+ | `checkall` | string | `"true"` = evaluate all rules (multiple outputs possible); `"false"` = stop at first match (single output). **Must be string, not boolean.** |
212
+ | `outputs` | number | **REQUIRED.** Number of output ports. Must match `rules.length`. Without this, the editor shows only 1 port. |
213
+ | `repair` | boolean | **REQUIRED.** Send unmatched messages to a catch node. Default `false`. Set explicitly. |
214
+
215
+ **Switch rule operators (`t` field):**
216
+
217
+ | Operator | Description | Example |
218
+ |----------|-------------|---------|
219
+ | `eq` | Equal to | `{ t: "eq", v: "hello", vt: "str" }` |
220
+ | `neq` | Not equal to | `{ t: "neq", v: "0", vt: "num" }` |
221
+ | `lt` | Less than | `{ t: "lt", v: "15", vt: "num" }` |
222
+ | `lte` | Less than or equal | `{ t: "lte", v: "100", vt: "num" }` |
223
+ | `gt` | Greater than | `{ t: "gt", v: "0", vt: "num" }` |
224
+ | `gte` | Greater than or equal | `{ t: "gte", v: "30", vt: "num" }` |
225
+ | `btwn` | Between (inclusive) | `{ t: "btwn", v: "10", vt: "num", v2: "20", vt2: "num" }` |
226
+ | `cont` | Contains substring | `{ t: "cont", v: "error", vt: "str" }` |
227
+ | `regex` | Matches regex | `{ t: "regex", v: "^\\d{3}$", vt: "str" }` |
228
+ | `true` | Boolean true | `{ t: "true" }` |
229
+ | `false` | Boolean false | `{ t: "false" }` |
230
+ | `null` | Is null | `{ t: "null" }` |
231
+ | `nnull` | Is not null | `{ t: "nnull" }` |
232
+ | `istype` | Is type | `{ t: "istype", v: "string" }` (types: "string", "number", "boolean", "object", "array", "buffer", "null", "undefined") |
233
+ | `empty` | Is empty | `{ t: "empty" }` |
234
+ | `nempty` | Is not empty | `{ t: "nempty" }` |
235
+ | `hask` | Has key | `{ t: "hask", v: "temperature", vt: "str" }` |
236
+ | `jsonata_exp` | JSONata expression | `{ t: "jsonata_exp", v: "payload.temp > 30", vt: "str" }` |
237
+ | `otherwise` | Catch-all (always matches) | `{ t: "otherwise" }` — **Use with caution: may not work in all Node-RED versions.** Prefer explicit `{ t: "lt", v: "15", vt: "num" }` over `otherwise` for the last rule. |
238
+
239
+ **⚠️ `otherwise` operator caveat:** The `otherwise` operator may throw `"operators[rule.t] is not a function"` in certain Node-RED versions. When in doubt, replace `{ t: "otherwise" }` with an explicit inverse rule (e.g., if previous rules check `gte 30` and `gte 15`, the last rule should be `lt 15`).
240
+
241
+ **Best practices for `switch` node:**
242
+ - Always set `outputs` to `rules.length`.
243
+ - Always set `repair` explicitly (usually `false`).
244
+ - With `checkall: "false"`, rules are evaluated top-to-bottom and the first match wins. Ensure rules are ordered from most specific to least specific.
245
+ - For numeric ranges, use `lte`/`gte`/`lt`/`gt` operators in descending or ascending order, ensuring no overlaps when `checkall: "false"`.
246
+ - For `btwn` (between), provide both `v`/`vt` (lower bound) and `v2`/`vt2` (upper bound).
247
+
248
+ ### Full Node Configuration Examples
249
+
250
+ These are the `properties` objects you pass to `create-node` or `update-node`. Extracted from real Node-RED nodes.
251
+
252
+ **Minimal example — single rule, single output:**
253
+
254
+ ```json
255
+ {
256
+ "type": "switch",
257
+ "name": "",
258
+ "property": "payload",
259
+ "propertyType": "msg",
260
+ "rules": [
261
+ { "t": "eq", "v": "pass", "vt": "str" }
262
+ ],
263
+ "checkall": "true",
264
+ "repair": false,
265
+ "outputs": 1
266
+ }
267
+ ```
268
+
269
+ **Full example — 6 rules, all outputs labeled and wired:**
270
+
271
+ ```json
272
+ {
273
+ "type": "switch",
274
+ "name": "Switch example",
275
+ "property": "payload",
276
+ "propertyType": "msg",
277
+ "rules": [
278
+ { "t": "eq", "v": "pass", "vt": "str" },
279
+ { "t": "eq", "v": "fail", "vt": "str" },
280
+ { "t": "gt", "v": "1", "vt": "str" },
281
+ { "t": "lt", "v": "-1", "vt": "str" },
282
+ { "t": "null" },
283
+ { "t": "cont", "v": "text", "vt": "str" }
284
+ ],
285
+ "checkall": "false",
286
+ "repair": false,
287
+ "outputs": 6,
288
+ "outputLabels": ["pass", "fail", "gt 1", "lt -1", "null", "cont text"],
289
+ "info": "Doc"
290
+ }
291
+ ```
292
+
293
+ **What each rule does in this example (with `checkall: "false"` — first match wins):**
294
+
295
+ | Output | Rule | Matches when |
296
+ |--------|------|-------------|
297
+ | 0 | `eq "pass"` | `msg.payload === "pass"` |
298
+ | 1 | `eq "fail"` | `msg.payload === "fail"` |
299
+ | 2 | `gt 1` | `msg.payload > 1` (string-compared, use `vt: "num"` for numeric) |
300
+ | 3 | `lt -1` | `msg.payload < -1` |
301
+ | 4 | `null` | `msg.payload` is `null` or `undefined` |
302
+ | 5 | `cont "text"` | `msg.payload` contains substring `"text"` |
303
+
304
+ > **⚠️ vt types matter:** The examples use `vt: "str"` for string comparison. For numeric comparisons, use `vt: "num"`. The operator `null` has no `v`/`vt` fields — just `{ "t": "null" }`.
305
+
306
+ ### change
307
+ `type: "change"` — Set, change, delete, or move message properties.
308
+
309
+ **⚠️ Legacy fields — still REQUIRED by `create-node`:** `action`, `property`, `from`, `to`, and `reg` appear in Node-RED API responses. Although the editor ignores them for runtime behavior, they are **mandatory for `create-node`** — the Node-RED editor will show a red triangle on the node if ANY of these fields are absent. Set them to their default values:
310
+
311
+ | Legacy field | Required default value |
312
+ |-------------|----------------------|
313
+ | `action` | `""` (empty string) |
314
+ | `property` | `""` (empty string) |
315
+ | `from` | `""` (empty string) |
316
+ | `to` | `""` (empty string) |
317
+ | `reg` | `false` |
318
+
319
+ | Property | Type | Description |
320
+ |----------|------|-------------|
321
+ | `name` | string | Label |
322
+ | `rules` | array | Array of rule objects. Each rule has its own fields depending on `t` (type). See table below. |
323
+ | `info` | string | **Description** field in the Node-RED editor UI. Optional. |
324
+
325
+ **Rule types and their fields:**
326
+
327
+ | `t` (type) | Required fields | Optional fields | Description |
328
+ |-------------|----------------|-----------------|-------------|
329
+ | `"set"` | `p`, `pt`, `to`, `tot` | — | Set a property to a value |
330
+ | `"change"` | `p`, `pt`, `from`, `fromt`, `to`, `tot` | — | Search and replace within a string property. Set `fromt: "re"` to treat `from` as a regex pattern. |
331
+ | `"delete"` | `p`, `pt` | — | Delete a property from the message |
332
+ | `"move"` | `p`, `pt`, `to`, `tot` | — | Move (rename) a property |
333
+
334
+ **Common rule fields:**
335
+
336
+ | Field | Type | Description |
337
+ |-------|------|-------------|
338
+ | `t` | string | Rule type: `"set"`, `"change"`, `"delete"`, or `"move"` |
339
+ | `p` | string | Target property name (e.g., `"payload"`, `"topic"`, `"headers.x-auth"`) |
340
+ | `pt` | string | Property scope: `"msg"`, `"flow"`, `"global"`, `"env"` |
341
+ | `to` | string | Value to set/move to (for `set` and `move`) |
342
+ | `tot` | string | Value type of `to`: `"str"`, `"num"`, `"json"`, `"bool"`, `"template"`, `"jsonata"` |
343
+ | `from` | string | Search string (for `change` — replaces occurrences of this) |
344
+ | `fromt` | string | Type of `from`: `"str"`, `"num"`, `"re"` (regex pattern) |
345
+ | `reg` | boolean | **⚠️ DEPRECATED — do NOT use.** The top-level `reg` field is a legacy leftover. Regex search/replace is controlled by `fromt: "re"` on the individual rule. |
346
+
347
+ ### Full Node Configuration Examples
348
+
349
+ These are the `properties` objects you pass to `create-node` or `update-node`. Extracted from real Node-RED nodes.
350
+
351
+ **Minimal example — set payload to a string:**
352
+
353
+ ```json
354
+ {
355
+ "type": "change",
356
+ "name": "",
357
+ "rules": [
358
+ { "t": "set", "p": "payload", "pt": "msg", "to": "123", "tot": "str" }
359
+ ]
360
+ }
361
+ ```
362
+
363
+ > **Note:** `name` can be `""` (empty string). The legacy fields (`action`, `property`, `from`, `to`, `reg`) are shown by the API but can be omitted in `create-node`.
364
+
365
+ **Full example — set, change, delete, regex change, with documentation:**
366
+
367
+ ```json
368
+ {
369
+ "type": "change",
370
+ "name": "Change example",
371
+ "rules": [
372
+ { "t": "set", "p": "payload", "pt": "msg", "to": "123", "tot": "str" },
373
+ { "t": "change", "p": "payload", "pt": "msg", "from": "45", "fromt": "str", "to": "54", "tot": "str" },
374
+ { "t": "delete", "p": "topic", "pt": "msg" },
375
+ { "t": "change", "p": "payload", "pt": "msg", "from": "\\d+", "fromt": "re", "to": "#", "tot": "str" }
376
+ ],
377
+ "info": "Documentation"
378
+ }
379
+ ```
380
+
381
+ **What each rule does in this example:**
382
+
383
+ | Rule # | Action | Detail |
384
+ |--------|--------|--------|
385
+ | 1 | **set** | Sets `msg.payload` to the string `"123"` |
386
+ | 2 | **change** | Replaces all occurrences of `"45"` with `"54"` in `msg.payload` |
387
+ | 3 | **delete** | Removes `msg.topic` from the message |
388
+ | 4 | **change (regex)** | Replaces all digit sequences (`\d+`) in `msg.payload` with `#`. Regex mode is activated by `fromt: "re"`. |
389
+
390
+ ### range
391
+ `type: "range"` — Scale a numeric value from one range to another.
392
+
393
+ | Property | Type | Description |
394
+ |----------|------|-------------|
395
+ | `name` | string | Label |
396
+ | `action` | string | `"scale"` (linear map), `"clamp"`, `"rollover"`, `"scale and limit"` |
397
+ | `property` | string | Input property (default `"payload"`) |
398
+ | `minin` | number | Input range minimum |
399
+ | `maxin` | number | Input range maximum |
400
+ | `minout` | number | Output range minimum |
401
+ | `maxout` | number | Output range maximum |
402
+ | `round` | boolean | Round result to integer |
403
+
404
+ ### template
405
+ `type: "template"` — Set a property using Mustache templates.
406
+
407
+ | Property | Type | Description |
408
+ |----------|------|-------------|
409
+ | `name` | string | Label |
410
+ | `field` | string | Property to set (default `"payload"`) |
411
+ | `fieldType` | string | `"msg"`, `"flow"`, `"global"` |
412
+ | `template` | string | Mustache template (e.g., `"Hello {{payload.name}}"`) |
413
+ | `syntax` | string | `"mustache"`, `"plain"`, `"json"` |
414
+ | `output` | string | `"str"`, `"json"`, `"yaml"`, `"plain"` |
415
+
416
+ ### filter (RBE)
417
+ `type: "rbe"` — Report By Exception. Passes messages only when a value changes.
418
+
419
+ | Property | Type | Description |
420
+ |----------|------|-------------|
421
+ | `name` | string | Label |
422
+ | `func` | string | `"rbe"` (value change), `"deadband"` (numeric band), `"narrowband"`, `"rbei"` (ignore initial) |
423
+ | `gap` | number | Deadband threshold (for deadband/narrowband modes) |
424
+ | `property` | string | Property to monitor |
425
+ | `start` | string | Initial value |
426
+
427
+ ---
428
+
429
+ ## Flow Control Nodes
430
+
431
+ ### delay
432
+ `type: "delay"` — Delays messages by a fixed time, rate-limits, or waits for a reset.
433
+
434
+ | Property | Type | Description |
435
+ |----------|------|-------------|
436
+ | `name` | string | Label |
437
+ | `pauseType` | string | `"delay"` (fixed), `"rate"` (rate limit), `"delayv"` (variable from property), `"timed"` (hourly/daily) |
438
+ | `timeout` | number | Delay in ms (for delay/delayv) |
439
+ | `timeoutUnits` | string | `"milliseconds"`, `"seconds"`, `"minutes"`, `"hours"`, `"days"` |
440
+ | `rate` | number | Messages per time unit (for rate mode) |
441
+ | `rateUnits` | string | `"second"`, `"minute"`, `"hour"`, `"day"` |
442
+ | `nbRateUnits` | number | Count for rate window |
443
+ | `drop` | boolean | Drop intermediate messages (rate mode) |
444
+
445
+ ### trigger
446
+ `type: "trigger"` — Sends a message, then optionally a second message after a timeout. Useful for debouncing and watchdog timers.
447
+
448
+ | Property | Type | Description |
449
+ |----------|------|-------------|
450
+ | `name` | string | Label |
451
+ | `op1` | string | First output value (sent immediately) |
452
+ | `op1Type` | string | Type of first output |
453
+ | `op2` | string | Second output value (sent after timeout) |
454
+ | `op2Type` | string | Type of second output |
455
+ | `duration` | number | Timeout between outputs (ms) |
456
+ | `extend` | boolean | Extend timer on new input (re-triggerable) |
457
+ | `reset` | string | Reset condition |
458
+
459
+ ### split
460
+ `type: "split"` — Splits a message into a sequence of messages (array → individual, string → lines, object → key/value pairs).
461
+
462
+ | Property | Type | Description |
463
+ |----------|------|-------------|
464
+ | `name` | string | Label |
465
+ | `splt` | string | Split mode: `"\\n"` (lines), `"str"` (fixed length), `"len"` (array chunks) |
466
+ | `spltType` | string | Alternative: `"str"`, `"arr"`, `"obj"`, `"bin"` |
467
+ | `arraySplt` | number | Chunk size for array split |
468
+ | `stream` | boolean | Stream mode: rebuild before splitting |
469
+
470
+ ### join
471
+ `type: "join"` — Joins sequences of messages into a single message (counterpart to split).
472
+
473
+ | Property | Type | Description |
474
+ |----------|------|-------------|
475
+ | `name` | string | Label |
476
+ | `mode` | string | `"auto"` (paired with split), `"custom"` (manual config) |
477
+ | `build` | string | `"array"`, `"string"`, `"merged"`, `"segmented"` |
478
+ | `property` | string | Property to join (default `"payload"`) |
479
+ | `key` | string | Key property for grouping messages |
480
+ | `joiner` | string | Separator string (string mode) |
481
+ | `timeout` | number | Timeout to auto-send partial batches |
482
+
483
+ ### sort
484
+ `type: "sort"` — Sorts messages by a property value.
485
+
486
+ | Property | Type | Description |
487
+ |----------|------|-------------|
488
+ | `name` | string | Label |
489
+ | `target` | string | Property to sort by |
490
+ | `targetType` | string | `"msg"`, `"flow"`, `"global"` |
491
+ | `order` | string | `"ascending"` or `"descending"` |
492
+ | `as_num` | boolean | Compare as numbers |
493
+
494
+ ### batch
495
+ `type: "batch"` — Groups messages into batches by count, interval, or both.
496
+
497
+ | Property | Type | Description |
498
+ |----------|------|-------------|
499
+ | `name` | string | Label |
500
+ | `mode` | string | `"count"`, `"interval"`, `"concat"` (array concat on topic match) |
501
+ | `count` | number | Messages per batch |
502
+ | `interval` | number | Time window between batches |
503
+ | `overlap` | number | Overlap between batches |
504
+
505
+ ---
506
+
507
+ ## Network Nodes
508
+
509
+ ### http in
510
+ `type: "http in"` — Creates an HTTP endpoint. Starts a message when a request arrives.
511
+
512
+ | Property | Type | Description |
513
+ |----------|------|-------------|
514
+ | `name` | string | Label |
515
+ | `url` | string | URL path (e.g., `"/api/data"`) |
516
+ | `method` | string | **⚠️ LOWERCASE only.** `"get"`, `"post"`, `"put"`, `"delete"`, `"patch"` — **Do NOT use uppercase** (`"GET"` does not work). Contrast: `http request` uses uppercase. |
517
+
518
+ **⚠️ CRITICAL — method must be lowercase:** The `http in` node does NOT accept `"GET"` (uppercase). If you use `"GET"` the endpoint registers but never responds (404 errors). Always use `"get"`. The `http request` node DOES use uppercase (`"GET"`, `"POST"`, etc.) — do not confuse the two.
519
+
520
+ **Response:** Must be paired with an `http response` node. `msg.req` and `msg.res` are available. Set `msg.payload` for the response body, `msg.statusCode` for status, and `msg.headers` for response headers.
521
+
522
+ ### http response
523
+ `type: "http response"` — Sends an HTTP response. Must be wired after an `http in` node.
524
+
525
+ | Property | Type | Description |
526
+ |----------|------|-------------|
527
+ | `name` | string | Label |
528
+ | `statusCode` | number | Default status code |
529
+
530
+ ### http request
531
+ `type: "http request"` — Makes an outbound HTTP request.
532
+
533
+ | Property | Type | Description |
534
+ |----------|------|-------------|
535
+ | `name` | string | Label |
536
+ | `method` | string | `"GET"`, `"POST"`, `"PUT"`, `"DELETE"`, `"PATCH"`, `"HEAD"` |
537
+ | `url` | string | Target URL (can use `{{msg.url}}` for dynamic) |
538
+ | `ret` | string | `"txt"`, `"obj"` (parsed JSON), `"bin"` (Buffer) |
539
+ | `tls` | string | TLS config node ID (for HTTPS with custom certs) |
540
+ | `paytoqs` | boolean | Append payload as query string (GET only) |
541
+
542
+ ### websocket in / websocket out
543
+ `type: "websocket in"` / `type: "websocket out"` — WebSocket client or server.
544
+
545
+ | Property | Type | Description |
546
+ |----------|------|-------------|
547
+ | `name` | string | Label |
548
+ | `server` | string | WebSocket config node ID (shared between in/out) |
549
+ | `client` | string | Client config node ID (for client mode) |
550
+
551
+ ### tcp in / tcp out
552
+ `type: "tcp in"` / `type: "tcp out"` — Raw TCP socket communication.
553
+
554
+ | Property | Type | Description |
555
+ |----------|------|-------------|
556
+ | `name` | string | Label |
557
+ | `server` | string | TCP config node ID |
558
+ | `host` | string | Host (client mode) |
559
+ | `port` | number | Port |
560
+ | `datamode` | boolean | Output as stream/Buffer |
561
+ | `ret` | string | Encoding (e.g., `"utf8"`) |
562
+
563
+ ### udp in / udp out
564
+ `type: "udp in"` / `type: "udp out"` — UDP datagram communication.
565
+
566
+ | Property | Type | Description |
567
+ |----------|------|-------------|
568
+ | `name` | string | Label |
569
+ | `group` | string | Multicast group |
570
+ | `port` | number | Local port |
571
+ | `iface` | string | Network interface |
572
+ | `outport` | number | Output port (udp out) |
573
+ | `outhost` | string | Output host (udp out) |
574
+
575
+ ### mqtt in / mqtt out
576
+ `type: "mqtt in"` / `type: "mqtt out"` — MQTT publish/subscribe.
577
+
578
+ | Property | Type | Description |
579
+ |----------|------|-------------|
580
+ | `name` | string | Label |
581
+ | `broker` | string | MQTT broker config node ID |
582
+ | `topic` | string | Topic to subscribe/publish |
583
+ | `qos` | string | `"0"`, `"1"`, `"2"` |
584
+ | `datatype` | string | `"auto-detect"`, `"utf8"`, `"json"`, `"buffer"` |
585
+ | `rh` | boolean | Retain flag (mqtt out only) |
586
+
587
+ ---
588
+
589
+ ## Data Parsing Nodes
590
+
591
+ ### json
592
+ `type: "json"` — Converts between JSON string and JavaScript object.
593
+
594
+ | Property | Type | Description |
595
+ |----------|------|-------------|
596
+ | `name` | string | Label |
597
+ | `property` | string | Property to convert (default `"payload"`) |
598
+ | `action` | string | `"obj"` (string→object), `"str"` (object→string) |
599
+ | `pretty` | boolean | Pretty-print output |
600
+
601
+ ### xml
602
+ `type: "xml"` — Converts between XML string and JavaScript object.
603
+
604
+ | Property | Type | Description |
605
+ |----------|------|-------------|
606
+ | `name` | string | Label |
607
+ | `property` | string | Property to convert |
608
+ | `attr` | string | Attribute prefix (default `"$"`) |
609
+ | `chr` | string | Character data key (default `"_"`) |
610
+
611
+ ### yaml
612
+ `type: "yaml"` — Converts between YAML string and JavaScript object.
613
+
614
+ | Property | Type | Description |
615
+ |----------|------|-------------|
616
+ | `name` | string | Label |
617
+ | `property` | string | Property to convert |
618
+
619
+ ### csv
620
+ `type: "csv"` — Converts between CSV string and JavaScript object/array.
621
+
622
+ | Property | Type | Description |
623
+ |----------|------|-------------|
624
+ | `name` | string | Label |
625
+ | `sep` | string | Column separator (default `","`) |
626
+ | `hdrin` | boolean | First row is header |
627
+ | `hdrout` | string | `"all"`, `"none"`, `"once"` |
628
+ | `skip` | number | Lines to skip |
629
+ | `charset` | string | Character encoding |
630
+
631
+ ### html
632
+ `type: "html"` — Extracts data from HTML using CSS selectors.
633
+
634
+ | Property | Type | Description |
635
+ |----------|------|-------------|
636
+ | `name` | string | Label |
637
+ | `property` | string | Property containing HTML |
638
+ | `tag` | string | CSS selector (e.g., `"div.content p"`) |
639
+ | `ret` | string | `"text"`, `"html"` (innerHTML), `"attr"` |
640
+ | `as` | string | `"single"` or `"multi"` (array) |
641
+ | `attr` | string | Attribute name (when ret is "attr") |
642
+
643
+ ---
644
+
645
+ ## Storage & Exec Nodes
646
+
647
+ ### file
648
+ `type: "file"` — Reads a file and sends contents as payload. Can also append to a file.
649
+
650
+ | Property | Type | Description |
651
+ |----------|------|-------------|
652
+ | `name` | string | Label |
653
+ | `filename` | string | File path |
654
+ | `action` | string | `"read"` (str/Buffer), `"append"` |
655
+ | `outputType` | string | `"utf8"` or `"buffer"` |
656
+ | `deleteAfterRead` | boolean | Delete file after reading |
657
+
658
+ ### file in
659
+ `type: "file in"` — Watches a file and triggers on change. Alternative to `watch`.
660
+
661
+ | Property | Type | Description |
662
+ |----------|------|-------------|
663
+ | `name` | string | Label |
664
+ | `filename` | string | File path |
665
+ | `format` | string | `"lines"`, `"stream"`, `"object"` |
666
+ | `outputType` | string | `"utf8"` or `"buffer"` |
667
+
668
+ ### watch
669
+ `type: "watch"` — Watches a directory for file changes.
670
+
671
+ | Property | Type | Description |
672
+ |----------|------|-------------|
673
+ | `name` | string | Label |
674
+ | `files` | string | Directory path |
675
+ | `recursive` | boolean | Watch subdirectories |
676
+
677
+ ### exec
678
+ `type: "exec"` — Runs a system command and returns its output.
679
+
680
+ | Property | Type | Description |
681
+ |----------|------|-------------|
682
+ | `name` | string | Label |
683
+ | `command` | string | Command to run |
684
+ | `addpay` | boolean | Append msg.payload as arguments |
685
+ | `append` | string | Extra arguments |
686
+ | `useSpawn` | boolean | Use spawn (streaming) instead of exec |
687
+ | `timer` | number | Timeout (seconds) |
688
+ | `winHide` | boolean | Hide window (Windows) |
689
+
690
+ ---
691
+
692
+ ## Config Nodes (Shared Resources)
693
+
694
+ Config nodes hold reusable settings (broker connections, TLS certificates, proxy settings) shared across flow nodes. They are NOT wired into flows — regular nodes reference them by ID.
695
+
696
+ **⚠️ Credential privacy**: Node-RED **never** exposes credential values via the API. `get-node-detail` will NOT show `username`, `password`, `key`, `cert`, etc. — even if configured. A missing `credentials` field does NOT mean credentials are absent.
697
+
698
+ **To create/update credentials**, always use a `credentials` sub-object:
699
+ ```
700
+ create-node / update-node properties: { ..., credentials: { username: "u", password: "p" } }
701
+ ```
702
+
703
+ ### mqtt-broker
704
+ `type: "mqtt-broker"` — MQTT connection settings shared by `mqtt in` and `mqtt out` nodes.
705
+
706
+ | Property | Type | Description |
707
+ |----------|------|-------------|
708
+ | `name` | string | Label |
709
+ | `broker` | string | MQTT server hostname |
710
+ | `port` | string | MQTT server port |
711
+ | `usetls` | boolean | Enable TLS |
712
+ | `clientid` | string | Client ID (auto-generated if empty) |
713
+ | `keepalive` | string | Keep-alive interval (seconds) |
714
+ | `cleansession` | boolean | Clean session flag |
715
+
716
+ **Credentials** (send inside `credentials: { ... }` — NOT at top level):
717
+ | Field | Type | Description |
718
+ |-------|------|-------------|
719
+ | `username` | string | MQTT username |
720
+ | `password` | string | MQTT password |
721
+
722
+ ### http-proxy
723
+ `type: "http-proxy"` — HTTP proxy settings shared by `http request` node.
724
+
725
+ | Property | Type | Description |
726
+ |----------|------|-------------|
727
+ | `name` | string | Label |
728
+ | `url` | string | Proxy URL (e.g., `http://proxy.example.com:8080`) |
729
+ | `noproxy` | string | Bypass proxy for these hosts |
730
+
731
+ **Credentials** (send inside `credentials: { ... }`):
732
+ | Field | Type | Description |
733
+ |-------|------|-------------|
734
+ | `username` | string | Proxy username |
735
+ | `password` | string | Proxy password |
736
+
737
+ ### tls-config
738
+ `type: "tls-config"` — TLS/SSL certificate configuration shared by nodes that need secure connections.
739
+
740
+ | Property | Type | Description |
741
+ |----------|------|-------------|
742
+ | `name` | string | Label |
743
+ | `servername` | string | Expected server name (SNI) |
744
+ | `verifyservercert` | boolean | Verify server certificate |
745
+
746
+ **Credentials** (send inside `credentials: { ... }`):
747
+ | Field | Type | Description |
748
+ |-------|------|-------------|
749
+ | `cert` | string | Client certificate (PEM) |
750
+ | `key` | string | Client private key (PEM) |
751
+ | `ca` | string | CA certificate (PEM) |
752
+ | `passphrase` | string | Private key passphrase |
753
+
754
+ ### websocket-listener / websocket-client
755
+ `type: "websocket-listener"` / `type: "websocket-client"` — WebSocket server/client config shared by `websocket in` and `websocket out` nodes.
756
+
757
+ | Property | Type | Description |
758
+ |----------|------|-------------|
759
+ | `name` | string | Label |
760
+ | `path` | string | URL path (listener) or full URL (client) |
761
+ | `wholemsg` | string | Send entire msg object (not just payload) |
762
+
763
+ **Credentials** (send inside `credentials: { ... }`):
764
+ | Field | Type | Description |
765
+ |-------|------|-------------|
766
+ | `username` | string | Auth username (if using auth) |
767
+ | `password` | string | Auth password (if using auth) |
768
+
769
+ ---
770
+
771
+ ## Function Node — Deep Dive
772
+
773
+ The function node (`type: "function"`) is the most powerful node — it runs arbitrary JavaScript for each incoming message.
774
+
775
+ ### Setting Code via MCP
776
+
777
+ Use the `func` property in `create-node` or `update-node` to set the function body:
778
+
779
+ ```
780
+ properties: { func: "msg.payload = msg.payload * 2;\nreturn msg;" }
781
+ ```
782
+
783
+ Set the number of output ports with `outputs`:
784
+
785
+ ```
786
+ properties: { outputs: 3 }
787
+ ```
788
+
789
+ > **Tip:** Use `get-node-detail` to read a function node's current code. It returns the full `func` field.
790
+
791
+ ### Full Node Configuration Examples
792
+
793
+ These are the `properties` objects you pass to `create-node` or `update-node`. They represent **real, working** node configurations as exposed by the Node-RED API.
794
+
795
+ **Minimal example — single output, pass-through:**
796
+
797
+ ```json
798
+ {
799
+ "type": "function",
800
+ "name": "function 1",
801
+ "func": "\nreturn msg;",
802
+ "outputs": 1,
803
+ "timeout": 0,
804
+ "noerr": 0,
805
+ "initialize": "",
806
+ "finalize": "",
807
+ "libs": []
808
+ }
809
+ ```
810
+
811
+ > **⚠️ CRITICAL — do NOT omit default fields.** Even though these are Node-RED defaults, the MCP `create-node` does NOT auto-populate them. You MUST set them explicitly, or the Node-RED editor will show red triangle errors on the node.
812
+ >
813
+ > **Minimum `create-node` properties for a function node with 1 output:**
814
+ > ```js
815
+ > {
816
+ > name: "myFunction",
817
+ > func: "return msg;",
818
+ > outputs: 1,
819
+ > noerr: 0,
820
+ > initialize: "",
821
+ > finalize: "",
822
+ > libs: []
823
+ > }
824
+ > ```
825
+ >
826
+ > **Minimum for 2 outputs:** same, with `outputs: 2`.
827
+
828
+ **Full example — all available properties:**
829
+
830
+ ```json
831
+ {
832
+ "type": "function",
833
+ "name": "Function example",
834
+ "func": "const output1 = 1;\nconst output2 = \"text\";\nconst msg1 = {\n \"payload\": output1\n};\nconst msg2 = {\n \"payload\": output2\n};\nreturn [msg1, msg2];",
835
+ "outputs": 2,
836
+ "timeout": 0,
837
+ "noerr": 0,
838
+ "initialize": "// Code added here will be run once\n// whenever the node is started.\n// This is optional\nnode.warn(\"this is the start\");",
839
+ "finalize": "// Code added here will be run when the\n// node is being stopped or re-deployed.\n// This is optional\nnode.warn(\"this is the end\");",
840
+ "libs": [{ "var": "uuidv4", "module": "uuidv4" }],
841
+ "inputLabels": ["input label"],
842
+ "outputLabels": ["output 1 label", "output 2 label"],
843
+ "info": "This is the documentation of this function node"
844
+ }
845
+ ```
846
+
847
+ **Optional fields explained:**
848
+
849
+ | Field | Type | Optional | Description |
850
+ |-------|------|----------|-------------|
851
+ | `name` | string | Yes | Display label on the node |
852
+ | `func` | string | **No** | JavaScript body. Plain string, `\n` for line breaks. |
853
+ | `outputs` | number | **No** | Number of output ports. Must match array length in `return [a, b, ...]`. |
854
+ | `timeout` | number | Yes | Seconds before the function is killed (0 = no timeout). |
855
+ | `noerr` | number | Yes | If `1`, errors thrown inside `func` are NOT caught by catch nodes (0 = caught). |
856
+ | `initialize` | string | Yes | Code run once when the node starts. Use for setup (connections, config loading). |
857
+ | `finalize` | string | Yes | Code run when the node stops or re-deploys. Use for cleanup (close connections). |
858
+ | `libs` | array | Yes | Array of `{ "var": "<name>", "module": "<npm-package>" }`. Loads npm modules into the function scope. Node-RED must have the package installed. |
859
+ | `inputLabels` | array | Yes | Labels for input ports shown in the editor. One string per input. |
860
+ | `outputLabels` | array | Yes | Labels for output ports shown in the editor. One string per output. |
861
+ | `info` | string | Yes | **Description** field in the Node-RED editor UI. Rich text documentation for the node. |
862
+
863
+ ### Available Globals
864
+
865
+ Inside the function body, these globals are available:
866
+
867
+ | Global | Description |
868
+ |--------|-------------|
869
+ | `msg` | The incoming message object |
870
+ | `node` | The function node instance (`node.id()`, `node.name()`) |
871
+ | `context` | Node-scoped key-value store |
872
+ | `flow` | Flow-scoped key-value store |
873
+ | `global` | Global-scoped key-value store |
874
+ | `env` | Flow environment variables helper |
875
+ | `RED` | Node-RED runtime (advanced: `RED.util.cloneMessage()`, `RED.nodes.getNode()`) |
876
+
877
+ ### Return Semantics
878
+
879
+ The function node's behavior depends on what you return:
880
+
881
+ | Return Value | Behavior |
882
+ |-------------|----------|
883
+ | `return msg;` | Send `msg` to output 0 |
884
+ | `return [msg1, msg2];` | Send `msg1` to output 0, `msg2` to output 1 |
885
+ | `return [msg1, null, msg3];` | Send `msg1` to output 0, nothing to output 1, `msg3` to output 2 |
886
+ | `return null;` | Send nothing (stop the message) |
887
+ | No return | Same as `return null;` |
888
+ | Single element array `return [msg];` | Send `msg` to output 0 |
889
+
890
+ ### Context API
891
+
892
+ Within a function node, you can **read AND write** context at all three scopes:
893
+
894
+ ```javascript
895
+ // Node scope (this node only)
896
+ const count = context.get("count") || 0;
897
+ context.set("count", count + 1);
898
+
899
+ // Flow scope (all nodes on this tab)
900
+ const config = flow.get("config") || {};
901
+ config.updated = Date.now();
902
+ flow.set("config", config);
903
+
904
+ // Global scope (entire instance)
905
+ const users = global.get("users") || [];
906
+ users.push(msg.payload.username);
907
+ global.set("users", users);
908
+ ```
909
+
910
+ > **Contrast with MCP:** The MCP `get-context` tool can only **read** context; `delete-context` can **delete**. The Admin API does not support writing context. Use a function node as shown above when you need to write context values.
911
+
912
+ ### Async Patterns
913
+
914
+ For asynchronous operations (HTTP requests, file I/O, database queries), use the callback pattern:
915
+
916
+ ```javascript
917
+ // Pattern 1: node.send()
918
+ doAsyncWork(msg, (err, result) => {
919
+ if (err) { node.error(err, msg); return; }
920
+ msg.payload = result;
921
+ node.send(msg);
922
+ });
923
+ return; // MUST return after calling async
924
+
925
+ // Pattern 2: done() (requires Outputs: 1)
926
+ doAsyncWork(msg, (err, result) => {
927
+ if (err) { node.error(err, msg); return; }
928
+ msg.payload = result;
929
+ node.send(msg);
930
+ done();
931
+ });
932
+
933
+ // Pattern 3: Promise with async/await
934
+ (async () => {
935
+ try {
936
+ const result = await doAsyncWork(msg);
937
+ msg.payload = result;
938
+ node.send(msg);
939
+ } catch (err) {
940
+ node.error(err, msg);
941
+ }
942
+ })();
943
+ return;
944
+
945
+ // Pattern 4: Multiple async outputs
946
+ doAsync1(msg, (err, r1) => {
947
+ doAsync2(msg, (err, r2) => {
948
+ node.send([{payload: r1}, {payload: r2}]);
949
+ });
950
+ });
951
+ return;
952
+ ```
953
+
954
+ **Critical rule:** Always `return;` after calling an async function. Without it, the function node will continue to the end and the closure runs after, causing unpredictable behavior.
955
+
956
+ ### Logging & Errors
957
+
958
+ ```javascript
959
+ // Logging (visible in Node-RED console, not in debug sidebar)
960
+ node.log("Processing message from " + msg.topic);
961
+ node.warn("Unexpected payload type: " + typeof msg.payload);
962
+ node.trace("msg._msgid: " + msg._msgid); // Debug-level
963
+
964
+ // Error handling
965
+ if (!msg.payload) {
966
+ node.error("Empty payload", msg); // Caught by catch nodes
967
+ return null;
968
+ }
969
+
970
+ // Throwing inside catch
971
+ try {
972
+ JSON.parse(msg.payload);
973
+ } catch (e) {
974
+ node.error("Invalid JSON: " + e.message, msg);
975
+ return null;
976
+ }
977
+ ```
978
+
979
+ `node.error("message", msg)` sends the error to catch nodes. Always pass `msg` as the second argument so the catch node can access the original message.
980
+
981
+ ### Inline Code Examples
982
+
983
+ **Transform payload:**
984
+ ```javascript
985
+ msg.payload = msg.payload.toUpperCase();
986
+ return msg;
987
+ ```
988
+
989
+ **Filter messages:**
990
+ ```javascript
991
+ if (msg.payload.temperature > 30) {
992
+ return msg; // Pass through
993
+ }
994
+ return null; // Drop
995
+ ```
996
+
997
+ **Add metadata:**
998
+ ```javascript
999
+ msg.topic = "sensor/" + msg.payload.deviceId;
1000
+ msg.timestamp = Date.now();
1001
+ return msg;
1002
+ ```
1003
+
1004
+ **Count and tag:**
1005
+ ```javascript
1006
+ const count = context.get("count") || 0;
1007
+ context.set("count", count + 1);
1008
+ msg.payload = { index: count, data: msg.payload };
1009
+ return msg;
1010
+ ```
1011
+
1012
+ **Route to multiple outputs (3 outputs configured):**
1013
+ ```javascript
1014
+ if (msg.payload.type === "alarm") {
1015
+ return [msg, null, null]; // Output 0 only
1016
+ } else if (msg.payload.type === "warning") {
1017
+ return [null, msg, null]; // Output 1 only
1018
+ }
1019
+ return [null, null, msg]; // Output 2 (info)
1020
+ ```