@nextblock-cms/cortex 0.14.5 → 0.14.6

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.
@@ -3646,7 +3646,7 @@ export declare function createCortexGlobalAgentTools(context?: ToolExecutionCont
3646
3646
  description: string;
3647
3647
  primaryKey: readonly string[];
3648
3648
  readOnly: boolean;
3649
- table: "site_settings" | "languages" | "pages" | "posts" | "products" | "profiles" | "coupons" | "orders" | "freemius_plans" | "media" | "navigation_items" | "product_variants" | "product_attributes" | "shipping_zones" | "product_attribute_terms" | "blocks" | "cortex_ai_db_mutation_audit" | "coupon_freemius_mappings" | "coupon_products" | "coupon_redemptions" | "currencies" | "freemius_pricing" | "inventory_items" | "logos" | "order_items" | "package_activations" | "page_revisions" | "post_revisions" | "product_media" | "shipping_zone_locations" | "shipping_zone_methods" | "tax_rates" | "translations" | "user_addresses" | "variant_attribute_mapping" | "product_revisions";
3649
+ table: "site_settings" | "languages" | "pages" | "posts" | "products" | "profiles" | "coupons" | "orders" | "freemius_plans" | "media" | "navigation_items" | "product_variants" | "product_attributes" | "shipping_zones" | "product_attribute_terms" | "blocks" | "cortex_ai_db_mutation_audit" | "coupon_freemius_mappings" | "coupon_products" | "coupon_redemptions" | "currencies" | "freemius_pricing" | "inventory_items" | "logos" | "order_items" | "package_activations" | "page_revisions" | "post_revisions" | "product_media" | "product_revisions" | "shipping_zone_locations" | "shipping_zone_methods" | "tax_rates" | "translations" | "user_addresses" | "variant_attribute_mapping";
3650
3650
  }[];
3651
3651
  }>;
