Files
nodered-mcp/README.md
Joe Carter 764d123fdb Initial commit: Node-RED MCP server (Python/FastMCP)
22-tool MCP server for Node-RED flow management with Pydantic models,
incremental flow patching, layout linting, and flexible auth (token,
basic, OAuth2). 137 tests, full ruff compliance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:19:33 +00:00

261 lines
8.4 KiB
Markdown

# nodered-mcp
A Python MCP server for Node-RED flow management, built on [FastMCP](https://github.com/jlowin/fastmcp) and [Pydantic](https://docs.pydantic.dev/).
Gives language models full read/write access to a Node-RED instance through the [Admin HTTP API](https://nodered.org/docs/api/admin/), with structured Pydantic responses, incremental flow patching, layout linting, and flexible authentication (token, basic, OAuth2).
## Compared to node-red-mcp-server
This project was inspired by [karavaev-evgeniy/node-red-mcp-server](https://github.com/karavaev-evgeniy/node-red-mcp-server) (TypeScript/npm). Key differences:
| | node-red-mcp-server (TS) | nodered-mcp (Python) |
|---|---|---|
| Runtime | Node.js / npm | Python 3.11+ / uv |
| Framework | Custom MCP SDK | FastMCP |
| Responses | Raw JSON strings | Typed Pydantic models |
| Auth | Token only | Token, Basic, OAuth2 |
| Deployment types | `full` only | `full`, `nodes`, `flows`, `reload` |
| Flow mutations | Replace entire flow | `patch_flow` with granular ops (add/remove/update/rewire) |
| Layout checks | None | `lint_flow` detects overlaps, spacing, wire crossings |
| Auto-lint | N/A | `update_flow` and `patch_flow` append warnings automatically |
| Tool count | 19 | 22 |
## Installation
Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/):
```bash
git clone <repo-url> && cd nodered-mcp
uv sync
```
## Quick Start
```bash
# Minimal — token auth (default)
export NODE_RED_URL=http://localhost:1880
export NODE_RED_TOKEN=your-api-token
uv run nodered-mcp
```
## Configuration
All configuration is via environment variables with the `NODE_RED_` prefix.
| Variable | Default | Description |
|----------|---------|-------------|
| `NODE_RED_URL` | `http://localhost:1880` | Node-RED instance URL |
| `NODE_RED_TOKEN` | `""` | Bearer token (for `token` auth method) |
| `NODE_RED_API_VERSION` | `v1` | API version header |
| `NODE_RED_AUTH_METHOD` | `token` | Auth method: `token`, `basic`, or `oauth` |
| `NODE_RED_AUTH_USERNAME` | `""` | Username for `basic` or `oauth` auth |
| `NODE_RED_AUTH_PASSWORD` | `""` | Password/app password for `basic` or `oauth` auth |
| `NODE_RED_OAUTH_TOKEN_URL` | `""` | OAuth2 token endpoint URL |
| `NODE_RED_OAUTH_CLIENT_ID` | `""` | OAuth2 client ID |
## Authentication
### Token (default)
Static bearer token sent with every request. This is the simplest method and matches how `node-red-mcp-server` works:
```bash
NODE_RED_URL=http://localhost:1880
NODE_RED_TOKEN=your-api-token
```
### Basic Auth
HTTP Basic authentication, useful when Node-RED sits behind a forward auth proxy.
```bash
NODE_RED_AUTH_METHOD=basic
NODE_RED_AUTH_USERNAME=admin
NODE_RED_AUTH_PASSWORD=your-password
```
### Basic Auth with Authentik
A common setup is Node-RED behind [Authentik](https://goauthentik.io/) as a forward auth proxy. Authentik intercepts requests, validates credentials, and sets session cookies before forwarding to Node-RED.
1. **Create an Authentik application** for your Node-RED instance using the Forward Auth (single application) provider.
2. **Configure your reverse proxy** (Traefik, nginx, Caddy) to use Authentik's forward auth endpoint. For example, with Traefik:
```yaml
# docker-compose.yml (Traefik labels on the Node-RED service)
labels:
- "traefik.http.routers.nodered.middlewares=authentik@docker"
```
3. **Create an Authentik service account** (or use an existing user) and generate an app password under the user's token settings. This avoids MFA prompts that would block API access.
4. **Configure nodered-mcp**:
```bash
NODE_RED_URL=https://nodered.yourdomain.com
NODE_RED_AUTH_METHOD=basic
NODE_RED_AUTH_USERNAME=service-account
NODE_RED_AUTH_PASSWORD=the-app-password-from-authentik
```
The basic auth credentials are sent to Authentik's proxy, which validates them and sets cookies. Subsequent requests use the session cookie, so Node-RED itself doesn't need auth enabled.
### OAuth2 Client Credentials
Fetches a short-lived JWT via OAuth2 client credentials grant. The token is cached and auto-refreshed with a 30-second expiry buffer. On a 401 response, the server retries once with a fresh token.
```bash
NODE_RED_AUTH_METHOD=oauth
NODE_RED_AUTH_USERNAME=service-account
NODE_RED_AUTH_PASSWORD=service-password
NODE_RED_OAUTH_TOKEN_URL=https://auth.example.com/application/o/token/
NODE_RED_OAUTH_CLIENT_ID=your-client-id
```
## MCP Client Configuration
### Claude Desktop
```json
{
"mcpServers": {
"nodered": {
"command": "uv",
"args": ["--directory", "/path/to/nodered-mcp", "run", "nodered-mcp"],
"env": {
"NODE_RED_URL": "http://localhost:1880",
"NODE_RED_TOKEN": "your-token"
}
}
}
}
```
### Claude Code
```json
{
"mcpServers": {
"nodered": {
"command": "uv",
"args": ["--directory", "/path/to/nodered-mcp", "run", "nodered-mcp"],
"env": {
"NODE_RED_URL": "http://localhost:1880",
"NODE_RED_TOKEN": "your-token"
}
}
}
}
```
## Tools
### Flow Tools (12)
| Tool | Description |
|------|-------------|
| `get_flows` | Get all flows with summary statistics |
| `update_flows` | Replace all flows with deployment type control (`full`/`nodes`/`flows`/`reload`) |
| `get_flow` | Get a single flow by ID |
| `update_flow` | Update a single flow (auto-lints after save) |
| `list_tabs` | List all flow tabs (workspaces) |
| `create_flow` | Create a new flow tab |
| `delete_flow` | Delete a flow tab |
| `get_flows_state` | Get runtime state (started/stopped) |
| `set_flows_state` | Start or stop the flow runtime |
| `get_flows_formatted` | Get flows grouped by tabs/nodes/subflows with statistics |
| `visualize_flows` | Markdown-formatted per-tab structural overview |
| `patch_flow` | Incremental operations: `add_nodes`, `remove_nodes`, `update_node`, `rewire`, `set_label`, `set_info`, `set_disabled` (auto-lints after save) |
### Node Tools (6)
| Tool | Description |
|------|-------------|
| `inject` | Trigger an inject node |
| `get_nodes` | List installed node modules |
| `get_node_info` | Detailed info about a node module |
| `toggle_node_module` | Enable or disable a node module |
| `find_nodes_by_type` | Find all nodes of a given type |
| `search_nodes` | Search nodes by name or any property |
### Layout Tools (1)
| Tool | Description |
|------|-------------|
| `lint_flow` | Check a flow for node overlaps, insufficient spacing, and wire-through-node crossings. Optionally scoped to specific node IDs. |
### Settings Tools (2)
| Tool | Description |
|------|-------------|
| `get_settings` | Get Node-RED runtime settings |
| `get_diagnostics` | Get runtime diagnostics (Node.js version, OS, modules) |
### Utility Tools (1)
| Tool | Description |
|------|-------------|
| `api_help` | Node-RED Admin API endpoint reference |
## Architecture
```
src/nodered_mcp/
├── server.py # FastMCP server, config, client init
├── config.py # Pydantic Settings (env vars)
├── client.py # Async httpx wrapper with auth
├── models/
│ ├── base.py # BaseApiModel with from_api()
│ ├── flow.py # Node, FlowTab, Flow, FlowState
│ ├── node.py # NodeModule, NodeSet
│ ├── responses.py # FlowList, FlowSummary, Settings, etc.
│ └── layout.py # LayoutIssue, LayoutReport
└── tools/
├── flows.py # Flow CRUD + patch + auto-lint
├── nodes.py # Node management + search
├── layout.py # Layout lint checks
├── settings.py # Settings + diagnostics
└── utility.py # API help reference
```
The layer diagram is simple:
```
MCP Tools (thin async functions, return Pydantic models)
NodeRedClient (async httpx, returns raw dicts)
Node-RED Admin HTTP API
```
## Development
```bash
make all # format + lint + test
make check # lint + test (no format)
make test # uv run pytest tests/ -v
make lint # uv run ruff check src/ tests/
make format # uv run ruff format src/ tests/
```
Run a single test file:
```bash
uv run pytest tests/tools/test_layout.py -v
```
Run with coverage:
```bash
uv run pytest tests/ -v --cov=src/nodered_mcp --cov-report=term-missing
```
Always use `uv run` — never bare `python` or `pytest`.
See [conventions.md](conventions.md) for detailed code patterns (model layering, tool conventions, client architecture).
## License
MIT