Skip to main content
Glama
kskwon02

node-red-mcp

by kskwon02
README.md
<p align="center">
  <img alt="node-red-mcp" src="docs/node-red-ai-mcp.png" width="60%">
</p>

<div align="center">

[![npm version](https://img.shields.io/npm/v/@mysterysd/node-red-mcp?color=%238F00FF&logo=npm)](https://www.npmjs.com/package/@mysterysd/node-red-mcp)
[![Node.js](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white)](https://nodejs.org)
[![TypeScript](https://img.shields.io/badge/types-%3E%205.6-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/mysterysd/node-red-mcp/pulls)

**A production-ready [MCP](https://modelcontextprotocol.io) server that connects AI assistants (Claude, Copilot, etc.) to the [Node-RED](https://nodered.org/) Admin API.**

Inspect flows, manage nodes, analyze graph topology, capture debug output, apply JSON patches, and rollback changes — all through natural language.

</div>

---

## Architecture

```mermaid
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
```

## Data Flow

```mermaid
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](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Authentication](#authentication)
- [Usage](#usage)
- [Tools Reference](#tools-reference)
  - [Auth](#auth)
  - [Runtime](#runtime)
  - [Flows](#flows)
  - [Node-Level Mutations](#node-level-mutations)
  - [Graph](#graph)
  - [Nodes](#nodes)
- [Resources](#resources)
- [Prompts](#prompts)
- [Node Registry](#node-registry)
- [Client Library](#client-library)
- [Graph Engine](#graph-engine)
- [Development](#development)
- [Docker](#docker)
- [License](#license)

---

## Installation

```bash
npm install -g @mysterysd/node-red-mcp
```

Or run directly without installing:

```bash
npx @mysterysd/node-red-mcp
```

### Prerequisites

- **Node.js** >= 18
- **Node-RED** instance running with the [Admin API](https://nodered.org/docs/api/admin/) enabled (default: `http://localhost:1880`)

## Quick Start

```bash
# 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-mcp
```

Then 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

```json
{
  "mcpServers": {
    "node-red": {
      "command": "npx",
      "args": ["@mysterysd/node-red-mcp"],
      "env": {
        "NODE_RED_URL": "http://localhost:1880",
        "NODE_RED_ACCESS_TOKEN": "your-token"
      }
    }
  }
}
```

### opencode

```json
{
  "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.example` to `.env` as a reference for all available environment variables.

## Authentication

The server supports two auth methods:

1. **Token-based** (recommended):
   ```bash
   export NODE_RED_TOKEN="your-bearer-token"
   ```

2. **Password-based** (auto-login on first request):
   ```bash
   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.

## Usage

```bash
# 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
```

### Transport 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):

```json
// 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.

```mermaid
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

1. **Startup**: `refreshRegistry(client)` fetches all flows via Admin API and builds an index
2. **Persistence**: Index saved to `NODE_REGISTRY_PATH` (default: `./node-registry.json`) as JSON
3. **Auto-sync**: Every mutation tool (add, remove, update, rewire, move) and flows-create/update triggers a refresh
4. **Lookup**: Search by name, type, ID, or flow label with fuzzy matching and result ranking
5. **Categories**: Each node is categorized as `source`, `debug`, `transform`, `network`, `messaging`, `storage`, `template`, `dashboard`, `config`, or `other`

**Example query**:
```json
// 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:

```typescript
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

```typescript
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:

```typescript
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";
```

```mermaid
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

```typescript
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

```bash
# 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:fix
```

### Project 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 point
```

### Test 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

```dockerfile
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:

```bash
docker build -t node-red-mcp .
docker run -e NODE_RED_URL=http://host.docker.internal:1880 -p 3002:3002 node-red-mcp
```

---

## License

[MIT](LICENSE) Ā© Mystery SD

TDQS

B3.3/5.0

Scored across 37 tools

Disambiguation5/5

Each tool belongs to a clearly prefixed category (auth, runtime, debug, flows, graph, nodes) and performs a distinct operation. No two tools have overlapping purposes.

Naming Consistency5/5

All tools follow the consistent pattern 'node-red-{category}-{action}' using snake_case. There is no deviation or mixing of styles, making the naming predictable and easy to understand.

Tool Count4/5

With 37 tools, the count is on the higher side but justified by the complexity of Node-RED. Each tool covers a specific aspect of management, and the organization into categories prevents overwhelming the user.

Completeness5/5

The toolset provides comprehensive coverage for Node-RED administration, including authentication, runtime inspection, flow CRUD and node manipulation, graph analysis, and node module management. No significant gaps are apparent.

Maintenance

ActivityStale
ResponsivenessNo issues