@com55/node-red-ai-access 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/errors.md ADDED
@@ -0,0 +1,83 @@
1
+ <!-- Generated from src/plugin/error-registry.ts — run `yarn generate:errors`. Do not edit by hand. -->
2
+
3
+ # Error codes
4
+
5
+ JSON error bodies use `{ error, code, hint? }` (some also include
6
+ channel-specific fields). Channels: **GET** = `/mcp/a/<token>`, **REST** =
7
+ `/mcp/api/v1`, **MCP** = `/mcp/rpc` (tool results with `isError: true` carry
8
+ the same JSON plus `statusCode`), **admin** = owner-only `/mcp/api/v1` routes.
9
+
10
+ GET-link `401` responses include a `WWW-Authenticate: Bearer …` header.
11
+
12
+ ## Errors
13
+
14
+ | Code | HTTP | Meaning | Hint / next step | Channels |
15
+ | --- | --- | --- | --- | --- |
16
+ | `access_link_missing` | 401 | No Bearer / path token | Ask the user for a new access link of the right kind | GET, REST, MCP |
17
+ | `bearer_scheme_required` | 401 | Auth used Basic (or non-Bearer) | Send `Authorization: Bearer <token>` | REST, MCP |
18
+ | `token_expired` | 401 | Known token past `expiresAt` (body includes `expiredAt` and `accessLinksUrl`) | Ask the user to create a new link on the `/mcp` page → Access links (`accessLinksUrl`) | GET, REST, MCP |
19
+ | `access_link_invalid` | 401 | Unknown, revoked, or cleared after restart | Ask the user for a new access link | GET, REST, MCP |
20
+ | `wrong_token_kind` | 403 | Token kind does not match this surface | Mint the matching kind on `/mcp` → Access links; show `tellUser` to the human | GET, REST, MCP |
21
+ | `ai_features_disabled` | 403 | Plugin AI features off | Owner enables AI Access / `adminAuth` | GET, REST, MCP |
22
+ | `auth_required` | 401 | Admin session required | Sign in to the Node-RED editor | admin |
23
+ | `insufficient_permissions` | 403 | Admin lacks required scope | Need `*` for link mint / approve | admin |
24
+ | `invalid_link_kind` | 400 | Unknown `kind` on create | Use `get_link`, `rest`, or `mcp` | admin |
25
+ | `invalid_ttl` | 400 | `ttlSeconds` is zero, negative, not a number, or over the cap for the kind | ttlSeconds must be a positive number of seconds within the cap for the token kind: get_link ≤ 86400 (24h); rest and mcp ≤ 2592000 (30 days). | admin |
26
+ | `access_link_limit` | 409 | Too many active links | Revoke an existing link first | admin |
27
+ | `access_link_not_found` | 404 | Unknown link id on revoke | List links and use an existing id | admin |
28
+ | `link_create_not_supported` | 405 | Attempted mint under a GET link | Admin mints via `POST /mcp/api/v1/links` only | GET |
29
+ | `rate_limited` | 429 | Per-IP or per-token limit | Wait `retryAfter` / `Retry-After` | GET, REST, MCP |
30
+ | `payload_too_large` | 413 | Body / proposal over ~1.5MB | Shrink content | GET, REST, MCP |
31
+ | `invalid_json` | 400 | Malformed JSON body | Fix Content-Type / body | REST, MCP |
32
+ | `unsupported_media_type` | 415 | POST body is not `application/json` | Send `Content-Type: application/json` | REST, MCP |
33
+ | `url_too_long` | 414 | GET URL over soft cap (~8KB) | Use chunked drafts or REST/MCP | GET |
34
+ | `not_found` | 404 | Unknown path or resource | Re-read docs / discovery | GET, REST, MCP |
35
+ | `missing_query` | 400 | Required search `q` missing | Pass `?q=` | GET, REST |
36
+ | `invalid_filter` | 400 | Invalid list filter (`status`, `limit`, `offset`, `mine`); body names `parameter` and `allowed` | Use an allowed value: status one of the proposal statuses; limit integer 1–200; offset integer ≥ 0; mine true\|false | GET, REST, MCP |
37
+ | `invalid_arguments` | 400 | MCP tool arguments failed schema validation; body names `parameter` | Fix the named argument to match the tool input schema (see tools/list) | MCP |
38
+ | `invalid_diff_format` | 400 | Unknown `format` on proposal diff | Use `format=summary` (default) or `format=full` | GET, REST, MCP |
39
+ | `confirm_required` | 400 | GET withdraw without `confirm=1` | Repeat the request with `confirm=1` | GET |
40
+ | `missing_expected_rev` | 400 | Proposal submitted without `expectedRev` / `rev` | Read the flow and pass its `rev` | GET, REST, MCP |
41
+ | `version_mismatch` | 409 | Stale `rev` / `expectedRev` | Re-read flows and resubmit | GET, REST, MCP |
42
+ | `proposal_not_found` | 404 | Unknown proposal id | List proposals and use an existing id | GET, REST, MCP |
43
+ | `proposal_not_pending` | 409 | Action needs a pending proposal | Only pending can withdraw / decide | GET, REST, MCP |
44
+ | `proposal_not_owned` | 403 | Withdraw of another token’s proposal | Only the creating token can withdraw | GET, REST, MCP |
45
+ | `validation_failed` | 400 | Several validation errors at once (message `N validation errors`) | Fix every entry in errors[] and resubmit. | GET, REST, MCP |
46
+ | `duplicate_node_id` | 400 | The same node id appears more than once in a full tab or batch | Give every node a unique id | GET, REST, MCP |
47
+ | `missing_node_id` | 400 | Node / op without an id | Include a non-empty string id | GET, REST, MCP |
48
+ | `missing_node_type` | 400 | Node without a `type` | Set `type` to an installed palette type name | GET, REST, MCP |
49
+ | `invalid_node_type` | 400 | `type` is not a string | Use the palette type name exactly | GET, REST, MCP |
50
+ | `invalid_nodes` | 400 | `flow.nodes` / `flow.configs` not an array | Send arrays (use `[]` to clear) | GET, REST, MCP |
51
+ | `invalid_node` | 400 | Node entry is not an object | Send `{ id, type, … }` objects | GET, REST, MCP |
52
+ | `invalid_wires` | 400 | Bad wires shape, missing target, or more ports than outputs | Wire only to existing node ids / ports | GET, REST, MCP |
53
+ | `z_mismatch` | 400 | Node `z` differs from the flow id | Set `z` to the tab id or omit it | GET, REST, MCP |
54
+ | `node_already_exists` | 400 | `add` op for an id already in the flow | Use `set` or a new id | GET, REST, MCP |
55
+ | `protected_property` | 400 | Tried to change id / type / z | Omit id, type, and z from edits | GET, REST, MCP |
56
+ | `node_not_found` | 404 | Unknown node (single error only; several errors → `validation_failed`) | Re-read the flow | GET, REST, MCP |
57
+ | `field_not_found` | 404 | Unknown field on a node | Re-read the node | GET, REST, MCP |
58
+ | `text_not_found` | 400 | `replace_text` find had no match | Re-read field; find is exact | GET, REST, MCP |
59
+ | `ambiguous_match` | 409 | `replace_text` matched more than once | Pass `occurrence` (1-based) or unique find | GET, REST, MCP |
60
+ | `unknown_node_type` | 400 | Proposal uses a type that is not installed | List node-types / palette search; ask user to install | GET, REST, MCP |
61
+ | `node_type_not_found` | 404 | Unknown type on node-types detail | List types or search palette | GET, REST, MCP |
62
+ | `chunk_conflict` | 409 | Same draft seq re-sent with different data | Re-send identical data or start a new draft | GET |
63
+ | `missing_chunks` | 400 | Draft submit with gaps in seq | Upload every seq from 0 through max | GET |
64
+ | `draft_hash_mismatch` | 400 | Draft sha256 does not match assembled bytes | Send the hex sha256 of the assembled UTF-8 | GET |
65
+ | `upstream_error` | 502 | Node-RED runtime/flows error | Retry; check Node-RED logs | GET, REST, MCP |
66
+ | `internal_error` | 500 | Unexpected server error | Retry; check Node-RED logs | GET, REST, MCP |
67
+
68
+ ## Warnings
69
+
70
+ Returned in `warnings[]` on a successful propose/submit; they never block.
71
+
72
+ | Code | HTTP | Meaning | Hint / next step | Channels |
73
+ | --- | --- | --- | --- | --- |
74
+ | `missing_config_node` | 201 | Warning: node has no (or an unresolved) config node yet | Configure before deploy if needed; see `candidates` | GET, REST, MCP |
75
+ | `unwired_node` | 201 | Warning: node has outputs but no wires | Wire it, or ignore while scaffolding | GET, REST, MCP |
76
+ | `incomplete_wires` | 201 | Warning: fewer wire ports than outputs | Add ports if outputs should connect | GET, REST, MCP |
77
+
78
+ ## Auto-closed proposal statuses
79
+
80
+ | Code | HTTP | Meaning | Hint / next step | Channels |
81
+ | --- | --- | --- | --- | --- |
82
+ | `expired` | 200 | Status: pending proposal auto-closed because the live flow rev moved past `expectedRev` | Re-read the flow and submit a new proposal | GET, REST, MCP |
83
+ | `superseded` | 200 | Status: pending proposal auto-closed because another proposal on the same flow was approved | Re-read the flow and submit a new proposal if still needed | GET, REST, MCP |
@@ -0,0 +1,78 @@
1
+ # MCP client setup
2
+
3
+ Use an **mcp** access-link token from `/mcp` → Access links (kind = MCP token).
4
+ Endpoint: `POST https://YOUR-NODE-RED/mcp/rpc` with
5
+ `Authorization: Bearer <mcp-token>`.
6
+
7
+ If your AI app cannot add MCP servers, create a **REST** or **GET link** token
8
+ instead.
9
+
10
+ Replace placeholders — never commit real hosts or tokens to shared repos.
11
+
12
+ ## Claude Desktop
13
+
14
+ `claude_desktop_config.json` → `mcpServers`:
15
+
16
+ ```json
17
+ {
18
+ "mcpServers": {
19
+ "node-red-ai-access": {
20
+ "url": "https://YOUR-NODE-RED/mcp/rpc",
21
+ "headers": {
22
+ "Authorization": "Bearer aia_…"
23
+ }
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ ## Cursor
30
+
31
+ `.cursor/mcp.json` (or project `mcp.json`):
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "node-red-ai-access": {
37
+ "url": "https://YOUR-NODE-RED/mcp/rpc",
38
+ "headers": {
39
+ "Authorization": "Bearer aia_…"
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## VS Code
47
+
48
+ MCP servers config (HTTP type):
49
+
50
+ ```json
51
+ {
52
+ "servers": {
53
+ "node-red-ai-access": {
54
+ "type": "http",
55
+ "url": "https://YOUR-NODE-RED/mcp/rpc",
56
+ "headers": {
57
+ "Authorization": "Bearer aia_…"
58
+ }
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ ## Claude Code CLI
65
+
66
+ ```bash
67
+ claude mcp add --transport http node-red-ai-access https://YOUR-NODE-RED/mcp/rpc \
68
+ --header "Authorization: Bearer aia_…"
69
+ ```
70
+
71
+ ## Generic Streamable HTTP
72
+
73
+ - **URL:** `https://YOUR-NODE-RED/mcp/rpc`
74
+ - **Header:** `Authorization: Bearer aia_…`
75
+ - **Method:** `POST` with `Accept: application/json, text/event-stream`
76
+ - **Body:** JSON-RPC 2.0 (`tools/list`, `tools/call`, …)
77
+
78
+ See [MCP tools reference](./mcp-tools.md) for the tool list.
@@ -0,0 +1,148 @@
1
+ # MCP tools reference
2
+
3
+ Generated from the live `tools/list` schemas on `POST /mcp/rpc`.
4
+ Do not edit by hand — run `yarn generate:mcp-tools` after changing MCP tools.
5
+
6
+ Tools: **15**
7
+
8
+ ## `get_flow`
9
+
10
+ Get one tab/subflow by id with nested nodes/configs and the current rev. Pass summary=true for a lightweight node list (id/type/name/wires/bytes, no field contents). Prefer summary → search_flows → get_node_field(lines) → replace_text → submit_proposal. Returns { rev, flow } or summary shape. Use rev as expectedRev when submitting.
11
+
12
+ | Parameter | Required | Type | Description |
13
+ | --- | --- | --- | --- |
14
+ | `id` | yes | string | Flow / tab id |
15
+ | `summary` | no | boolean | When true, return node list with sizes only (no field contents) |
16
+
17
+ ## `get_node`
18
+
19
+ Get one full node by id (includes owning flowId when known). Optional flow scopes the lookup.
20
+
21
+ | Parameter | Required | Type | Description |
22
+ | --- | --- | --- | --- |
23
+ | `flow` | no | string | Optional tab id to scope the lookup |
24
+ | `id` | yes | string | Node id |
25
+
26
+ ## `get_node_field`
27
+
28
+ Get one string field from a node. Optional lines="A-B" (1-based inclusive) returns a slice; response always includes totalLines and bytes.
29
+
30
+ | Parameter | Required | Type | Description |
31
+ | --- | --- | --- | --- |
32
+ | `field` | yes | string | Field name (e.g. template, func) |
33
+ | `flow` | no | string | Optional tab id to scope the lookup |
34
+ | `id` | yes | string | Node id |
35
+ | `lines` | no | string | Optional 1-based line range A or A-B |
36
+
37
+ ## `get_node_type`
38
+
39
+ Get detail for one installed node type: defaults (JSON), inputs/outputs (always set), requiredConfigNodeTypes, help sections (summary/inputs/outputs), category/module/color. Types are case-sensitive. Pass includeHelp=false to omit help. 404 node_type_not_found when unknown (didYouMean on case mismatch; else nearest names).
40
+
41
+ | Parameter | Required | Type | Description |
42
+ | --- | --- | --- | --- |
43
+ | `includeHelp` | no | boolean | When false, omit help sections (default true) |
44
+ | `type` | yes | string | Node type id (e.g. inject, template) — case-sensitive |
45
+
46
+ ## `get_proposal`
47
+
48
+ Get any proposal by id (all tokens can read). Response includes mine:true|false and, for pending proposals, conflictingProposals when other pending work touches the same flow. Lean by default (no payload/baseFlow); pass include_payload=true for full bodies. Cite displayName + reviewUrl when talking to the user.
49
+
50
+ | Parameter | Required | Type | Description |
51
+ | --- | --- | --- | --- |
52
+ | `id` | yes | string | Proposal id (prop-…) |
53
+ | `include_payload` | no | boolean | When true, include payload and baseFlow |
54
+
55
+ ## `get_proposal_diff`
56
+
57
+ Diff any proposal against its stored baseFlow snapshot (what changed). Optional format=summary|full (default summary). Pending + stale also includes vsCurrent.
58
+
59
+ | Parameter | Required | Type | Description |
60
+ | --- | --- | --- | --- |
61
+ | `format` | no | "summary" \| "full" | Diff payload format (default summary) |
62
+ | `id` | yes | string | Proposal id (prop-…) |
63
+
64
+ ## `get_status`
65
+
66
+ Return plugin status (version, AI feature flags, storage path, deploy defaults). For access-link callers, permission is null and caller.kind is "access-link" — that is expected, not an error. Use before other tools to confirm the plugin is enabled.
67
+
68
+ _No input parameters._
69
+
70
+ ## `list_flows`
71
+
72
+ List all flows as a flat Admin API v2 array plus the current rev string. Returns { rev, flows }. Call this (or get_flow) before submit_proposal so expectedRev matches.
73
+
74
+ _No input parameters._
75
+
76
+ ## `list_my_proposals`
77
+
78
+ Alias of list_proposals. Prefer list_proposals. Lists proposals visible to this access link with optional mine/status/flowId filters and limit/offset pagination.
79
+
80
+ | Parameter | Required | Type | Description |
81
+ | --- | --- | --- | --- |
82
+ | `flowId` | no | string | Filter by flow tab id (payload.flowId) |
83
+ | `include_payload` | no | boolean | When true, include payload and baseFlow on each proposal |
84
+ | `limit` | no | integer | Page size (1–200). Omit to return all matches. |
85
+ | `mine` | no | boolean | true: only proposals created by this access link; false: only other links’ proposals |
86
+ | `offset` | no | integer | Skip this many filtered results (default 0) |
87
+ | `status` | no | string \| string[] | Filter by proposal status: one value, an array, or a comma-separated string (pending, approved, rejected, failed, withdrawn, expired, superseded). An empty array or empty string means no status filter. |
88
+
89
+ ## `list_node_types`
90
+
91
+ List installed Node-RED palette node types (type, module, category, description, …). Optional q filters type/module/category/description (case-insensitive). Results are capped. Call this before proposing nodes with unfamiliar types.
92
+
93
+ | Parameter | Required | Type | Description |
94
+ | --- | --- | --- | --- |
95
+ | `q` | no | string | Optional case-insensitive filter |
96
+
97
+ ## `list_proposals`
98
+
99
+ List proposals visible to this access link (all proposals by default). Each item includes mine:true|false for the caller. Filter with mine, status, and/or flowId; paginate with limit/offset (response includes total, offset, hasMore, nextOffset). Invalid filters return invalid_filter. Returns lean { proposals, total, … } with title, displayName, reviewUrl, changeSummary (no payload/baseFlow unless include_payload=true). Prefer displayName + reviewUrl when referring to a proposal with the user (not the raw id).
100
+
101
+ | Parameter | Required | Type | Description |
102
+ | --- | --- | --- | --- |
103
+ | `flowId` | no | string | Filter by flow tab id (payload.flowId) |
104
+ | `include_payload` | no | boolean | When true, include payload and baseFlow on each proposal |
105
+ | `limit` | no | integer | Page size (1–200). Omit to return all matches. |
106
+ | `mine` | no | boolean | true: only proposals created by this access link; false: only other links’ proposals |
107
+ | `offset` | no | integer | Skip this many filtered results (default 0) |
108
+ | `status` | no | string \| string[] | Filter by proposal status: one value, an array, or a comma-separated string (pending, approved, rejected, failed, withdrawn, expired, superseded). An empty array or empty string means no status filter. |
109
+
110
+ ## `search_flows`
111
+
112
+ Text search across string fields. Returns hits with node id, field, line numbers, and a short snippet. Results are capped; truncated=true when the cap is hit. Optional flow scopes to one tab.
113
+
114
+ | Parameter | Required | Type | Description |
115
+ | --- | --- | --- | --- |
116
+ | `flow` | no | string | Optional tab id |
117
+ | `q` | yes | string | Substring to find (case-sensitive; non-empty required) |
118
+
119
+ ## `search_palette`
120
+
121
+ Search the public Node-RED catalogue for modules to recommend. Returns module id, description, version, updated, link, and installed boolean (cross-checked against installed modules). Core node-red types are not in the catalogue — use list_node_types. RECOMMEND ONLY — never installs.
122
+
123
+ | Parameter | Required | Type | Description |
124
+ | --- | --- | --- | --- |
125
+ | `q` | yes | string | Search string (id / description / keywords; non-empty required) |
126
+
127
+ ## `submit_proposal`
128
+
129
+ Submit a replace_flow proposal for owner approval (does not deploy). Preferred for small string edits: payload: { flowId, nodes: [{ op:"replace_text", node, field, find, replace, occurrence? }] } — find must be unique unless occurrence (1-based) is set; multiple replace_text ops apply in order; yields a short readable diff. Or send a full tab: payload.flow from get_flow (id, type, label?, nodes[], configs?). Inputs (all top-level, next to each other): type="replace_flow", expectedRev (required string from list_flows/get_flow rev; expected_rev alias accepted), optional title (≤80 chars), and payload: { flowId, flow? } and/or payload.nodes / top-level nodes. Do not nest expectedRev inside payload. Payload size limit is 1500000 bytes (413 if exceeded). expectedRev is checked against the live rev at submit time (409 if stale). replace_text errors: 400 text_not_found; 409 ambiguous_match with matchCount. Unknown palette types return 400 unknown_node_type with nearest matches — call list_node_types / search_palette and ask the user to install via Manage palette (never auto-install). Returns a lean proposal (id, status, title, storedTitle, flowLabel, displayName, reviewUrl, changeSummary, …) by default; title may gain " in <flow>"; storedTitle is the title as saved (your title without that suffix, or the auto-generated title, which may already name the flow). Pass include_payload=true to include payload and baseFlow. When telling the user about the proposal, cite displayName and reviewUrl — not the raw id. Humans approve on the /mcp page (reviewUrl opens/scrolls to it).
130
+
131
+ | Parameter | Required | Type | Description |
132
+ | --- | --- | --- | --- |
133
+ | `expectedRev` | no | string | Top-level current flow revision from get_flow/list_flows (required at submit; sit next to type and payload, not inside payload) |
134
+ | `expected_rev` | no | string | Alias for expectedRev (snake_case); prefer expectedRev |
135
+ | `include_payload` | no | boolean | When true, include payload and baseFlow in the response (default lean) |
136
+ | `nodes` | no | object[] | Optional node ops (set/add/remove/replace_text). Same as payload.nodes when flow is omitted. |
137
+ | `payload` | yes | object | Replacement flow payload and/or node ops |
138
+ | `title` | no | string | Optional short human title (≤80 chars). Auto-generated from changes when omitted. |
139
+ | `type` | yes | "replace_flow" | Must be 'replace_flow' |
140
+
141
+ ## `withdraw_proposal`
142
+
143
+ Withdraw one of YOUR pending proposals (status becomes withdrawn, not rejected). Optional reason. Only the creating access link can withdraw; other tokens get 403. Cite displayName + reviewUrl when telling the user.
144
+
145
+ | Parameter | Required | Type | Description |
146
+ | --- | --- | --- | --- |
147
+ | `id` | yes | string | Proposal id (prop-…) owned by this access link |
148
+ | `reason` | no | string | Optional withdraw reason |
package/package.json CHANGED
@@ -1,6 +1,137 @@
1
1
  {
2
2
  "name": "@com55/node-red-ai-access",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Node-RED plugin that gives AI agents temporary, owner-approved access via REST and MCP",
5
+ "main": "dist/plugin.cjs",
6
+ "types": "dist/plugin.d.cts",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "require": {
11
+ "types": "./dist/plugin.d.cts",
12
+ "default": "./dist/plugin.cjs"
13
+ },
14
+ "default": "./dist/plugin.cjs"
15
+ },
16
+ "./plugin": {
17
+ "require": {
18
+ "types": "./dist/plugin.d.cts",
19
+ "default": "./dist/plugin.cjs"
20
+ },
21
+ "default": "./dist/plugin.cjs"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "!dist/**/*.map",
27
+ "docs",
28
+ "ai-access.cjs",
29
+ "ai-access.html",
30
+ "README.md",
31
+ "LICENSE",
32
+ "CHANGELOG.md"
33
+ ],
34
+ "node-red": {
35
+ "version": ">=4.0.0",
36
+ "plugins": {
37
+ "ai-access": "ai-access.cjs"
38
+ }
39
+ },
40
+ "peerDependencies": {
41
+ "node-red": ">=4.0.0"
42
+ },
43
+ "peerDependenciesMeta": {
44
+ "node-red": {
45
+ "optional": true
46
+ }
47
+ },
48
+ "scripts": {
49
+ "build": "tsup",
50
+ "build:prod": "NODE_ENV=production tsup",
51
+ "dev:build": "NODE_ENV=development tsup --watch",
52
+ "clean": "rimraf dist",
53
+ "prebuild": "yarn clean",
54
+ "type-check": "tsc --noEmit",
55
+ "lint": "eslint src/**/*.ts",
56
+ "lint:fix": "eslint src/**/*.ts --fix",
57
+ "format": "prettier --write .",
58
+ "format:check": "prettier --check .",
59
+ "quality": "yarn type-check && yarn lint && yarn format:check",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "test:ui": "vitest --ui --open",
63
+ "test:coverage": "vitest run --coverage",
64
+ "test:coverage:ui": "vitest --ui --coverage --open",
65
+ "generate:mcp-tools": "UPDATE_MCP_TOOLS=1 vitest run test/plugin/mcp-tools-doc.test.ts",
66
+ "check:mcp-tools": "vitest run test/plugin/mcp-tools-doc.test.ts",
67
+ "generate:errors": "UPDATE_ERRORS=1 vitest run test/plugin/error-registry.test.ts",
68
+ "check:errors": "vitest run test/plugin/error-registry.test.ts",
69
+ "prepare": "husky",
70
+ "prepublishOnly": "yarn quality && yarn test && yarn build:prod"
71
+ },
72
+ "keywords": [
73
+ "node-red",
74
+ "node-red-plugin",
75
+ "mcp",
76
+ "model-context-protocol",
77
+ "ai",
78
+ "ai-agent",
79
+ "llm",
80
+ "automation"
81
+ ],
82
+ "author": "com55",
83
+ "license": "MIT",
84
+ "dependencies": {
85
+ "@modelcontextprotocol/sdk": "^1.31.0",
86
+ "express": "^5.3.0",
87
+ "express-rate-limit": "^8.5.2",
88
+ "yaml": "^2.8.0",
89
+ "zod": "^4.4.3"
90
+ },
91
+ "devDependencies": {
92
+ "@eslint/js": "^10.0.1",
93
+ "@types/express": "^5.0.6",
94
+ "@types/node": "^26.1.2",
95
+ "@types/supertest": "^7.2.0",
96
+ "@vitest/coverage-v8": "^5.0.3",
97
+ "@vitest/ui": "^5.0.3",
98
+ "eslint": "^10",
99
+ "eslint-plugin-import": "^2.32.0",
100
+ "husky": "^9.1.7",
101
+ "lint-staged": "^17.6.0",
102
+ "node-red": "4.1.12",
103
+ "prettier": "^3.9.10",
104
+ "rimraf": "^6.1.3",
105
+ "supertest": "^7.3.1",
106
+ "tsup": "^8.5.1",
107
+ "typescript": "^5.9.3",
108
+ "typescript-eslint": "^8.71.1",
109
+ "vite": "^6.4.0",
110
+ "vitest": "^5.0.3"
111
+ },
112
+ "engines": {
113
+ "node": ">=20.0.0",
114
+ "npm": ">=10.0.0"
115
+ },
116
+ "repository": {
117
+ "type": "git",
118
+ "url": "git+https://github.com/com55/node-red-ai-access.git"
119
+ },
120
+ "bugs": {
121
+ "url": "https://github.com/com55/node-red-ai-access/issues"
122
+ },
123
+ "homepage": "https://github.com/com55/node-red-ai-access#readme",
124
+ "packageManager": "yarn@4.9.4+sha512.7b1cb0b62abba6a537b3a2ce00811a843bea02bcf53138581a6ae5b1bf563f734872bd47de49ce32a9ca9dcaff995aa789577ffb16811da7c603dcf69e73750b",
125
+ "lint-staged": {
126
+ "*.{js,ts}": [
127
+ "eslint --fix --no-warn-ignored",
128
+ "prettier --write"
129
+ ],
130
+ "*.{json,md,yml,yaml}": [
131
+ "prettier --write"
132
+ ]
133
+ },
134
+ "publishConfig": {
135
+ "access": "public"
136
+ }
137
+ }