A production-ready MCP server that connects AI assistants (Claude, Copilot, etc.) to the Node-RED Admin API.
Inspect flows, manage nodes, analyze graph topology, capture debug output, apply JSON patches, and rollback changes — all through natural language.
graph TB
subgraph "AI Client"
A[Claude Desktop]
B[VS Code MCP]
C[Custom Client]
end
subgraph "MCP Server @mysterysd/node-red-mcp"
direction LR
T[Transport Layer<br/>stdio / SSE / streamableHttp]
S[Server Factory<br/>McpServer]
T --> S
S --> Tools[38 Tools<br/>flows / nodes / graph / auth / runtime]
S --> Res[7 Resources<br/>settings / flows / graph / registry]
S --> Prompts[3 Prompts<br/>analyze / repair / refactor]
Tools --> GE[Graph Engine<br/>DAG analysis / cycles / search]
Tools --> NR[Node Registry<br/>persistent name-to-ID lookup]
Tools --> Snap[Snapshots<br/>20-deep ring buffer per flow]
Tools --> Debug[Debug Capture<br/>WebSocket /comms]
end
subgraph "Node-RED Instance"
NR_API[Admin REST API<br/>:1880]
NR_WS[WebSocket<br/>:1880/comms]
end
A & B & C -->|MCP Protocol| T
S -->|axios| NR_API
Debug -->|ws| NR_WS
sequenceDiagram
participant AI as AI Assistant
participant MCP as node-red-mcp
participant NR as Node-RED
AI->>MCP: flows-create(label, nodes)
MCP->>MCP: auto-generate node IDs
MCP->>MCP: auto-layout positions
MCP->>NR: POST /flow
NR-->>MCP: { id, rev }
MCP->>MCP: refresh NodeRegistry
MCP-->>AI: { status, flowId }
AI->>MCP: flows-add-node(flowId, node)
MCP->>NR: GET /flow/:id
MCP->>MCP: snapshot current state
MCP->>MCP: append node, validate wires
MCP->>NR: PUT /flow/:id
MCP->>MCP: refresh NodeRegistry
MCP-->>AI: { status, nodeId }
AI->>MCP: nodes-resolve(query)
MCP->>MCP: lookup in NodeRegistry
MCP-->>AI: { matches: [...] }
- 🚀 3 Transport Modes — stdio (default), SSE, streamable HTTP
- 🔌 38 MCP Tools — full coverage of flows, nodes, runtime, auth, inject, graph analysis, and debug capture
- 🧩 5 Node-Level Mutation Tools — add, remove, update, rewire, and move individual nodes without rewriting entire flows
- 📊 Graph Engine — auto-builds directed acyclic graph (DAG) from flow topology, detects cycles, sources, sinks, and computes node categories
- 🗃️ Node Registry — persistent, auto-synced hot DB for looking up node IDs by name, type, label, and flow
- 🔍 Semantic Search — query flows by node name, type, topic, URL, or any metadata
- 🐛 Live Debug Capture — WebSocket-based capture of Node-RED debug output, combined with inject or standalone
- 🔧 JSON Patch — RFC 6902 compliant patch engine for incremental flow edits
- 📸 Snapshots — in-memory 20-entry ring buffer per flow for rollback
- 🔗 7 Resources + 3 Prompts — inspect runtime settings, diagnostics, flows, graph, registry, and get AI-assisted analysis
- Features
- Installation
- Quick Start
- Configuration
- Authentication
- Usage
- Tools Reference
- Resources
- Prompts
- Node Registry
- Client Library
- Graph Engine
- Development
- Docker
- License
npm install -g @mysterysd/node-red-mcpOr run directly without installing:
npx @mysterysd/node-red-mcp- Node.js >= 18
- Node-RED instance running with the Admin API enabled (default:
http://localhost:1880)
# 1. Set your Node-RED URL (default: http://localhost:1880)
export NODE_RED_URL=http://my-nodered:1880
# 2. Start the MCP server in stdio mode
npx @mysterysd/node-red-mcpThen configure your MCP client:
Tip
The server auto-refreshes the Node Registry on startup, so all your nodes are immediately searchable by name.
{
"mcpServers": {
"node-red": {
"command": "npx",
"args": ["@mysterysd/node-red-mcp"],
"env": {
"NODE_RED_URL": "http://localhost:1880",
"NODE_RED_ACCESS_TOKEN": "your-token"
}
}
}
}{
"mcpServers": {
"node-red": {
"command": "node",
"args": ["path/to/dist/index.js", "stdio"],
"env": {
"NODE_RED_URL": "http://localhost:1880"
}
}
}
}All configuration is via environment variables:
| Variable | Default | Description |
|---|---|---|
NODE_RED_URL |
http://localhost:1880 |
Base URL of the Node-RED Admin API |
NODE_RED_TOKEN |
— | Bearer token for API auth (takes precedence over NODE_RED_ACCESS_TOKEN) |
NODE_RED_ACCESS_TOKEN |
— | Alternative name for the bearer token |
NODE_RED_USERNAME |
— | Username for password-based auth |
NODE_RED_PASSWORD |
— | Password for password-based auth |
MCP_SERVER_PORT |
3001 (SSE), 3002 (streamable HTTP) |
HTTP server port for HTTP transports |
PORT |
3001 (SSE), 3002 (streamable HTTP) |
Fallback port (overridden by MCP_SERVER_PORT) |
NODE_REGISTRY_PATH |
./node-registry.json |
File path for persistent Node Registry |
Note
Copy .env.example to .env as a reference for all available environment variables.
The server supports two auth methods:
-
Token-based (recommended):
export NODE_RED_TOKEN="your-bearer-token"
-
Password-based (auto-login on first request):
export NODE_RED_USERNAME="admin" export NODE_RED_PASSWORD="your-password"
If neither is set, the server will attempt unauthenticated requests (Node-RED's default for local-only deployments).
Warning
Avoid using NODE_RED_TOKEN in shared or version-controlled config files. Use environment-specific secrets whenever possible.
# stdio mode (default — for Claude Desktop, VS Code, etc.)
node-red-mcp
node-red-mcp stdio
# SSE mode (HTTP server on :3001)
node-red-mcp sse
# Streamable HTTP mode (HTTP server on :3002)
node-red-mcp streamableHttp| Mode | Protocol | Best For |
|---|---|---|
stdio |
stdin/stdout JSON-RPC | Claude Desktop, VS Code MCP extensions |
sse |
Server-Sent Events | Remote or containerized setups |
streamableHttp |
HTTP POST + DELETE | Stateless proxies, load-balanced deployments |
Two tools provide real-time debug output capture via Node-RED WebSocket (/comms):
| Tool | Description |
|---|---|
node-red-inject (with waitForDebug) |
Inject + capture — fires inject, listens, returns both result and debug messages in one call |
node-red-debug-listen |
Standalone listener — connects, subscribes to debug, captures messages for N seconds with optional node/flow filtering |
Example (one-call inject + verify):
// node-red-inject({ nodeId: "my-inject", waitForDebug: 5 })
→ { status: "injected", debug: [...], debugCount: 3 }Both tools handle Node-RED's array-batched WebSocket format automatically.
All 38 tools are registered with the MCP server. Each returns JSON output.
| Tool | Description |
|---|---|
node-red-auth-get-scheme |
Inspect the active Node-RED admin auth scheme |
node-red-auth-login |
Exchange credentials for a bearer token |
node-red-auth-revoke |
Revoke an existing bearer token |
| Tool | Description |
|---|---|
node-red-runtime-get-settings |
Read runtime settings |
node-red-runtime-get-diagnostics |
Read runtime diagnostics |
node-red-runtime-get-flow-state |
Read runtime flow state |
node-red-runtime-set-flow-state |
Update runtime flow state |
node-red-debug-listen |
Capture debug messages via WebSocket for a duration (optionally filtered by node/flow) |
| Tool | Description |
|---|---|
node-red-flows-list |
List active flow tabs and metadata |
node-red-flows-get |
Get a single flow by id or label |
node-red-flows-create |
Create a new flow tab with nodes (auto-generates IDs, auto-layouts positions, remaps wires) |
node-red-flows-update |
Replace an existing flow tab (auto-layouts positions) |
node-red-flows-patch |
Apply JSON Patch (RFC 6902) operations to a flow |
node-red-flows-delete |
Delete a flow tab |
node-red-flows-clone |
Clone an existing flow tab |
node-red-flows-rollback |
Rollback a flow to a previous snapshot |
node-red-inject |
Trigger an inject node by its ID (optionally waitForDebug to capture debug output in one call) |
| Tool | Description |
|---|---|
node-red-flows-add-node |
Add a single node to an existing flow tab. Auto-generates ID if omitted, validates wire targets, positions intelligently. |
node-red-flows-remove-node |
Remove a single node from a flow tab. Cleans up wire references from all remaining nodes. |
node-red-flows-update-node |
Update specific properties of a single node by ID. Deep-merges the provided properties onto the existing node. Reports which keys changed. |
node-red-flows-rewire-node |
Replace all wire connections for a specific node. Validates all target IDs exist in the flow. |
node-red-flows-move-node |
Move a single node to a specific visual position (x, y) within a flow tab. |
| Tool | Description |
|---|---|
node-red-graph-analyze |
Analyze topology, dependencies, and graph health |
node-red-graph-summary |
Summary statistics and risk assessment |
node-red-graph-visualize |
Generate a human-readable graph view |
node-red-graph-dependencies |
Resolve upstream and downstream dependencies for a node |
node-red-graph-query |
Search nodes by semantic query |
node-red-graph-pack |
Context pack for semantic search with neighbor expansion |
node-red-graph-export |
Export the full flow graph in serializable format |
| Tool | Description |
|---|---|
node-red-nodes-list |
List installed node modules and node sets |
node-red-nodes-install |
Install a node module from npm |
node-red-nodes-get-module |
Inspect a specific node module |
node-red-nodes-toggle-module |
Enable or disable a node module |
node-red-nodes-remove-module |
Remove a node module |
node-red-nodes-get-set |
Inspect a specific node set within a module |
node-red-nodes-toggle-set |
Enable or disable a node set |
node-red-nodes-resolve |
Look up node IDs by name, type, description, or flow label using the Node Registry |
| URI | Description |
|---|---|
node-red://runtime/settings |
Current runtime settings (JSON) |
node-red://runtime/diagnostics |
Runtime diagnostics (JSON) |
node-red://flows |
All active flows (raw JSON) |
node-red://nodes |
All installed nodes (JSON) |
node-red://graph |
Full graph snapshot (serializable format) |
node-red://flow/{id} |
Single flow by ID (template) |
node-red://registry |
Node Registry snapshot — all indexed nodes with names, types, flow labels, and categories |
| Prompt | Description |
|---|---|
analyze-flow |
Analyze a flow for risks, dependencies, and graph structure |
repair-flow |
Draft a repair plan for invalid or broken flow wiring |
refactor-flow |
Suggest a graph-aware refactor plan for a flow |
The Node Registry is a persistent, auto-synced hot database that indexes every node across all flows.
graph LR
subgraph "Node Registry"
NR[(registry.json)]
L[lookupInRegistry]
GF[getRegistryForFlow]
GS[getRegistrySnapshot]
end
MC[flows-create] -->|refresh| NR
MU[5 Mutation Tools] -->|refresh| NR
FU[flows-update] -->|refresh| NR
ST[Server Start] -->|initial load| NR
NR -->|query| RS[nodes-resolve tool]
NR -->|expose| RES[node-red://registry resource]
- Startup:
refreshRegistry(client)fetches all flows via Admin API and builds an index - Persistence: Index saved to
NODE_REGISTRY_PATH(default:./node-registry.json) as JSON - Auto-sync: Every mutation tool (add, remove, update, rewire, move) and flows-create/update triggers a refresh
- Lookup: Search by name, type, ID, or flow label with fuzzy matching and result ranking
- Categories: Each node is categorized as
source,debug,transform,network,messaging,storage,template,dashboard,config, orother
Example query:
// nodes-resolve({ query: "Temperature", flowLabel: "Factory Floor" })
→ {
"matches": [
{ "id": "abc123", "type": "inject", "name": "Temperature Sensor",
"flowId": "...", "flowLabel": "Factory Floor", "category": "source" }
]
}The client library (src/client/index.ts) provides a standalone NodeRedClient class you can use programmatically:
import { NodeRedClient } from "@mysterysd/node-red-mcp/client";
const client = new NodeRedClient({
baseUrl: "http://localhost:1880",
accessToken: process.env.NODE_RED_ACCESS_TOKEN,
});
// List all flows
const flows = await client.getFlows();
// Get a specific flow
const flow = await client.getFlow("flow-id");
// Install a node
await client.installNode({ module: "node-red-contrib-something" });class NodeRedClient {
baseUrl: string; // Public getter (for WebSocket URL construction)
constructor(options: ClientOptions);
// Auth
getAuthScheme(): Promise<AuthScheme>;
login(credentials?: AuthCredentials): Promise<TokenResponse>;
revoke(token?: string): Promise<unknown>;
// Runtime
getSettings(): Promise<Record<string, unknown>>;
getDiagnostics(): Promise<Record<string, unknown>>;
getFlowState(): Promise<unknown>;
setFlowState(state: unknown): Promise<unknown>;
// Flows
getFlows(): Promise<FlowsResponse>;
getFlow(id: string): Promise<FlowDocument>;
createFlow(flow: FlowDocument): Promise<FlowDocument>;
updateFlow(id: string, flow: FlowDocument): Promise<FlowDocument>;
deleteFlow(id: string): Promise<unknown>;
// Nodes
listNodes(): Promise<unknown>;
installNode(payload: Record<string, unknown>): Promise<unknown>;
getNodeModule(module: string): Promise<unknown>;
toggleNodeModule(module: string, enabled: boolean): Promise<unknown>;
removeNodeModule(module: string): Promise<unknown>;
getNodeSet(module: string, set: string): Promise<unknown>;
toggleNodeSet(module: string, set: string, enabled: boolean): Promise<unknown>;
// Inject
inject(nodeId: string): Promise<unknown>;
}The graph engine (src/graph/) is a standalone library for building and analyzing Node-RED flow topologies:
import { buildGraph, formatGraph } from "@mysterysd/node-red-mcp/graph/engine";
import { queryGraph } from "@mysterysd/node-red-mcp/graph/search";
import { applyPatch } from "@mysterysd/node-red-mcp/graph/patch";graph TD
RAW[Raw Flows<br/>from Admin API] --> BG[buildGraph]
BG --> FG[FlowGraph]
FG --> VA[graph-analyze]
FG --> VS[graph-visualize]
FG --> DP[graph-dependencies]
FG --> QY[queryGraph<br/>semantic search]
FG --> PK[graph-pack<br/>context pack]
FG --> SM[summarizeGraph]
FG --> EX[graph-export]
interface FlowGraph {
rev: string;
tabs: FlowTab[];
nodes: FlowNode[];
nodeById: Map<string, FlowNode>;
adjacency: Map<string, string[]>; // forward edges
reverseAdjacency: Map<string, Set<string>>; // backward edges
edges: GraphEdge[];
sources: string[]; // nodes with 0 in-degree
sinks: string[]; // nodes with 0 out-degree
cycles: string[][]; // detected cycles
categories: Map<string, NodeCategory>;
}| Function | Description |
|---|---|
buildGraph(flowsResponse) |
Build a FlowGraph from raw Node-RED API response |
categorizeNode(node) |
Classify node as source, debug, transform, subflow, config, dashboard, or other |
isConfigNode(node) |
Check if a node is a config node (no position, no wires) |
collectClosure(map, start) |
BFS/DFS from a start node to collect all reachable nodes |
graphToSerializable(graph) |
Convert graph to plain JSON-safe format |
formatGraph(graph, focusId?) |
Produce a human-readable topology view |
findCycles(adjacency) |
Detect all cycles in a directed graph |
buildSemanticIndex(graph) |
Build a searchable text index over all nodes |
queryGraph(graph, query, flowId?) |
Search nodes by semantic query (returns scored results) |
collectRelevantSubgraph(graph, index, query, opts) |
Find matching nodes + expand to neighbors |
summarizeGraph(graph) |
Generate a summary with counts, categories, risky nodes |
applyPatch(document, operations) |
Apply RFC 6902 JSON Patch operations |
# Clone and install
git clone https://github.com/mysterysd/node-red-mcp.git
cd node-red-mcp
npm install
# Build
npm run build
# Watch mode
npm run watch
# Test (with coverage)
npm test
# Lint
npm run prettier:check
npm run prettier:fixsrc/
├── config.ts # Centralized env var config
├── client/ # NodeRedClient — Admin API wrapper
├── graph/ # Graph engine (types, engine, search, patch, registry)
│ └── registry.ts # NodeRegistry — persistent name-to-ID hot DB
├── tools/
│ ├── auth/ # 3 tools: get-scheme, login, revoke
│ ├── runtime/ # 5 tools: settings, diagnostics, flow-state, debug-listen
│ ├── flows/ # 14 tools: list, get, create, update, patch, delete, clone,
│ │ # rollback, inject, add-node, remove-node, update-node,
│ │ # rewire-node, move-node
│ │ └── mutation-utils.ts # Shared mutateFlow() utility
│ ├── graph/ # 7 tools: analyze, summary, visualize, dependencies,
│ │ # query, pack, export
│ └── nodes/ # 8 tools: list, install, get-module, toggle-module,
│ # remove-module, get-set, toggle-set, resolve
├── resources/ # 7 MCP resource handlers (incl. registry)
├── prompts/ # 3 MCP prompt templates
├── server/ # McpServer factory
├── transports/ # stdio, SSE, streamableHttp
├── __tests__/ # 171 unit tests (17 files)
└── index.ts # CLI entry point
- 171 unit tests across 17 test files
- ~77% overall coverage (core modules at 100%)
- 25 integration tests against live Node-RED
- Coverage includes: all 5 mutation tools (94–100%), registry (90%), resolve (100%)
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/package*.json ./
RUN npm ci --omit=dev
EXPOSE 3002
ENV NODE_RED_URL=http://nodered:1880
CMD ["node", "dist/index.js", "streamableHttp"]Build and run:
docker build -t node-red-mcp .
docker run -e NODE_RED_URL=http://host.docker.internal:1880 -p 3002:3002 node-red-mcpMIT © Mystery SD
