@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/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
- /** Raw token kept in memory so admins can re-copy the GET URL. */
27
- token: string;
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 /mcp admin page, access links, proposals, flows, logs APIs,
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 admin page at `/mcp` → **Access links** (admin `*` only).
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`.
@@ -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/mcp#links'
1365
- hint: 'Ask the user to create a new access link in Node-RED on the /mcp page → Access links (https://node-red.example/mcp#links).'
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/mcp#proposal=<id>`) built from the
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 `/mcp#proposal=<id>` (under `httpAdminRoot` when
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 /mcp UI may offer a sandboxed HTML preview.
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 `/mcp` page → Access links (`accessLinksUrl`) | GET, 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 `/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 `/mcp` → Access links; show `tellUser` to the human | GET, REST, MCP |
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 |
@@ -2,14 +2,15 @@
2
2
 
3
3
  ## OAuth (for clients with OAuth discovery)
4
4
 
5
- Enable MCP OAuth in the AI Access editor sidebar and set a public HTTPS origin
6
- in **Public URL**. Give the client the MCP endpoint
7
- `https://YOUR-NODE-RED/mcp/rpc`. It can discover the authorization server from
8
- the protected resource metadata, register itself, and request `read` or
9
- `propose` scope. Sign in to Node-RED and approve the client on the consent page.
10
- The sidebar lists registered clients and lets an admin revoke them and their
11
- tokens. Localhost HTTP is supported for development. If `httpNodeRoot` is
12
- disabled, see the sidebar warning for the metadata fallback path.
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 in the sidebar (0 = unlimited).
30
- Lowering the maximum applies immediately to existing token chains. Raising it
31
- or setting 0 does not extend the refresh token a client already holds; the new
32
- limit takes effect from that chain's next successful refresh (and for new
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 `/mcp` → Access links (kind = MCP token).
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 /mcp page (reviewUrl opens/scrolls to it).
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@com55/node-red-ai-access",
3
- "version": "0.2.0-beta.1",
3
+ "version": "0.2.0-beta.3",
4
4
  "description": "Node-RED plugin that gives AI agents temporary, owner-approved access via REST and MCP",
5
5
  "main": "dist/plugin.cjs",
6
6
  "types": "dist/plugin.d.cts",