@mysterysd/node-red-mcp
v2.0.4
Published
MCP server for Node-RED flow management via the Admin API
Readme
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.
Architecture
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_WSData Flow
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: [...] }Features
- 🚀 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
Table of Contents
- Features
- Installation
- Quick Start
- Configuration
- Authentication
- Usage
- Tools Reference
- Resources
- Prompts
- Node Registry
- Client Library
- Graph Engine
- Development
- Docker
- License
Installation
npm install -g @mysterysd/node-red-mcpOr run directly without installing:
npx @mysterysd/node-red-mcpPrerequisites
- Node.js >= 18
- Node-RED instance running with the Admin API enabled (default:
http://localhost:1880)
Quick Start
# 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.
Claude Desktop / VS Code
{
"mcpServers": {
"node-red": {
"command": "npx",
"args": ["@mysterysd/node-red-mcp"],
"env": {
"NODE_RED_URL": "http://localhost:1880",
"NODE_RED_ACCESS_TOKEN": "your-token"
}
}
}
}opencode
{
"mcpServers": {
"node-red": {
"command": "node",
"args": ["path/to/dist/index.js", "stdio"],
"env": {
"NODE_RED_URL": "http://localhost:1880"
}
}
}
}Configuration
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.exampleto.envas a reference for all available environment variables.
Authentication
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_TOKENin shared or version-controlled config files. Use environment-specific secrets whenever possible.
Usage
# 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 streamableHttpTransport Modes
| 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 |
Debug Capture
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.
Tools Reference
All 38 tools are registered with the MCP server. Each returns JSON output.
Auth
| 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 |
Runtime
| 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) |
Flows
| 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) |
Node-Level Mutations
| 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. |
Graph
| 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 |
Nodes
| 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 |
Resources
| 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 |
Prompts
| 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 |
Node Registry
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]How it works
- 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" }
]
}Client Library
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" });API
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>;
}Graph Engine
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]Graph Types
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>;
}Functions
| 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 |
Development
# 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:fixProject Structure
src/
├── 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 pointTest Stats
- 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%)
Docker
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-mcpLicense
MIT © Mystery SD
