@com55/node-red-ai-access 0.1.0
Node-RED plugin that gives AI agents temporary, owner-approved access via REST and MCP
Node-RED AI Access
Temporary, owner-approved Node-RED access for AI clients. Ships as a Node-RED 4.x+ plugin with:
- an admin page at
/mcp - a versioned REST API under
/mcp/api/v1 - an MCP JSON-RPC endpoint at
/mcp/rpc(Streamable HTTP) over the same services
Git mental model: a proposal is like a pull request. Human approve = merge;
reject = close; withdraw = the author closes their own PR. If rev /
expectedRev does not match, treat it as a conflict: re-read the flow, rebase
the edit, and resubmit. Every access link can see all proposals
(mine: true|false).
Derived from ziv-daniel/node-red-mcp.
Install (Node-RED plugin)
Requirements: Node-RED 4.x or newer, Node.js 20+, and adminAuth
enabled (recommended). Without adminAuth, AI features stay off unless you
opt in (see Configuration).
Install into your Node-RED user directory (or via the palette):
cd ~/.node-red npm install @com55/node-red-ai-accessRestart Node-RED.
Log in to the editor, then open
/mcpon the admin root (for examplehttp://localhost:1880/mcp, or{httpAdminRoot}/mcpwhenhttpAdminRootis set).
The /mcp HTML shell is served without adminAuth so the page can read the
editor token from browser localStorage (Node-RED does not use cookies) and
call APIs with Authorization: Bearer. If no valid token is present, the page
redirects to the editor login. All data and actions stay behind permission
checks.
On /mcp you can create access links, review proposals (with a readable
node/property diff against the live flow), approve or reject changes, and read a
human-readable audit log. Users with read (including guest, if configured) get
a read-only view; only users with * can create/revoke links and approve/reject
proposals.
Configuration
In settings.js:
module.exports = {
// adminAuth: { ... }, // required for AI features in normal setups
aiAccess: {
// Master switch (default: true). Also editable in the editor sidebar tab.
enabled: true,
// Local-only opt-in when adminAuth is disabled (default: false)
allowWithoutAdminAuth: false,
// Access-link default TTL in seconds (default: 14400 = 4h)
defaultLinkTtlSeconds: 14400,
// Max concurrent active access links (default: 10)
maxActiveLinks: 10,
// Deploy type used on approve (default: "nodes"; also "flows" | "full")
defaultDeploymentType: 'nodes',
// In-memory rate limits for /mcp/api and /mcp/a (per IP + per access token)
rateLimitWindowMs: 60000,
rateLimitMaxPerIp: 600,
rateLimitMaxPerToken: 300,
},
};
Settings precedence
Effective values are resolved without restart in this order (highest wins):
- Disk —
<userDir>/.ai-access/settings.json(editor sidebar AI Access tab, orPUT /mcp/api/v1/settings) settings.js—aiAccessblock (install / bootstrap defaults)- Built-in defaults — as documented above
Only keys present on disk override; omitted keys fall through to settings.js,
then defaults. The editor right-hand sidebar tab (AI Access, next to
Information / Help / Debug / Configuration nodes / Context Data) lets * admins
change enable/disable, default deploy type, and default access-link TTL; read
users see the same values read-only. A button opens the /mcp page. When
adminAuth is off, the tab exposes the allowWithoutAdminAuth opt-in with a
clear local-only warning.
Storage lives under <userDir>/.ai-access (settings.json,
proposals/*.json, and append-only logs/audit.jsonl). Directories use mode
0700, files 0600. Proposal JSON is capped at 1_500_000 bytes; oversized
proposal JSON or audit lines return HTTP 413 with code: payload_too_large
(never silently truncated). Malformed JSON returns 400 JSON (REST
invalid_json, MCP JSON-RPC -32700) — never HTML stack traces (middleware is
attached to the editor-api app that owns body-parser, not only the nested plugin
httpAdmin). Unauthenticated /mcp/api/v1 admin calls return JSON
{ code: "auth_required" } rather than plain-text Unauthorized.
Docs
- Channel index — GET link / REST / MCP overview
- MCP client setup — Claude Desktop, Cursor, VS Code, CLI
- MCP tools reference — generated from live
tools/list - Error codes — code, status, hint, channels
- OpenAPI 3.1 — also
GET /mcp/api/v1/openapi.yaml(JSON:GET /mcp/api/v1/openapi.json)
Regenerate the MCP tools doc after tool changes: yarn generate:mcp-tools.
REST API
OpenAPI: GET /mcp/api/v1/openapi.yaml or
GET /mcp/api/v1/openapi.json (also in
docs/api/openapi.yaml).
| Path | Purpose |
|---|---|
GET /mcp |
Admin UI (status, links, proposals, audit log) |
GET /mcp/a/<token> |
Self-contained GET link (markdown docs) |
GET /mcp/a/<token>/… |
Token-scoped GET status/flows/propose/drafts |
POST /mcp/rpc |
MCP Streamable HTTP (access-link Bearer; tools) |
GET /mcp/api/v1 |
Discovery index (no auth; endpoints + auth hint) |
GET /mcp/api/v1/openapi.yaml |
OpenAPI 3.1 specification (YAML) |
GET /mcp/api/v1/openapi.json |
OpenAPI 3.1 specification (JSON) |
GET /mcp/api/v1/status |
Plugin status JSON (admin or access link) |
GET /mcp/api/v1/settings |
Effective AI Access settings (admin read) |
PUT /mcp/api/v1/settings |
Update settings (*; persists to disk, hot) |
GET /mcp/api/v1/links |
List active access links (* only) |
POST /mcp/api/v1/links |
Create access link (* only; token once) |
DELETE /mcp/api/v1/links/:id |
Revoke access link (* only) |
GET /mcp/api/v1/flows |
List flows + rev (admin read or access link) |
GET /mcp/api/v1/flows/:id |
Get one flow + rev (admin read or access link) |
POST /mcp/api/v1/proposals |
Create proposal (access-link Bearer only) |
GET /mcp/api/v1/proposals |
List proposals (admin: all; link: own) |
GET /mcp/api/v1/proposals/:id |
Get proposal (admin: any; link: own) |
GET /mcp/api/v1/proposals/:id/diff |
Diff proposal vs current flow (admin read/own) |
POST /mcp/api/v1/proposals/:id/approve |
Approve + deploy (*; optional deploymentType) |
POST /mcp/api/v1/proposals/:id/reject |
Reject with reason (*; no flow mutate) |
GET /mcp/api/v1/logs |
Append-only audit log (admin read) |
MCP JSON-RPC (POST /mcp/rpc)
AI clients can use the official MCP Streamable HTTP transport against
{httpAdminRoot}/mcp/rpc with the same access-link Bearer token as the REST
API. Tools mirror the AI-facing REST surface only:
get_status, list_flows, get_flow, submit_proposal (including
replace_flow with expectedRev; expected_rev accepted as alias; optional
title ≤80 chars), list_proposals (alias list_my_proposals),
get_proposal. Tool descriptions document purpose, inputs, returns, and when to
use them. Proposal create/list/get responses include title, displayName
(title · server-local datetime with explicit offset, e.g.
16 Oct 01:35 +00:00), reviewUrl (absolute URL using the request
Host/protocol — X-Forwarded-* only when Express trust proxy is set), and
reviewPath (relative /mcp#proposal=<id>). createdAt remains ISO-8601. The
/mcp page renders times in the browser's local timezone. AI clients should
refer to proposals by displayName + reviewUrl, not the raw id. For
access-link callers, get_status reports permission: null and
caller.kind: "access-link" (expected — AI tokens are not admin sessions).
Approve, reject, and access-link management stay human-only on the /mcp admin
page (and the corresponding REST routes). Rate limits, payload size limits, and
audit logging match REST. Missing tokens return 401 access_link_missing;
known-expired tokens return 401 token_expired; other invalid/revoked
tokens return 401 access_link_invalid (all with WWW-Authenticate)
telling the AI to ask the user for a new access link. Every error JSON includes
a code. token_expired also includes expiredAt and accessLinksUrl (the
/mcp page's Access links section). Access-link /status includes expiresAt,
minutesRemaining (rounded up), and secondsRemaining. See docs/errors.md
(generated from src/plugin/error-registry.ts).
Access links are in memory (default TTL 4h, max 10). Caps: get_link ≤
24h, rest/mcp ≤ 30d. Creating a link returns the raw token plus a
self-contained url for get_link (/mcp/a/<token>). While the link remains
active, GET /links includes url again so admins can re-copy from /mcp (the
raw token field stays create-only). After a restart every old token is
invalid.
Call list_flows or get_flow first and pass the returned rev as
expectedRev. Submit checks the live rev (409 if stale). Approve may send
deploymentType: nodes (default), flows, or full. A stale expectedRev
at approve marks the proposal rejected with a resubmit message; other deploy
errors mark it failed. Reject never mutates flows.
Flow runtime adapter
- RedAdapter — in-process flows API (
RED.runtime.flows, or the initialized@node-red/runtimesingleton when the plugin RED object has no.runtime) - HttpAdapter — localhost Admin API v2 fallback only when credentials are
available (or adminAuth is off). Never selected silently without credentials
when adminAuth is on; upstream 401/403 becomes
plugin_misconfigured updateFlow— requiresexpectedRev; default deployment typenodes
AI quickstart
Self-contained GET link (chat AIs that can only HTTP GET)
- An owner with
*opens/mcp→ Access links → create a GET link (default TTL 4 hours, max 24 hours). Copy the full URLhttp(s)://<host><httpAdminRoot>/mcp/a/<token>(or Copy link for AI). - The AI opens that URL for markdown docs, then uses GET-only routes under the
same path (
/status,/flows,/flows/<id>,/proposals,/proposewith preferredreplace_textfor small string edits, chunked/draft/…for large HTML/fields; URL soft-cap ~8KB; prefer ~4–5 KB raw chunks — base64url adds ~33%). Patch /mode=jsondrafts supportnodes:[{op:'add'|'remove'|'set',...}](or a fullreplace_flowdocument). Field drafts validate node/field atdraft/new. Unknown/mcp/a/*paths return JSONnot_foundwithout echoing the token. - Changes become pending proposals; a human approves on
/mcp. JSON responses useCache-Control: no-store.
Bearer REST / MCP
Optional:
GET /mcp/api/v1(no auth) lists endpoints, auth scheme, OpenAPI, and MCP URLs. Spec:GET /mcp/api/v1/openapi.yaml.Send
Authorization: Bearer <token>to REST and/or MCP:- REST:
GET /mcp/api/v1/status,GET /mcp/api/v1/flows,GET /mcp/api/v1/flows/{id}?summary=1,GET /mcp/api/v1/nodes/{id},GET /mcp/api/v1/nodes/{id}/field/{name},GET /mcp/api/v1/search,POST /mcp/api/v1/proposals,GET /mcp/api/v1/proposals - MCP:
POST /mcp/rpctoolslist_flows,get_flow(optionalsummary),get_node,get_node_field,search_flows,submit_proposal,list_proposals/ aliaslist_my_proposals(same access-link auth and limits)
- REST:
The owner reviews the proposal on
/mcpand approves or rejects it.If the token is invalid, expired, revoked, or cleared by a restart, the API returns 401 telling the AI to ask the user for a new access link.
Example create body:
{
"type": "replace_flow",
"expectedRev": "abc123",
"title": "Rename inject to Hello",
"payload": {
"flowId": "tab-1",
"flow": {
"id": "tab-1",
"type": "tab",
"label": "Main",
"nodes": []
}
}
}
Development
yarn install
yarn build
yarn test
yarn quality
License
MIT — see LICENSE.