@com55/node-red-ai-access 0.2.0-beta.1 → 0.2.0-beta.3
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/CHANGELOG.md +107 -1
- package/README.md +103 -78
- package/ai-access.html +1 -1
- package/dist/plugin.cjs +1198 -611
- package/dist/plugin.d.cts +24 -10
- package/docs/README.md +2 -2
- package/docs/api/openapi.yaml +5 -5
- package/docs/errors.md +2 -2
- package/docs/mcp-clients.md +16 -15
- package/docs/mcp-tools.md +1 -1
- package/package.json +1 -1
package/dist/plugin.d.cts
CHANGED
|
@@ -23,8 +23,11 @@ interface AccessLinkCreated extends AccessLinkPublic {
|
|
|
23
23
|
interface AccessLinkRecord {
|
|
24
24
|
id: string;
|
|
25
25
|
tokenHash: string;
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Legacy field: raw tokens are no longer kept. Only `tokenHash` is
|
|
28
|
+
* used for validation; any `token` on an inserted record is dropped.
|
|
29
|
+
*/
|
|
30
|
+
token?: string;
|
|
28
31
|
/**
|
|
29
32
|
* Token surface. Optional for in-memory migration of pre-kind records
|
|
30
33
|
* (treated as `get_link` by {@link resolveAccessLinkKind}).
|
|
@@ -82,11 +85,6 @@ declare class AccessLinkStore {
|
|
|
82
85
|
private isActive;
|
|
83
86
|
list(): AccessLinkPublic[];
|
|
84
87
|
create(input?: CreateAccessLinkInput): AccessLinkCreated;
|
|
85
|
-
/**
|
|
86
|
-
* Raw token for an active link (in-memory only). Used to rebuild the GET URL
|
|
87
|
-
* for admin "Copy link" — never logs the value.
|
|
88
|
-
*/
|
|
89
|
-
peekToken(linkId: string): string | null;
|
|
90
88
|
revoke(linkId: string): AccessLinkPublic;
|
|
91
89
|
/**
|
|
92
90
|
* Look up a link by id even if revoked/expired (still in memory).
|
|
@@ -602,7 +600,11 @@ interface NodeRedPluginSettings {
|
|
|
602
600
|
userDir: string;
|
|
603
601
|
httpAdminRoot?: string;
|
|
604
602
|
httpNodeRoot?: string | false;
|
|
605
|
-
httpStatic?: string | string
|
|
603
|
+
httpStatic?: string | (string | {
|
|
604
|
+
path: string;
|
|
605
|
+
root?: string;
|
|
606
|
+
})[];
|
|
607
|
+
httpStaticRoot?: string;
|
|
606
608
|
adminAuth?: unknown;
|
|
607
609
|
aiAccess?: AiAccessSettings;
|
|
608
610
|
}
|
|
@@ -615,8 +617,13 @@ interface NodeRedPluginAPI {
|
|
|
615
617
|
httpNode?: Express;
|
|
616
618
|
nodes?: {
|
|
617
619
|
eachNode(listener: (node: {
|
|
620
|
+
id?: string;
|
|
618
621
|
type?: string;
|
|
619
622
|
url?: string;
|
|
623
|
+
name?: string;
|
|
624
|
+
z?: string;
|
|
625
|
+
d?: boolean;
|
|
626
|
+
disabled?: boolean;
|
|
620
627
|
}) => void): void;
|
|
621
628
|
};
|
|
622
629
|
events?: {
|
|
@@ -752,14 +759,21 @@ declare function packageVersion(): string;
|
|
|
752
759
|
/**
|
|
753
760
|
* Node-RED plugin entry for @com55/node-red-ai-access.
|
|
754
761
|
*
|
|
755
|
-
* Registers /
|
|
762
|
+
* Registers the /ai-access dashboard, access links, proposals, flows, logs APIs,
|
|
756
763
|
* and the /mcp/rpc Streamable HTTP endpoint.
|
|
757
764
|
*/
|
|
758
765
|
|
|
766
|
+
/**
|
|
767
|
+
* True for MCP Streamable HTTP GET requests (they ask for `text/event-stream`
|
|
768
|
+
* and not HTML). Browsers opening `/mcp` are redirected to `/ai-access`.
|
|
769
|
+
*/
|
|
770
|
+
/** Media ranges from an Accept header with q > 0 (q=0 means "not acceptable"). */
|
|
771
|
+
declare function acceptedMediaTypes(accept: string | undefined): Set<string>;
|
|
772
|
+
declare function isMcpStreamRequest(req: Request): boolean;
|
|
759
773
|
declare function registerRoutes(RED: NodeRedPluginAPI, serviceOptions?: CreatePluginServicesOptions): void;
|
|
760
774
|
/**
|
|
761
775
|
* Node-RED plugin factory. Loaded via `ai-access.cjs` → `dist/plugin.cjs`.
|
|
762
776
|
*/
|
|
763
777
|
declare function registerAiAccessPlugin(RED: NodeRedPluginAPI): void;
|
|
764
778
|
|
|
765
|
-
export { ACCESS_LINK_INVALID_MESSAGE, ACCESS_LINK_MISSING_MESSAGE, type AccessLinkAuthInfo, AccessLinkStore, type AiAccessCapabilities, type AiAccessPermission, type AiAccessSettings, type AiAccessStatus, EDITOR_SIDEBAR_ASSET_VERSION, MAX_PROPOSAL_JSON_BYTES, type NodeRedPluginAPI, SettingsStore, areAiFeaturesEnabled, bindSettingsStore, createPluginServices, registerAiAccessPlugin as default, getStorageDir, isAdminAuthEnabled, packageVersion, registerRoutes, renderEditorSidebarJs, renderEditorSidebarLoaderHtml, requireAccessLink, resolveAiAccessSettings };
|
|
779
|
+
export { ACCESS_LINK_INVALID_MESSAGE, ACCESS_LINK_MISSING_MESSAGE, type AccessLinkAuthInfo, AccessLinkStore, type AiAccessCapabilities, type AiAccessPermission, type AiAccessSettings, type AiAccessStatus, EDITOR_SIDEBAR_ASSET_VERSION, MAX_PROPOSAL_JSON_BYTES, type NodeRedPluginAPI, SettingsStore, acceptedMediaTypes, areAiFeaturesEnabled, bindSettingsStore, createPluginServices, registerAiAccessPlugin as default, getStorageDir, isAdminAuthEnabled, isMcpStreamRequest, packageVersion, registerRoutes, renderEditorSidebarJs, renderEditorSidebarLoaderHtml, requireAccessLink, resolveAiAccessSettings };
|
package/docs/README.md
CHANGED
|
@@ -24,5 +24,5 @@ bodies. Use a **GET link** only as a fallback for headerless GET-only clients.
|
|
|
24
24
|
`GET /mcp/api/v1/openapi.yaml` (JSON: `GET /mcp/api/v1/openapi.json`)
|
|
25
25
|
- Discovery guide — `GET /mcp/api/v1` (unauthenticated JSON index)
|
|
26
26
|
|
|
27
|
-
Mint tokens on the
|
|
28
|
-
Each token works on **one** surface; wrong kind → `403 wrong_token_kind`.
|
|
27
|
+
Mint tokens on the dashboard at `/ai-access` → **Access tokens** (admin `*`
|
|
28
|
+
only). Each token works on **one** surface; wrong kind → `403 wrong_token_kind`.
|
package/docs/api/openapi.yaml
CHANGED
|
@@ -1361,8 +1361,8 @@ components:
|
|
|
1361
1361
|
error: 'Access link token has expired. Ask the user for a new access link.'
|
|
1362
1362
|
code: token_expired
|
|
1363
1363
|
expiredAt: '2026-01-01T12:00:00.000Z'
|
|
1364
|
-
accessLinksUrl: 'https://node-red.example/
|
|
1365
|
-
hint: 'Ask the user to create a new access link in Node-RED on the /
|
|
1364
|
+
accessLinksUrl: 'https://node-red.example/ai-access#links'
|
|
1365
|
+
hint: 'Ask the user to create a new access link in Node-RED on the /ai-access dashboard → Access tokens (https://node-red.example/ai-access#links).'
|
|
1366
1366
|
askForNewLink:
|
|
1367
1367
|
value:
|
|
1368
1368
|
error: 'Access link is invalid, revoked, or no longer available after a restart. Ask the user for a new access link.'
|
|
@@ -2252,14 +2252,14 @@ components:
|
|
|
2252
2252
|
reviewUrl:
|
|
2253
2253
|
type: string
|
|
2254
2254
|
description: |
|
|
2255
|
-
Absolute deep link (`https://host/
|
|
2255
|
+
Absolute deep link (`https://host/ai-access#proposal=<id>`) built from the
|
|
2256
2256
|
request Host/protocol. Uses `X-Forwarded-Host` / `X-Forwarded-Proto`
|
|
2257
2257
|
only when Express `trust proxy` is enabled; otherwise falls back to
|
|
2258
2258
|
the relative `reviewPath` when Host is unavailable.
|
|
2259
2259
|
reviewPath:
|
|
2260
2260
|
type: string
|
|
2261
2261
|
description: |
|
|
2262
|
-
Relative deep link `/
|
|
2262
|
+
Relative deep link `/ai-access#proposal=<id>` (under `httpAdminRoot` when
|
|
2263
2263
|
set) that opens the admin page, switches to Proposals, and
|
|
2264
2264
|
scrolls/highlights this item.
|
|
2265
2265
|
createdBy:
|
|
@@ -2501,7 +2501,7 @@ components:
|
|
|
2501
2501
|
preview:
|
|
2502
2502
|
type: string
|
|
2503
2503
|
enum: [html]
|
|
2504
|
-
description: When set, the /
|
|
2504
|
+
description: When set, the /ai-access dashboard may offer a sandboxed HTML preview.
|
|
2505
2505
|
|
|
2506
2506
|
ProposalDiffPresenceNode:
|
|
2507
2507
|
type: object
|
package/docs/errors.md
CHANGED
|
@@ -15,9 +15,9 @@ GET-link `401` responses include a `WWW-Authenticate: Bearer …` header.
|
|
|
15
15
|
| --- | --- | --- | --- | --- |
|
|
16
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
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 `/
|
|
18
|
+
| `token_expired` | 401 | Known token past `expiresAt` (body includes `expiredAt` and `accessLinksUrl`) | Ask the user to create a new link on the `/ai-access` dashboard → Access tokens (`accessLinksUrl`) | GET, REST, MCP |
|
|
19
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 `/
|
|
20
|
+
| `wrong_token_kind` | 403 | Token kind does not match this surface | Mint the matching kind on `/ai-access` → Access tokens; show `tellUser` to the human | GET, REST, MCP |
|
|
21
21
|
| `ai_features_disabled` | 403 | Plugin AI features off | Owner enables AI Access / `adminAuth` | GET, REST, MCP |
|
|
22
22
|
| `auth_required` | 401 | Admin session required | Sign in to the Node-RED editor | admin |
|
|
23
23
|
| `insufficient_permissions` | 403 | Admin lacks required scope | Need `*` for link mint / approve | admin |
|
package/docs/mcp-clients.md
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## OAuth (for clients with OAuth discovery)
|
|
4
4
|
|
|
5
|
-
Enable MCP OAuth
|
|
6
|
-
|
|
7
|
-
`https://YOUR-NODE-RED/mcp
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
Enable MCP OAuth (AI Access editor sidebar, or the dashboard **OAuth** tab at
|
|
6
|
+
`/ai-access`) and set a public HTTPS origin in **Public URL** (dashboard
|
|
7
|
+
**Settings** tab). Give the client the MCP endpoint `https://YOUR-NODE-RED/mcp`
|
|
8
|
+
(`/mcp/rpc` also works). It can discover the authorization server from the
|
|
9
|
+
protected resource metadata, register itself, and request `read` or `propose`
|
|
10
|
+
scope. Sign in to Node-RED and approve the client on the consent page. The
|
|
11
|
+
dashboard **OAuth** tab lists registered clients and lets an admin revoke them
|
|
12
|
+
and their tokens. Localhost HTTP is supported for development. If `httpNodeRoot`
|
|
13
|
+
is disabled, see the OAuth tab for the metadata fallback path.
|
|
13
14
|
|
|
14
15
|
Notes:
|
|
15
16
|
|
|
@@ -26,16 +27,16 @@ Notes:
|
|
|
26
27
|
not count toward the client limit.
|
|
27
28
|
- Refresh tokens are sliding: each refresh renews the idle lifetime. A token
|
|
28
29
|
chain also has a maximum lifetime of 90 days from approval by default; after
|
|
29
|
-
that the client must sign in again. Change it
|
|
30
|
-
Lowering the maximum applies immediately to existing token chains.
|
|
31
|
-
or setting 0 does not extend the refresh token a client already
|
|
32
|
-
limit takes effect from that chain's next successful refresh
|
|
33
|
-
chains).
|
|
30
|
+
that the client must sign in again. Change it on the dashboard OAuth tab (0 =
|
|
31
|
+
unlimited). Lowering the maximum applies immediately to existing token chains.
|
|
32
|
+
Raising it or setting 0 does not extend the refresh token a client already
|
|
33
|
+
holds; the new limit takes effect from that chain's next successful refresh
|
|
34
|
+
(and for new chains).
|
|
34
35
|
|
|
35
36
|
## Access-link bearer tokens
|
|
36
37
|
|
|
37
|
-
Use an **mcp** access-link token from `/
|
|
38
|
-
Endpoint: `POST https://YOUR-NODE-RED/mcp/rpc` with
|
|
38
|
+
Use an **mcp** access-link token from `/ai-access` → Access tokens (kind = MCP
|
|
39
|
+
token). Endpoint: `POST https://YOUR-NODE-RED/mcp/rpc` with
|
|
39
40
|
`Authorization: Bearer <mcp-token>`.
|
|
40
41
|
|
|
41
42
|
If your AI app cannot add MCP servers, create a **REST** or **GET link** token
|
package/docs/mcp-tools.md
CHANGED
|
@@ -126,7 +126,7 @@ Search the public Node-RED catalogue for modules to recommend. Returns module id
|
|
|
126
126
|
|
|
127
127
|
## `submit_proposal`
|
|
128
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 /
|
|
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 /ai-access dashboard (reviewUrl opens/scrolls to it).
|
|
130
130
|
|
|
131
131
|
| Parameter | Required | Type | Description |
|
|
132
132
|
| --- | --- | --- | --- |
|
package/package.json
CHANGED