3652
3652
  execute_database_action_plan: import('ai').Tool<{
@@ -3710,7 +3710,7 @@ export declare function createCortexGlobalAgentTools(context?: ToolExecutionCont
3710
3710
  sampleTargetIds: string[];
3711
3711
  success: boolean;
3712
3712
  summary: string;
3713
- table: "site_settings" | "languages" | "pages" | "posts" | "products" | "profiles" | "coupons" | "orders" | "freemius_plans" | "media" | "navigation_items" | "product_variants" | "product_attributes" | "shipping_zones" | "product_attribute_terms" | "blocks" | "cortex_ai_db_mutation_audit" | "coupon_freemius_mappings" | "coupon_products" | "coupon_redemptions" | "currencies" | "freemius_pricing" | "inventory_items" | "logos" | "order_items" | "package_activations" | "page_revisions" | "post_revisions" | "product_media" | "shipping_zone_locations" | "shipping_zone_methods" | "tax_rates" | "translations" | "user_addresses" | "variant_attribute_mapping" | "product_revisions";
3713
+ table: "site_settings" | "languages" | "pages" | "posts" | "products" | "profiles" | "coupons" | "orders" | "freemius_plans" | "media" | "navigation_items" | "product_variants" | "product_attributes" | "shipping_zones" | "product_attribute_terms" | "blocks" | "cortex_ai_db_mutation_audit" | "coupon_freemius_mappings" | "coupon_products" | "coupon_redemptions" | "currencies" | "freemius_pricing" | "inventory_items" | "logos" | "order_items" | "package_activations" | "page_revisions" | "post_revisions" | "product_media" | "product_revisions" | "shipping_zone_locations" | "shipping_zone_methods" | "tax_rates" | "translations" | "user_addresses" | "variant_attribute_mapping";
3714
3714
  auditError?: string | undefined;
3715
3715
  affectedCount: number;
3716
3716
  auditLogged: boolean;
@@ -3719,7 +3719,7 @@ export declare function createCortexGlobalAgentTools(context?: ToolExecutionCont
3719
3719
  mutationExecuted: boolean;
3720
3720
  operation: "delete" | "update" | "insert" | "upsert";
3721
3721
  success: boolean;
3722
- table: "site_settings" | "languages" | "pages" | "posts" | "products" | "profiles" | "coupons" | "orders" | "freemius_plans" | "media" | "navigation_items" | "product_variants" | "product_attributes" | "shipping_zones" | "product_attribute_terms" | "blocks" | "cortex_ai_db_mutation_audit" | "coupon_freemius_mappings" | "coupon_products" | "coupon_redemptions" | "currencies" | "freemius_pricing" | "inventory_items" | "logos" | "order_items" | "package_activations" | "page_revisions" | "post_revisions" | "product_media" | "shipping_zone_locations" | "shipping_zone_methods" | "tax_rates" | "translations" | "user_addresses" | "variant_attribute_mapping" | "product_revisions";
3722
+ table: "site_settings" | "languages" | "pages" | "posts" | "products" | "profiles" | "coupons" | "orders" | "freemius_plans" | "media" | "navigation_items" | "product_variants" | "product_attributes" | "shipping_zones" | "product_attribute_terms" | "blocks" | "cortex_ai_db_mutation_audit" | "coupon_freemius_mappings" | "coupon_products" | "coupon_redemptions" | "currencies" | "freemius_pricing" | "inventory_items" | "logos" | "order_items" | "package_activations" | "page_revisions" | "post_revisions" | "product_media" | "product_revisions" | "shipping_zone_locations" | "shipping_zone_methods" | "tax_rates" | "translations" | "user_addresses" | "variant_attribute_mapping";
3723
3723
  auditError?: string | undefined;
3724
3724
  auditLogged: boolean;
3725
3725
  }>;
@@ -3743,7 +3743,7 @@ export declare function createCortexGlobalAgentTools(context?: ToolExecutionCont
3743
3743
  offset: number;
3744
3744
  rows: unknown[];
3745
3745
  success: boolean;
3746
- table: "site_settings" | "languages" | "pages" | "posts" | "products" | "profiles" | "coupons" | "orders" | "freemius_plans" | "media" | "navigation_items" | "product_variants" | "product_attributes" | "shipping_zones" | "product_attribute_terms" | "blocks" | "cortex_ai_db_mutation_audit" | "coupon_freemius_mappings" | "coupon_products" | "coupon_redemptions" | "currencies" | "freemius_pricing" | "inventory_items" | "logos" | "order_items" | "package_activations" | "page_revisions" | "post_revisions" | "product_media" | "shipping_zone_locations" | "shipping_zone_methods" | "tax_rates" | "translations" | "user_addresses" | "variant_attribute_mapping" | "product_revisions";
3746
+ table: "site_settings" | "languages" | "pages" | "posts" | "products" | "profiles" | "coupons" | "orders" | "freemius_plans" | "media" | "navigation_items" | "product_variants" | "product_attributes" | "shipping_zones" | "product_attribute_terms" | "blocks" | "cortex_ai_db_mutation_audit" | "coupon_freemius_mappings" | "coupon_products" | "coupon_redemptions" | "currencies" | "freemius_pricing" | "inventory_items" | "logos" | "order_items" | "package_activations" | "page_revisions" | "post_revisions" | "product_media" | "product_revisions" | "shipping_zone_locations" | "shipping_zone_methods" | "tax_rates" | "translations" | "user_addresses" | "variant_attribute_mapping";
3747
3747
  }>;
3748
3748
  };
3749
3749
  export {};
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const _=require("./mcp-tool-registry.cjs.js"),P="nextblock-cortex-ai",M="NextBlock Cortex AI",N="2025-06-18",C="2025-11-25",l="2026-07-28",S=[l,C,"2025-06-18","2025-03-26","2024-11-05"],y=-32700,O=-32600,d=-32601,R=-32602,E=-32603;function u(t,e){return{body:{id:t,jsonrpc:"2.0",result:e},status:200}}function a(t,e,i,r=200,c){return{body:{error:{code:e,message:i},id:t,jsonrpc:"2.0"},status:r}}const s={body:null,status:202};function h(t){return typeof t.method=="string"&&t.id===void 0}function A(t,e){const i=t.params?._meta;if(i&&typeof i=="object"){const r=i["io.modelcontextprotocol/protocolVersion"];if(typeof r=="string"&&r>=l)return!0}return!!(e&&e>=l)}function x(t){return typeof t!="string"||!t?N:S.includes(t)?t:C}function g(t,e){const i=t.scopes.includes("write");return{capabilities:{prompts:{},resources:{},tools:{}},instructions:t.instructions??["NextBlock Cortex AI exposes this CMS through typed tools.","Call get_database_schema first when you are unsure what exists.",i?"Page builds should go through generate_jsonb_layout, which stages a reviewable Live Draft rather than publishing directly.":"This connection is READ-ONLY. Mutating tools are not available on this token."].join(" "),protocolVersion:x(e),serverInfo:{name:P,title:M,version:t.serverVersion,websiteUrl:"https://nextblock.dev"}}}async function I(t,e){if(t===null||typeof t!="object"||Array.isArray(t))return a(null,O,"Expected a single JSON-RPC message.",400);if(typeof t.method!="string")return"result"in t||"error"in t?s:a(t.id??null,O,'Missing "method".',400);const{method:i}=t,r=t.id??null,c=h(t);if(i.startsWith("notifications/"))return s;switch(i){case"initialize":return c?s:u(r,g(e,t.params?.protocolVersion));case"ping":return c?s:u(r,{});case"tools/list":return c?s:u(r,{tools:_.buildCortexMcpToolDefinitions({context:e.context,scopes:e.scopes})});case"tools/call":{if(c)return s;const n=t.params?.name;if(typeof n!="string"||!n)return a(r,R,"tools/call requires params.name.");try{const o=await _.callCortexMcpTool({args:t.params?.arguments,context:e.context,name:n,scopes:e.scopes});return u(r,o)}catch(o){return o instanceof _.CortexMcpUnknownToolError||o instanceof _.CortexMcpForbiddenToolError?a(r,R,o.message):a(r,E,o instanceof Error?o.message:"Tool execution failed.")}}case"resources/list":return c?s:u(r,{resources:_.CORTEX_MCP_RESOURCES});case"resources/templates/list":return c?s:u(r,{resourceTemplates:[]});case"resources/read":{if(c)return s;const n=t.params?.uri;if(typeof n!="string"||!n)return a(r,R,"resources/read requires params.uri.");try{const o=await _.readCortexMcpResource({context:e.context,uri:n});return o?u(r,{contents:[o]}):a(r,R,`Unknown resource: ${n}`)}catch(o){return a(r,E,o instanceof Error?o.message:"Resource read failed.")}}case"prompts/list":return c?s:u(r,{prompts:_.CORTEX_MCP_PROMPTS});case"prompts/get":{if(c)return s;const n=t.params?.name;if(typeof n!="string"||!n)return a(r,R,"prompts/get requires params.name.");const o=t.params?.arguments,p={};if(o&&typeof o=="object")for(const[m,T]of Object.entries(o))typeof T=="string"&&(p[m]=T);const f=_.getCortexMcpPrompt({args:p,name:n});return f?u(r,f):a(r,R,`Unknown prompt: ${n}`)}default:return c?s:a(r,d,`Method not found: ${i}`)}}exports.CORTEX_MCP_DEFAULT_PROTOCOL_VERSION=N;exports.CORTEX_MCP_LATEST_LEGACY_PROTOCOL_VERSION=C;exports.CORTEX_MCP_MODERN_PROTOCOL_VERSION=l;exports.CORTEX_MCP_SERVER_NAME=P;exports.CORTEX_MCP_SERVER_TITLE=M;exports.CORTEX_MCP_SUPPORTED_PROTOCOL_VERSIONS=S;exports.JSON_RPC_INTERNAL_ERROR=E;exports.JSON_RPC_INVALID_PARAMS=R;exports.JSON_RPC_INVALID_REQUEST=O;exports.JSON_RPC_METHOD_NOT_FOUND=d;exports.JSON_RPC_PARSE_ERROR=y;exports.handleCortexMcpMessage=I;exports.isJsonRpcNotification=h;exports.isModernEraMessage=A;
@@ -0,0 +1,76 @@
1
+ import { CortexMcpToolContext } from './mcp-tool-registry';
2
+ import { CortexAiMcpScope } from './mcp-tokens';
3
+ /**
4
+ * JSON-RPC 2.0 engine for the Cortex AI MCP server.
5
+ *
6
+ * Transport-agnostic on purpose: it takes a parsed message and returns a status +
7
+ * body, so the Next.js route handler stays a thin HTTP shim and this file is testable
8
+ * without a server. It imports nothing from `next`.
9
+ *
10
+ * ## Why hand-rolled rather than @modelcontextprotocol/sdk
11
+ *
12
+ * The protocol surface a CMS tool server needs — initialize, tools/list, tools/call,
13
+ * resources, prompts, ping — is small and entirely declarative. The v1 SDK pulls in
14
+ * express, cors, hono and @hono/node-server, which is a lot of transitive weight for
15
+ * a publishable premium lib that carries only a `next` peer dependency, and its
16
+ * default transport class is built on Node's IncomingMessage/ServerResponse rather
17
+ * than the Web Request/Response the App Router hands you.
18
+ *
19
+ * ## Dual-era
20
+ *
21
+ * The spec forked. `2026-07-28` is stateless: no `initialize` handshake, no session
22
+ * id, protocol metadata rides in a `_meta` envelope on every request. Everything up
23
+ * to `2025-11-25` is handshake-based. As of this writing every shipping client
24
+ * (Claude Code, Claude Desktop, Cursor, VS Code) speaks the legacy era, so that path
25
+ * has to work; the modern path is handled too so an upgraded client keeps working.
26
+ * A dual-era server picks its behaviour from how the client opens — an `initialize`
27
+ * request selects legacy, per-request `_meta` selects modern — which costs us
28
+ * nothing here because the server is stateless either way.
29
+ */
30
+ export declare const CORTEX_MCP_SERVER_NAME = "nextblock-cortex-ai";
31
+ export declare const CORTEX_MCP_SERVER_TITLE = "NextBlock Cortex AI";
32
+ /** What we answer an `initialize` with when the client asks for something we don't know. */
33
+ export declare const CORTEX_MCP_DEFAULT_PROTOCOL_VERSION = "2025-06-18";
34
+ export declare const CORTEX_MCP_LATEST_LEGACY_PROTOCOL_VERSION = "2025-11-25";
35
+ export declare const CORTEX_MCP_MODERN_PROTOCOL_VERSION = "2026-07-28";
36
+ export declare const CORTEX_MCP_SUPPORTED_PROTOCOL_VERSIONS: readonly ["2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
37
+ export declare const JSON_RPC_PARSE_ERROR = -32700;
38
+ export declare const JSON_RPC_INVALID_REQUEST = -32600;
39
+ export declare const JSON_RPC_METHOD_NOT_FOUND = -32601;
40
+ export declare const JSON_RPC_INVALID_PARAMS = -32602;
41
+ export declare const JSON_RPC_INTERNAL_ERROR = -32603;
42
+ export type JsonRpcId = number | string | null;
43
+ export type JsonRpcMessage = {
44
+ id?: JsonRpcId;
45
+ jsonrpc?: string;
46
+ method?: string;
47
+ params?: Record<string, unknown>;
48
+ };
49
+ export type CortexMcpResponse = {
50
+ /** HTTP status the transport should use. */
51
+ status: number;
52
+ /** `null` means "no body" — a 202 for a notification. */
53
+ body: Record<string, unknown> | null;
54
+ };
55
+ export type CortexMcpHandlerDeps = {
56
+ context?: CortexMcpToolContext;
57
+ /** Instructions surfaced to the client at initialize time. */
58
+ instructions?: string;
59
+ scopes: readonly CortexAiMcpScope[];
60
+ serverVersion: string;
61
+ };
62
+ export declare function isJsonRpcNotification(message: JsonRpcMessage): boolean;
63
+ /**
64
+ * True when the client is speaking the stateless 2026-07-28 era.
65
+ *
66
+ * Detected from the per-request `_meta` envelope the modern spec requires, with the
67
+ * transport-supplied `MCP-Protocol-Version` header as a secondary signal.
68
+ */
69
+ export declare function isModernEraMessage(message: JsonRpcMessage, protocolVersionHeader?: string | null): boolean;
70
+ /**
71
+ * Handle one JSON-RPC message.
72
+ *
73
+ * Returns 202/no-body for notifications and responses (which a server receives but
74
+ * never answers), and a JSON-RPC envelope for requests.
75
+ */
76
+ export declare function handleCortexMcpMessage(message: JsonRpcMessage, deps: CortexMcpHandlerDeps): Promise<CortexMcpResponse>;
@@ -0,0 +1,157 @@
1
+ import { getCortexMcpPrompt as P, readCortexMcpResource as d, callCortexMcpTool as h, CortexMcpUnknownToolError as m, CortexMcpForbiddenToolError as M, buildCortexMcpToolDefinitions as x, CORTEX_MCP_PROMPTS as y, CORTEX_MCP_RESOURCES as N } from "./mcp-tool-registry.es.js";
2
+ const S = "nextblock-cortex-ai", b = "NextBlock Cortex AI", g = "2025-06-18", O = "2025-11-25", p = "2026-07-28", A = [
3
+ p,
4
+ O,
5
+ "2025-06-18",
6
+ "2025-03-26",
7
+ "2024-11-05"
8
+ ], j = -32700, E = -32600, I = -32601, l = -32602, C = -32603;
9
+ function u(t, n) {
10
+ return { body: { id: t, jsonrpc: "2.0", result: n }, status: 200 };
11
+ }
12
+ function a(t, n, i, r = 200, c) {
13
+ return {
14
+ body: {
15
+ error: { code: n, message: i },
16
+ id: t,
17
+ jsonrpc: "2.0"
18
+ },
19
+ status: r
20
+ };
21
+ }
22
+ const s = { body: null, status: 202 };
23
+ function w(t) {
24
+ return typeof t.method == "string" && t.id === void 0;
25
+ }
26
+ function v(t, n) {
27
+ const i = t.params?._meta;
28
+ if (i && typeof i == "object") {
29
+ const r = i["io.modelcontextprotocol/protocolVersion"];
30
+ if (typeof r == "string" && r >= p)
31
+ return !0;
32
+ }
33
+ return !!(n && n >= p);
34
+ }
35
+ function L(t) {
36
+ return typeof t != "string" || !t ? g : A.includes(t) ? t : O;
37
+ }
38
+ function V(t, n) {
39
+ const i = t.scopes.includes("write");
40
+ return {
41
+ capabilities: {
42
+ // listChanged is deliberately absent: the tool set is static per token, so
43
+ // advertising change notifications we never send would be a lie a client
44
+ // could wait on.
45
+ prompts: {},
46
+ resources: {},
47
+ tools: {}
48
+ },
49
+ instructions: t.instructions ?? [
50
+ "NextBlock Cortex AI exposes this CMS through typed tools.",
51
+ "Call get_database_schema first when you are unsure what exists.",
52
+ i ? "Page builds should go through generate_jsonb_layout, which stages a reviewable Live Draft rather than publishing directly." : "This connection is READ-ONLY. Mutating tools are not available on this token."
53
+ ].join(" "),
54
+ protocolVersion: L(n),
55
+ serverInfo: {
56
+ name: S,
57
+ title: b,
58
+ version: t.serverVersion,
59
+ websiteUrl: "https://nextblock.dev"
60
+ }
61
+ };
62
+ }
63
+ async function k(t, n) {
64
+ if (t === null || typeof t != "object" || Array.isArray(t))
65
+ return a(null, E, "Expected a single JSON-RPC message.", 400);
66
+ if (typeof t.method != "string")
67
+ return "result" in t || "error" in t ? s : a(t.id ?? null, E, 'Missing "method".', 400);
68
+ const { method: i } = t, r = t.id ?? null, c = w(t);
69
+ if (i.startsWith("notifications/"))
70
+ return s;
71
+ switch (i) {
72
+ case "initialize":
73
+ return c ? s : u(r, V(n, t.params?.protocolVersion));
74
+ case "ping":
75
+ return c ? s : u(r, {});
76
+ case "tools/list":
77
+ return c ? s : u(r, {
78
+ tools: x({ context: n.context, scopes: n.scopes })
79
+ });
80
+ case "tools/call": {
81
+ if (c)
82
+ return s;
83
+ const e = t.params?.name;
84
+ if (typeof e != "string" || !e)
85
+ return a(r, l, "tools/call requires params.name.");
86
+ try {
87
+ const o = await h({
88
+ args: t.params?.arguments,
89
+ context: n.context,
90
+ name: e,
91
+ scopes: n.scopes
92
+ });
93
+ return u(r, o);
94
+ } catch (o) {
95
+ return o instanceof m || o instanceof M ? a(r, l, o.message) : a(
96
+ r,
97
+ C,
98
+ o instanceof Error ? o.message : "Tool execution failed."
99
+ );
100
+ }
101
+ }
102
+ case "resources/list":
103
+ return c ? s : u(r, { resources: N });
104
+ case "resources/templates/list":
105
+ return c ? s : u(r, { resourceTemplates: [] });
106
+ case "resources/read": {
107
+ if (c)
108
+ return s;
109
+ const e = t.params?.uri;
110
+ if (typeof e != "string" || !e)
111
+ return a(r, l, "resources/read requires params.uri.");
112
+ try {
113
+ const o = await d({ context: n.context, uri: e });
114
+ return o ? u(r, { contents: [o] }) : a(r, l, `Unknown resource: ${e}`);
115
+ } catch (o) {
116
+ return a(
117
+ r,
118
+ C,
119
+ o instanceof Error ? o.message : "Resource read failed."
120
+ );
121
+ }
122
+ }
123
+ case "prompts/list":
124
+ return c ? s : u(r, { prompts: y });
125
+ case "prompts/get": {
126
+ if (c)
127
+ return s;
128
+ const e = t.params?.name;
129
+ if (typeof e != "string" || !e)
130
+ return a(r, l, "prompts/get requires params.name.");
131
+ const o = t.params?.arguments, f = {};
132
+ if (o && typeof o == "object")
133
+ for (const [T, _] of Object.entries(o))
134
+ typeof _ == "string" && (f[T] = _);
135
+ const R = P({ args: f, name: e });
136
+ return R ? u(r, R) : a(r, l, `Unknown prompt: ${e}`);
137
+ }
138
+ default:
139
+ return c ? s : a(r, I, `Method not found: ${i}`);
140
+ }
141
+ }
142
+ export {
143
+ g as CORTEX_MCP_DEFAULT_PROTOCOL_VERSION,
144
+ O as CORTEX_MCP_LATEST_LEGACY_PROTOCOL_VERSION,
145
+ p as CORTEX_MCP_MODERN_PROTOCOL_VERSION,
146
+ S as CORTEX_MCP_SERVER_NAME,
147
+ b as CORTEX_MCP_SERVER_TITLE,
148
+ A as CORTEX_MCP_SUPPORTED_PROTOCOL_VERSIONS,
149
+ C as JSON_RPC_INTERNAL_ERROR,
150
+ l as JSON_RPC_INVALID_PARAMS,
151
+ E as JSON_RPC_INVALID_REQUEST,
152
+ I as JSON_RPC_METHOD_NOT_FOUND,
153
+ j as JSON_RPC_PARSE_ERROR,
154
+ k as handleCortexMcpMessage,
155
+ w as isJsonRpcNotification,
156
+ v as isModernEraMessage
157
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("node:crypto"),p="Cortex AI MCP token utilities can only be imported from server-side code.";function l(){if(!(typeof window>"u"))throw new Error(p)}const T="cortex_ai_mcp_settings",u="mcp_access_tokens",i="nbmcp_",A=32,k=8,d=["read","write"],s={allowLocalhostWithoutToken:!0,enabled:!1};function h(e){const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{};return{allowLocalhostWithoutToken:typeof t.allowLocalhostWithoutToken=="boolean"?t.allowLocalhostWithoutToken:s.allowLocalhostWithoutToken,enabled:typeof t.enabled=="boolean"?t.enabled:s.enabled}}async function m(e){if(e)try{const{data:t}=await e.from("site_settings").select("value").eq("key",T).maybeSingle();if(t?.value)return h(t.value)}catch{}return{...s}}function a(e){return l(),c.createHash("sha256").update(e.trim(),"utf8").digest("hex")}function M(){l();const e=`${i}${c.randomBytes(A).toString("base64url")}`;return{token:e,tokenHash:a(e),tokenPrefix:e.slice(0,i.length+k)}}function O(e){const t=e?.trim();if(!t)return null;const o=/^Bearer\s+(.+)$/i.exec(t);return(o?o[1]:t).trim()||null}function y(e){const o=(Array.isArray(e)?e:[]).filter(n=>d.includes(n));return o.length>0?Array.from(new Set(o)):["read"]}async function x(e,t,o=new Date){l();const n=t?.trim();if(!n||n.length<16)return{reason:"malformed",valid:!1};const _=a(n),{data:f,error:E}=await e.from(u).select("id, name, scopes, token_prefix, created_at, created_by, expires_at, last_used_at, revoked_at").eq("token_hash",_).maybeSingle();if(E||!f)return{reason:"unknown",valid:!1};const S=Buffer.from(_,"utf8");if(!c.timingSafeEqual(S,Buffer.from(a(n),"utf8")))return{reason:"unknown",valid:!1};const r=f;return r.revoked_at?{reason:"revoked",valid:!1}:r.expires_at&&new Date(r.expires_at).getTime()<=o.getTime()?{reason:"expired",valid:!1}:{scopes:y(r.scopes),token:r,valid:!0}}async function R(e,t,o=new Date){try{await e.from(u).update({last_used_at:o.toISOString()}).eq("id",t)}catch{}}const v=new Set(["localhost","127.0.0.1","[::1]","::1","0.0.0.0"]);function C(e){const t=e?.trim().toLowerCase();if(!t)return!1;const o=t.startsWith("[")?t.replace(/\]:\d+$/,"]"):t.replace(/:\d+$/,"");return v.has(o)||o.endsWith(".localhost")}function I(e){return!e.settings.allowLocalhostWithoutToken||process.env.NODE_ENV==="production"?!1:C(e.hostHeader)}exports.CORTEX_AI_MCP_SCOPES=d;exports.CORTEX_AI_MCP_SETTINGS_DEFAULTS=s;exports.CORTEX_AI_MCP_SETTINGS_KEY=T;exports.CORTEX_AI_MCP_TOKENS_TABLE=u;exports.CORTEX_AI_MCP_TOKEN_PREFIX=i;exports.hashCortexAiMcpToken=a;exports.isLocalhostHost=C;exports.mintCortexAiMcpToken=M;exports.normalizeCortexAiMcpSettings=h;exports.parseBearerToken=O;exports.resolveCortexAiMcpSettings=m;exports.shouldTrustLocalMcpRequest=I;exports.touchCortexAiMcpToken=R;exports.verifyCortexAiMcpToken=x;
@@ -0,0 +1,95 @@
1
+ export declare const CORTEX_AI_MCP_SETTINGS_KEY = "cortex_ai_mcp_settings";
2
+ export declare const CORTEX_AI_MCP_TOKENS_TABLE = "mcp_access_tokens";
3
+ /** Human-recognisable prefix so a leaked string is greppable and self-identifying. */
4
+ export declare const CORTEX_AI_MCP_TOKEN_PREFIX = "nbmcp_";
5
+ export type CortexAiMcpScope = 'read' | 'write';
6
+ export declare const CORTEX_AI_MCP_SCOPES: readonly CortexAiMcpScope[];
7
+ export type CortexAiMcpSettings = {
8
+ /**
9
+ * When false the endpoint answers 404 for everything. Off by default: an MCP server
10
+ * is a remote write surface onto the CMS, so it must be an explicit opt-in rather
11
+ * than something a fresh install exposes silently.
12
+ */
13
+ enabled: boolean;
14
+ /**
15
+ * Trust loopback callers without a token. Convenient for `npx nx serve nextblock`
16
+ * plus a local Claude Code, and safe because the request must already originate on
17
+ * the machine running the CMS. Ignored entirely in production (see
18
+ * `shouldTrustLocalMcpRequest`).
19
+ */
20
+ allowLocalhostWithoutToken: boolean;
21
+ };
22
+ export declare const CORTEX_AI_MCP_SETTINGS_DEFAULTS: CortexAiMcpSettings;
23
+ export declare function normalizeCortexAiMcpSettings(raw: unknown): CortexAiMcpSettings;
24
+ type SupabaseLike = {
25
+ from: (table: string) => any;
26
+ };
27
+ /** Read the MCP server settings row, falling back to defaults on any failure. */
28
+ export declare function resolveCortexAiMcpSettings(supabase?: SupabaseLike | null): Promise<CortexAiMcpSettings>;
29
+ export declare function hashCortexAiMcpToken(token: string): string;
30
+ export type MintedCortexAiMcpToken = {
31
+ /** Plaintext. Shown to the admin once, never persisted. */
32
+ token: string;
33
+ tokenHash: string;
34
+ tokenPrefix: string;
35
+ };
36
+ export declare function mintCortexAiMcpToken(): MintedCortexAiMcpToken;
37
+ /**
38
+ * Pull the bearer credential out of an Authorization header.
39
+ *
40
+ * Accepts `Bearer <token>` (case-insensitive scheme, per RFC 7235) and also a bare
41
+ * token, because Claude Desktop's connector UI asks the user to type the scheme by
42
+ * hand and people routinely paste the token alone.
43
+ */
44
+ export declare function parseBearerToken(headerValue: string | null | undefined): string | null;
45
+ export type CortexAiMcpTokenRow = {
46
+ created_at: string;
47
+ created_by: string | null;
48
+ expires_at: string | null;
49
+ id: string;
50
+ last_used_at: string | null;
51
+ name: string;
52
+ revoked_at: string | null;
53
+ scopes: string[];
54
+ token_prefix: string;
55
+ };
56
+ export type CortexAiMcpTokenVerification = {
57
+ reason: 'expired' | 'malformed' | 'revoked' | 'unknown';
58
+ valid: false;
59
+ } | {
60
+ scopes: CortexAiMcpScope[];
61
+ token: CortexAiMcpTokenRow;
62
+ valid: true;
63
+ };
64
+ /**
65
+ * Look a plaintext token up by hash and report whether it may be used.
66
+ *
67
+ * The lookup is an indexed equality probe on the hash rather than a scan-and-compare,
68
+ * so there is no per-row timing signal to leak. The `timingSafeEqual` re-check below
69
+ * costs nothing and keeps the comparison constant-time even if a future caller passes
70
+ * a candidate row in directly.
71
+ */
72
+ export declare function verifyCortexAiMcpToken(supabase: SupabaseLike, plaintextToken: string, now?: Date): Promise<CortexAiMcpTokenVerification>;
73
+ /**
74
+ * Stamp `last_used_at` so an admin can spot a token that is still live but unused.
75
+ *
76
+ * Fire-and-forget: a failed bookkeeping write must never fail the MCP call that
77
+ * already authenticated successfully.
78
+ */
79
+ export declare function touchCortexAiMcpToken(supabase: SupabaseLike, tokenId: string, now?: Date): Promise<void>;
80
+ /** True when `value` is a loopback host, with or without a port. */
81
+ export declare function isLocalhostHost(value: string | null | undefined): boolean;
82
+ /**
83
+ * Decide whether a tokenless request may be trusted as local.
84
+ *
85
+ * Requires all three of: the setting on, a loopback Host header, and a non-production
86
+ * `NODE_ENV`. The last condition is the important one — behind a reverse proxy the
87
+ * Host header is attacker-controllable, so localhost trust is a development
88
+ * affordance only and must never be the thing standing between the public internet
89
+ * and a write-capable CMS endpoint.
90
+ */
91
+ export declare function shouldTrustLocalMcpRequest(params: {
92
+ hostHeader: string | null | undefined;
93
+ settings: CortexAiMcpSettings;
94
+ }): boolean;
95
+ export {};
@@ -0,0 +1,104 @@
1
+ import { createHash as h, randomBytes as T, timingSafeEqual as p } from "node:crypto";
2
+ const m = "Cortex AI MCP token utilities can only be imported from server-side code.";
3
+ function i() {
4
+ if (!(typeof window > "u"))
5
+ throw new Error(m);
6
+ }
7
+ const k = "cortex_ai_mcp_settings", f = "mcp_access_tokens", u = "nbmcp_", E = 32, S = 8, A = ["read", "write"], s = {
8
+ allowLocalhostWithoutToken: !0,
9
+ enabled: !1
10
+ };
11
+ function C(e) {
12
+ const t = e && typeof e == "object" && !Array.isArray(e) ? e : {};
13
+ return {
14
+ allowLocalhostWithoutToken: typeof t.allowLocalhostWithoutToken == "boolean" ? t.allowLocalhostWithoutToken : s.allowLocalhostWithoutToken,
15
+ enabled: typeof t.enabled == "boolean" ? t.enabled : s.enabled
16
+ };
17
+ }
18
+ async function x(e) {
19
+ if (e)
20
+ try {
21
+ const { data: t } = await e.from("site_settings").select("value").eq("key", k).maybeSingle();
22
+ if (t?.value)
23
+ return C(t.value);
24
+ } catch {
25
+ }
26
+ return { ...s };
27
+ }
28
+ function a(e) {
29
+ return i(), h("sha256").update(e.trim(), "utf8").digest("hex");
30
+ }
31
+ function M() {
32
+ i();
33
+ const e = `${u}${T(E).toString(
34
+ "base64url"
35
+ )}`;
36
+ return {
37
+ token: e,
38
+ tokenHash: a(e),
39
+ tokenPrefix: e.slice(
40
+ 0,
41
+ u.length + S
42
+ )
43
+ };
44
+ }
45
+ function g(e) {
46
+ const t = e?.trim();
47
+ if (!t)
48
+ return null;
49
+ const o = /^Bearer\s+(.+)$/i.exec(t);
50
+ return (o ? o[1] : t).trim() || null;
51
+ }
52
+ function y(e) {
53
+ const o = (Array.isArray(e) ? e : []).filter(
54
+ (n) => A.includes(n)
55
+ );
56
+ return o.length > 0 ? Array.from(new Set(o)) : ["read"];
57
+ }
58
+ async function L(e, t, o = /* @__PURE__ */ new Date()) {
59
+ i();
60
+ const n = t?.trim();
61
+ if (!n || n.length < 16)
62
+ return { reason: "malformed", valid: !1 };
63
+ const c = a(n), { data: l, error: _ } = await e.from(f).select("id, name, scopes, token_prefix, created_at, created_by, expires_at, last_used_at, revoked_at").eq("token_hash", c).maybeSingle();
64
+ if (_ || !l)
65
+ return { reason: "unknown", valid: !1 };
66
+ const d = Buffer.from(c, "utf8");
67
+ if (!p(d, Buffer.from(a(n), "utf8")))
68
+ return { reason: "unknown", valid: !1 };
69
+ const r = l;
70
+ return r.revoked_at ? { reason: "revoked", valid: !1 } : r.expires_at && new Date(r.expires_at).getTime() <= o.getTime() ? { reason: "expired", valid: !1 } : { scopes: y(r.scopes), token: r, valid: !0 };
71
+ }
72
+ async function R(e, t, o = /* @__PURE__ */ new Date()) {
73
+ try {
74
+ await e.from(f).update({ last_used_at: o.toISOString() }).eq("id", t);
75
+ } catch {
76
+ }
77
+ }
78
+ const w = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1", "0.0.0.0"]);
79
+ function O(e) {
80
+ const t = e?.trim().toLowerCase();
81
+ if (!t)
82
+ return !1;
83
+ const o = t.startsWith("[") ? t.replace(/\]:\d+$/, "]") : t.replace(/:\d+$/, "");
84
+ return w.has(o) || o.endsWith(".localhost");
85
+ }
86
+ function b(e) {
87
+ return !e.settings.allowLocalhostWithoutToken || process.env.NODE_ENV === "production" ? !1 : O(e.hostHeader);
88
+ }
89
+ export {
90
+ A as CORTEX_AI_MCP_SCOPES,
91
+ s as CORTEX_AI_MCP_SETTINGS_DEFAULTS,
92
+ k as CORTEX_AI_MCP_SETTINGS_KEY,
93
+ f as CORTEX_AI_MCP_TOKENS_TABLE,
94
+ u as CORTEX_AI_MCP_TOKEN_PREFIX,
95
+ a as hashCortexAiMcpToken,
96
+ O as isLocalhostHost,
97
+ M as mintCortexAiMcpToken,
98
+ C as normalizeCortexAiMcpSettings,
99
+ g as parseBearerToken,
100
+ x as resolveCortexAiMcpSettings,
101
+ b as shouldTrustLocalMcpRequest,
102
+ R as touchCortexAiMcpToken,
103
+ L as verifyCortexAiMcpToken
104
+ };
@@ -0,0 +1,4 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("./ai-global-agent-tools.cjs.js");require("./zod-config.cjs.js");const w=require("zod"),i={create_cms_page:"write",create_cms_post:"write",create_cms_product:"write",create_custom_block:"write",delete_cms_item:"write",delete_custom_block:"write",describe_database_schema:"read",execute_cms_action_plan:"write",execute_database_action_plan:"write",execute_database_mutation:"write",fetch_ecommerce_stats:"read",fetch_url_content:"read",insert_content_block:"write",list_custom_blocks:"read",prepare_delete_cms_item:"read",read_current_cms_item:"read",read_database_records:"read",rewrite_page_draft:"write",search_documentation:"read",search_stock_photos:"read",set_content_images:"write",translate_page:"write",update_cms_item_field:"write",update_content_block:"write",update_current_cms_fields:"write",update_custom_block:"write",update_footer:"write",update_navigation_bar:"write",update_section_column_block:"write"},s={generate_jsonb_layout:{canonical:"rewrite_page_draft",description:"Generate a complete, strictly-validated JSONB block layout and write it into a target page or post. Blocks are normalized and validated against the NextBlock block schema, then staged as a Live Draft on the target record — nothing goes live until an editor publishes the draft, and publishing snapshots a revision so the change is reversible. Use for whole-page builds and redesigns. Alias of `rewrite_page_draft`.",title:"Generate JSONB layout"},get_database_schema:{canonical:"describe_database_schema",description:"Return the current CMS database structure: every table Cortex AI may read or mutate, its columns, primary keys, and whether it is read-only. Pair with `list_custom_blocks` for the active data-defined block definitions and with the `cortex://schema/blocks` resource for the built-in block types. Read-only. Alias of `describe_database_schema`.",title:"Get database schema"},query_site_analytics:{canonical:"fetch_ecommerce_stats",description:"Query quantitative site analytics: revenue, order counts, order-status breakdowns, and top-selling products over a time range. Read-only. For inventory levels and post/page metrics that this report does not cover, use `read_database_records` against the `products`, `product_variants`, `posts`, or `pages` tables. Alias of `fetch_ecommerce_stats`.",title:"Query site analytics"},search_stock_media:{canonical:"search_stock_photos",description:"Search free stock media (Pexels, then Unsplash) for contextually relevant imagery, returning direct image URLs with dimensions, alt text, and photographer attribution. Drop a returned `url` straight into an image block's `external_url` or a section background. Read-only and zero cost. Requires a stock provider key in CMS Settings → Cortex AI. Alias of `search_stock_photos`.",title:"Search stock media"},update_site_navigation:{canonical:"update_navigation_bar",description:'Alter the public site navigation: header menu items and their nested child hierarchy, per locale. Use mode "append" to add links while preserving the existing menu, "update" to rename or re-point one link, and "replace" only when rebuilding the whole menu. For footer links and copyright use `update_footer`. Alias of `update_navigation_bar`.',title:"Update site navigation"}};function g(e){return e in s?s[e].canonical:e in i?e:null}function b(e){const t=g(e);return t?i[t]:null}function c(e,t){const o=b(t);return o?o==="read"?e.length>0:e.includes("write"):!1}function m(e){return e.split("_").map((o,r)=>r===0?o.charAt(0).toUpperCase()+o.slice(1):o).join(" ")}function h(e){const t={additionalProperties:!1,type:"object"};if(!e||typeof e!="object")return t;try{const o=w.z.toJSONSchema(e,{cycles:"ref",io:"input",reused:"inline",target:"draft-2020-12",unrepresentable:"any"});return delete o.$schema,o.type==="object"?o:t}catch{return t}}function l(e){return f.createCortexGlobalAgentTools(e)}function C(e){const t=Object.keys(l(e)),o=Object.keys(i);return{missingFromRegistry:o.filter(r=>!t.includes(r)),unclassified:t.filter(r=>!o.includes(r))}}function T(e){const t=l(e.context),o=[];for(const[r,a]of Object.entries(t))!(r in i)||!c(e.scopes,r)||o.push({description:a.description??`Cortex AI ${m(r)} tool.`,inputSchema:h(a.inputSchema),name:r,title:m(r)});for(const[r,a]of Object.entries(s)){const n=t[a.canonical];!n||!c(e.scopes,r)||o.push({description:a.description,inputSchema:h(n.inputSchema),name:r,title:a.title})}return o.sort((r,a)=>r.name.localeCompare(a.name))}function _(e,t){return{content:[{text:e,type:"text"}],isError:t}}function S(e){return e.issues.map(t=>`${t.path.length>0?t.path.join("."):"(root)"}: ${t.message}`).join("; ")}class p extends Error{constructor(t){super(`Unknown tool: ${t}`),this.name="CortexMcpUnknownToolError"}}class y extends Error{constructor(t){super(`Tool "${t}" requires the "write" scope. This MCP token is read-only — mint a read+write token in CMS Settings → Cortex AI.`),this.name="CortexMcpForbiddenToolError"}}async function v(e){const t=g(e.name);if(!t)throw new p(e.name);if(!c(e.scopes,e.name))throw new y(e.name);const r=l(e.context)[t];if(!r||typeof r.execute!="function")throw new p(e.name);const a=e.args&&typeof e.args=="object"?e.args:{};if(r.inputSchema&&typeof r.inputSchema.safeParse=="function"){const n=r.inputSchema.safeParse(a);if(!n.success)return _(`Invalid arguments for "${e.name}": ${S(n.error)}`,!0)}try{const n=await r.execute(a),u=JSON.stringify(n??{success:!0},null,2),d=n&&typeof n=="object"&&!Array.isArray(n)?n:null,k=d?.success===!1;return{content:[{text:u,type:"text"}],isError:k,...d?{structuredContent:d}:{}}}catch(n){const u=n instanceof Error?n.message:String(n);return _(`Tool "${e.name}" failed: ${u}`,!0)}}const M=[{description:"The CMS database structure Cortex AI can read and mutate: tables, columns, primary keys, and read-only flags.",mimeType:"application/json",name:"database-schema",title:"NextBlock database schema",uri:"cortex://schema/database"},{description:"The built-in NextBlock block types available when composing page, post, and product layouts.",mimeType:"application/json",name:"block-types",title:"NextBlock block types",uri:"cortex://schema/blocks"},{description:"Data-defined custom block definitions registered in this workspace, keyed by the slug used as a block instance `block_type`.",mimeType:"application/json",name:"custom-blocks",title:"NextBlock custom block definitions",uri:"cortex://schema/custom-blocks"}];async function O(e){const t=l(e.context);if(e.uri==="cortex://schema/database"){const o=await t.describe_database_schema?.execute?.({includeReadOnly:!0});return{mimeType:"application/json",text:JSON.stringify(o??{},null,2),uri:e.uri}}if(e.uri==="cortex://schema/blocks")return{mimeType:"application/json",text:JSON.stringify({blockTypes:f.availableCortexAiBlockTypes},null,2),uri:e.uri};if(e.uri==="cortex://schema/custom-blocks"){const o=await t.list_custom_blocks?.execute?.({});return{mimeType:"application/json",text:JSON.stringify(o??{},null,2),uri:e.uri}}return null}const x=[{arguments:[{description:"What the page is for, and any brand or tone notes.",name:"brief",required:!0},{description:'Slug of the page or post to rewrite, e.g. "home".',name:"slug",required:!0}],description:"Build or redesign a NextBlock page from a brief, following the section-based layout recipe, and stage it as a reviewable Live Draft.",name:"build-page",title:"Build a NextBlock page"},{arguments:[{description:"Source URL to draw structure and copy from.",name:"url",required:!0},{description:"Slug of the page to write the result into.",name:"slug",required:!0}],description:"Read an external page and rebuild an equivalent NextBlock page from it, reusing its imagery where available.",name:"clone-from-url",title:"Rebuild a page from a URL"},{arguments:[{description:'Target language code, e.g. "fr".',name:"languageCode",required:!0},{description:"Slug of the page or post to translate.",name:"slug",required:!0}],description:"Translate an existing page or post into another language, preserving its layout and imagery.",name:"translate-content",title:"Translate a page or post"}],R={"build-page":e=>[`Build the NextBlock page with slug "${e.slug??"<slug>"}" from this brief:`,"",e.brief??"<brief>","","Process:","1. Call get_database_schema and read_current_cms_item (or read_database_records on `pages`) to ground yourself in what exists.",'2. Compose the page from `section` blocks: one column entry per grid track, the first section a hero, alternating none / theme:"muted" / theme:"primary" backgrounds for rhythm. Use discrete heading blocks rather than <h2> inside text HTML.',"3. Call search_stock_media for real photography; copy each photo's attribution fields onto the image block.","4. Write the result with generate_jsonb_layout. It stages a Live Draft — tell the user to preview and publish it."].join(`
2
+ `),"clone-from-url":e=>[`Rebuild the NextBlock page "${e.slug??"<slug>"}" based on ${e.url??"<url>"}.`,"","Process:","1. Call fetch_url_content on the URL first. Use its headings, text, and mainImage — never invent an image URL.","2. Map the source structure onto NextBlock `section` blocks; do not copy its markup verbatim.","3. Write the result with generate_jsonb_layout so it lands as a reviewable Live Draft."].join(`
3
+ `),"translate-content":e=>[`Translate the page or post "${e.slug??"<slug>"}" into "${e.languageCode??"<languageCode>"}".`,"","Use the translate_page tool: it copies layout, structure, and imagery automatically and links the copy to the original translation group. Supply only the text translations — every visible string, including headings, paragraph text, button labels, image alt text, and captions. Do not rebuild the layout and do not search for new imagery."].join(`
4
+ `)};function j(e){const t=x.find(r=>r.name===e.name),o=R[e.name];return!t||!o?null:{description:t.description,messages:[{content:{text:o(e.args??{}),type:"text"},role:"user"}]}}exports.CORTEX_MCP_PROMPTS=x;exports.CORTEX_MCP_RESOURCES=M;exports.CORTEX_MCP_TOOL_ALIASES=s;exports.CORTEX_MCP_TOOL_KINDS=i;exports.CortexMcpForbiddenToolError=y;exports.CortexMcpUnknownToolError=p;exports.assertCortexMcpToolCoverage=C;exports.buildCortexMcpToolDefinitions=T;exports.callCortexMcpTool=v;exports.cortexMcpScopesAllow=c;exports.getCortexMcpPrompt=j;exports.getCortexMcpToolKind=b;exports.readCortexMcpResource=O;exports.resolveCortexMcpToolName=g;