@nextblock-cms/cortex 0.14.5 → 0.15.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.
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("@nextblock-cms/utils/custom-blocks");require("./zod-config.cjs.js");const e=require("zod"),r=["pages","posts","products","media","categories","profiles","languages"],d=e.z.enum(["article","aside","blockquote","div","figure","figcaption","h2","h3","img","p","section","span"]).describe("A safe semantic element supported by the dynamic layout renderer."),c=e.z.string().trim().max(4e3).describe("Tailwind utility classes only. Do not include CSS, style tags, or JavaScript."),l=e.z.strictObject({description:e.z.string().trim().max(500).optional(),key:o.customBlockFieldKeySchema.describe("Lowercase snake_case field key."),label:e.z.string().trim().min(1).max(120),required:e.z.boolean().default(!1)}),m=l.extend({default_value:e.z.string().max(5e3).optional(),max_length:e.z.number().int().positive().max(1e4).optional(),min_length:e.z.number().int().min(0).max(1e4).optional(),placeholder:e.z.string().max(250).optional(),type:e.z.literal("text")}),u=l.extend({default_value:e.z.string().max(5e4).optional(),placeholder:e.z.string().max(250).optional(),type:e.z.literal("rich-text")}),p=l.extend({accept:e.z.array(e.z.string().trim().min(1).max(120)).max(20).optional(),default_value:e.z.strictObject({alt:e.z.string().max(300).optional(),file_name:e.z.string().trim().min(1).max(255).optional(),file_type:e.z.string().trim().min(1).max(120).optional(),height:e.z.number().int().positive().optional(),object_key:e.z.string().trim().min(1).max(1024),size_bytes:e.z.number().int().positive().optional(),url:e.z.string().trim().min(1).max(2048),width:e.z.number().int().positive().optional()}).optional(),max_bytes:e.z.number().int().positive().max(50*1024*1024).optional(),type:e.z.literal("image_r2")}),f=l.extend({default_value:e.z.union([e.z.string(),e.z.array(e.z.string()),e.z.null()]).optional(),display_column:e.z.string().trim().min(1).max(80).default("title"),filters:e.z.record(e.z.string(),e.z.unknown()).optional(),multiple:e.z.boolean().default(!1),table:e.z.enum(r),type:e.z.literal("db_relation"),value_column:e.z.string().trim().min(1).max(80).default("id")}),y=e.z.discriminatedUnion("type",[m,u,p,f]).describe("A NextBlock custom block field. Allowed types: text, rich-text, image_r2, db_relation."),s=e.z.lazy(()=>e.z.discriminatedUnion("type",[e.z.strictObject({as:d.optional(),children:e.z.array(s).max(200).default([]),className:c.optional(),type:e.z.literal("container")}),e.z.strictObject({as:d.optional(),className:c.optional(),column:e.z.string().trim().min(1).max(80).optional().describe("For a db_relation field, the related record column to display (e.g. title, price)."),emptyFallback:e.z.string().max(300).optional(),field_key:o.customBlockFieldKeySchema,type:e.z.literal("field_render")})])),_=e.z.strictObject({context:e.z.string().trim().max(3e3).optional(),modelId:e.z.string().trim().min(1).max(200).optional(),prompt:e.z.string().trim().min(3).max(4e3)});function g(t){return t.type==="field_render"?[t.field_key]:t.children.flatMap(i=>g(i))}function k(t,i){const n=new Set;t.fields.forEach((a,b)=>{n.has(a.key)&&i.addIssue({code:"custom",message:`Duplicate field key "${a.key}".`,path:["fields",b,"key"]}),n.add(a.key)});for(const a of g(t.layout_schema))n.has(a)||i.addIssue({code:"custom",message:`Layout references unknown field "${a}".`,path:["layout_schema"]})}const h=e.z.strictObject({description:e.z.string().trim().max(1e3).default(""),fields:e.z.array(y).min(1).max(80),is_original:e.z.boolean().default(!0),layout_schema:s,name:e.z.string().trim().min(1).max(160),slug:o.customBlockSlugSchema.describe("Lowercase kebab-case slug.")}).superRefine(k).describe("A complete NextBlock custom block definition stored as database JSONB.");function x(t){const i=h.parse(t);return o.customBlockDefinitionCreateSchema.parse({...i,is_original:!0})}function z(){return["You are NextBlock Cortex, an expert web platform engineer building database-rendered custom CMS widgets.","Return ONLY one clean raw JSON object with the exact structure described in the user message. Do not include markdown fences, comments, prose, or explanatory text.","Never emit TSX, JSX, React components, JavaScript, CSS blocks, style attributes, script tags, or runtime code.","Use only these field types: text, rich-text, image_r2, db_relation.",`Use db_relation.table only from this allowlist: ${r.join(", ")}.`,"Use lowercase kebab-case for slug and lowercase snake_case for field keys.","Build layout_schema as a self-referential tree: container nodes may contain nested container or field_render nodes to any needed depth.","Use Tailwind utility classes in className strings. Use responsive utilities where helpful.",'The "as" property of any node MUST be exactly one of: article, aside, blockquote, div, figure, figcaption, h2, h3, img, p, section, span. Never use a, button, ul, ol, li, table, or any other tag. For a call-to-action or "more info" button, use a span or p styled with button-like Tailwind classes (rounded, padded, colored background).',"Every field_render.field_key must match one field key exactly.","For relation fields, set value_column to id and set display_column to a column that actually exists on the chosen table: use title for pages, posts, and products; sku for product_variants; full_name for profiles; name for categories and languages; file_name for media. Do not invent display columns.",`Entity images: when a block displays an image that belongs to a related product, page, or post (for example a product card photo or a post thumbnail), do NOT add an image_r2 upload field for it. Instead add a single db_relation field to that table (products, product_variants, pages, or posts) and add a field_render node that references it with "as": "img". The renderer automatically resolves the related record's primary image — a product or variant main_image/object_key, or a page/post feature image — so keep the table's normal display_column (for example title for a products relation).`,'You may reference the same db_relation field from more than one field_render node: for example one node with "as": "img" for the image and another text node for its title, plus the relation value to drive a "more info" link. This builds a product/page/post card from a single relation field.',`To display a SPECIFIC column of a related record (its title, price, sku, etc.), set the field_render node's "column" property to that column name. The "column" overrides the field display_column for that one node, so a single product relation can show its image (as "img"), title (column "title"), and price (column "price") from three field_render nodes.`,'Available record columns by table — only reference these in a node "column": products: title, sku, price, sale_price, stock, short_description, slug, status; product_variants: sku, price, sale_price, stock_quantity; pages: title, slug, status; posts: title, slug, excerpt, subtitle; profiles: full_name; categories: name, slug, description; media: file_name; languages: name, code. Use "as": "img" (no column) to show a record image.','Do NOT create a standalone text field for data that lives on a related record. For example, a product price must come from a products db_relation field rendered with column "price" — never a separate "text" field the editor types by hand.',"Monetary columns (price, sale_price, price_adjustment) are stored in integer cents and are automatically formatted as currency on display, so reference them directly; never multiply, divide, or add currency symbols yourself.","Only use image_r2 for standalone images uploaded directly by the editor that are not tied to any database record (for example a decorative banner or icon). Only use text or rich-text fields for free-form copy the editor writes, not for values that exist on a related record."].join(" ")}function v(t){return["Create a NextBlock custom block definition for this request:",t.prompt,t.context?`Additional CMS context:
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("@nextblock-cms/utils/custom-blocks");require("./zod-config.cjs.js");const e=require("zod"),r=["pages","posts","products","media","categories","profiles","languages"],d=e.z.enum(["article","aside","blockquote","div","figure","figcaption","h2","h3","img","p","section","span"]).describe("A safe semantic element supported by the dynamic layout renderer."),c=e.z.string().trim().max(4e3).describe("Tailwind utility classes only. Do not include CSS, style tags, or JavaScript."),l=e.z.strictObject({description:e.z.string().trim().max(500).optional(),key:o.customBlockFieldKeySchema.describe("Lowercase snake_case field key."),label:e.z.string().trim().min(1).max(120),required:e.z.boolean().default(!1)}),m=l.extend({default_value:e.z.string().max(5e3).optional(),max_length:e.z.number().int().positive().max(1e4).optional(),min_length:e.z.number().int().min(0).max(1e4).optional(),placeholder:e.z.string().max(250).optional(),type:e.z.literal("text")}),u=l.extend({default_value:e.z.string().max(5e4).optional(),placeholder:e.z.string().max(250).optional(),type:e.z.literal("rich-text")}),p=l.extend({accept:e.z.array(e.z.string().trim().min(1).max(120)).max(20).optional(),default_value:e.z.strictObject({alt:e.z.string().max(300).optional(),file_name:e.z.string().trim().min(1).max(255).optional(),file_type:e.z.string().trim().min(1).max(120).optional(),height:e.z.number().int().positive().optional(),object_key:e.z.string().trim().min(1).max(1024),size_bytes:e.z.number().int().positive().optional(),url:e.z.string().trim().min(1).max(2048),width:e.z.number().int().positive().optional()}).optional(),max_bytes:e.z.number().int().positive().max(50*1024*1024).optional(),type:e.z.literal("image_r2")}),f=l.extend({default_value:e.z.union([e.z.string(),e.z.array(e.z.string()),e.z.null()]).optional(),display_column:e.z.string().trim().min(1).max(80).default("title"),filters:e.z.record(e.z.string(),e.z.unknown()).optional(),multiple:e.z.boolean().default(!1),table:e.z.enum(r),type:e.z.literal("db_relation"),value_column:e.z.string().trim().min(1).max(80).default("id")}),y=e.z.discriminatedUnion("type",[m,u,p,f]).describe("A NextBlock custom block field. Allowed types: text, rich-text, image_r2, db_relation."),s=e.z.lazy(()=>e.z.discriminatedUnion("type",[e.z.strictObject({as:d.optional(),children:e.z.array(s).max(200).default([]),className:c.optional(),type:e.z.literal("container")}),e.z.strictObject({as:d.optional(),className:c.optional(),column:e.z.string().trim().min(1).max(80).optional().describe("For a db_relation field, the related record column to display (e.g. title, price)."),emptyFallback:e.z.string().max(300).optional(),field_key:o.customBlockFieldKeySchema,type:e.z.literal("field_render")})])),_=e.z.strictObject({context:e.z.string().trim().max(24e3).optional(),modelId:e.z.string().trim().min(1).max(200).optional(),prompt:e.z.string().trim().min(3).max(4e3)});function g(t){return t.type==="field_render"?[t.field_key]:t.children.flatMap(i=>g(i))}function k(t,i){const n=new Set;t.fields.forEach((a,b)=>{n.has(a.key)&&i.addIssue({code:"custom",message:`Duplicate field key "${a.key}".`,path:["fields",b,"key"]}),n.add(a.key)});for(const a of g(t.layout_schema))n.has(a)||i.addIssue({code:"custom",message:`Layout references unknown field "${a}".`,path:["layout_schema"]})}const h=e.z.strictObject({description:e.z.string().trim().max(1e3).default(""),fields:e.z.array(y).min(1).max(80),is_original:e.z.boolean().default(!0),layout_schema:s,name:e.z.string().trim().min(1).max(160),slug:o.customBlockSlugSchema.describe("Lowercase kebab-case slug.")}).superRefine(k).describe("A complete NextBlock custom block definition stored as database JSONB.");function x(t){const i=h.parse(t);return o.customBlockDefinitionCreateSchema.parse({...i,is_original:!0})}function z(){return["You are NextBlock Cortex, an expert web platform engineer building database-rendered custom CMS widgets.","Return ONLY one clean raw JSON object with the exact structure described in the user message. Do not include markdown fences, comments, prose, or explanatory text.","Never emit TSX, JSX, React components, JavaScript, CSS blocks, style attributes, script tags, or runtime code.","Use only these field types: text, rich-text, image_r2, db_relation.",`Use db_relation.table only from this allowlist: ${r.join(", ")}.`,"Use lowercase kebab-case for slug and lowercase snake_case for field keys.","Build layout_schema as a self-referential tree: container nodes may contain nested container or field_render nodes to any needed depth.","Use Tailwind utility classes in className strings. Use responsive utilities where helpful.",'The "as" property of any node MUST be exactly one of: article, aside, blockquote, div, figure, figcaption, h2, h3, img, p, section, span. Never use a, button, ul, ol, li, table, or any other tag. For a call-to-action or "more info" button, use a span or p styled with button-like Tailwind classes (rounded, padded, colored background).',"Every field_render.field_key must match one field key exactly.","For relation fields, set value_column to id and set display_column to a column that actually exists on the chosen table: use title for pages, posts, and products; sku for product_variants; full_name for profiles; name for categories and languages; file_name for media. Do not invent display columns.",`Entity images: when a block displays an image that belongs to a related product, page, or post (for example a product card photo or a post thumbnail), do NOT add an image_r2 upload field for it. Instead add a single db_relation field to that table (products, product_variants, pages, or posts) and add a field_render node that references it with "as": "img". The renderer automatically resolves the related record's primary image — a product or variant main_image/object_key, or a page/post feature image — so keep the table's normal display_column (for example title for a products relation).`,'You may reference the same db_relation field from more than one field_render node: for example one node with "as": "img" for the image and another text node for its title, plus the relation value to drive a "more info" link. This builds a product/page/post card from a single relation field.',`To display a SPECIFIC column of a related record (its title, price, sku, etc.), set the field_render node's "column" property to that column name. The "column" overrides the field display_column for that one node, so a single product relation can show its image (as "img"), title (column "title"), and price (column "price") from three field_render nodes.`,'Available record columns by table — only reference these in a node "column": products: title, sku, price, sale_price, stock, short_description, slug, status; product_variants: sku, price, sale_price, stock_quantity; pages: title, slug, status; posts: title, slug, excerpt, subtitle; profiles: full_name; categories: name, slug, description; media: file_name; languages: name, code. Use "as": "img" (no column) to show a record image.','Do NOT create a standalone text field for data that lives on a related record. For example, a product price must come from a products db_relation field rendered with column "price" — never a separate "text" field the editor types by hand.',"Monetary columns (price, sale_price, price_adjustment) are stored in integer cents and are automatically formatted as currency on display, so reference them directly; never multiply, divide, or add currency symbols yourself.","Only use image_r2 for standalone images uploaded directly by the editor that are not tied to any database record (for example a decorative banner or icon). Only use text or rich-text fields for free-form copy the editor writes, not for values that exist on a related record."].join(" ")}function v(t){return["Create a NextBlock custom block definition for this request:",t.prompt,t.context?`Additional CMS context:
2
2
  ${t.context}`:null,["Return ONLY a JSON object with EXACTLY these top-level keys:",'- "name": string (human-friendly block name).','- "slug": lowercase kebab-case string.','- "description": short string.','- "is_original": true.','- "fields": a non-empty array of field objects. Each field is { "key": lowercase snake_case string, "label": string, "required": boolean, "type": one of "text" | "rich-text" | "image_r2" | "db_relation" }.',` For "db_relation" fields also include "table" (one of: ${r.join(", ")}), "display_column" (e.g. title, name, full_name, file_name, code), "value_column": "id", and "multiple": boolean.`,'- "layout_schema": a single root layout node (a tree). Every node is one of:',' container: { "type": "container", "as": an HTML tag like div/section/article/figure, "className": Tailwind utility classes, "children": array of nodes }.',' field render: { "type": "field_render", "field_key": one of the field keys above, "as": an HTML tag like p/span/img/h2/h3/div, "className": Tailwind utility classes, "emptyFallback": optional placeholder string }.',' Containers may nest other containers to any depth. Every field_render.field_key MUST match one of the fields. Render image_r2 fields with "as": "img".'].join(`
3
3
  `)].filter(Boolean).join(`
4
4
 
@@ -146,6 +146,16 @@ export type CortexWidgetLayoutNode = {
146
146
  type: 'field_render';
147
147
  };
148
148
  export declare const cortexWidgetLayoutNodeSchema: z.ZodType<CortexWidgetLayoutNode>;
149
+ /**
150
+ * `context` is sized for a serialized block definition, not just a style note.
151
+ *
152
+ * `update_custom_block` regenerates a block by feeding the *existing* definition
153
+ * back in as context, and a definition with a dozen fields plus its layout schema
154
+ * runs well past a few thousand characters — the old 3000 cap made editing any
155
+ * non-trivial block fail validation before it ever reached the model. The route
156
+ * that accepts this is ADMIN/WRITER-gated, so the cap is a sanity bound rather
157
+ * than an abuse control; `prompt` still carries the tighter limit.
158
+ */
149
159
  export declare const cortexWidgetBuildRequestSchema: z.ZodObject<{
150
160
  context: z.ZodOptional<z.ZodString>;
151
161
  modelId: z.ZodOptional<z.ZodString>;
@@ -82,7 +82,7 @@ const l = [
82
82
  })
83
83
  ])
84
84
  ), T = e.strictObject({
85
- context: e.string().trim().max(3e3).optional(),
85
+ context: e.string().trim().max(24e3).optional(),
86
86
  modelId: e.string().trim().min(1).max(200).optional(),
87
87
  prompt: e.string().trim().min(3).max(4e3)
88
88
  });
@@ -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",list_media:"read",list_product_categories:"read",list_site_script_revisions:"read",list_site_scripts:"read",list_site_themes:"read",manage_language:"write",manage_product_category:"write",manage_product_variants:"write",manage_site_script:"write",manage_site_theme:"write",prepare_delete_cms_item:"read",publish_content_draft:"write",read_current_cms_item:"read",read_database_records:"read",revert_site_script:"write",rewrite_page_draft:"write",search_documentation:"read",search_stock_photos:"read",set_content_images:"write",translate_content_bulk:"write",translate_page:"write",update_cms_item_field:"write",upload_media:"write",update_content_block:"write",update_current_cms_fields:"write",update_custom_block:"write",update_footer:"write",update_global_css:"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 r=b(t);return r?r==="read"?e.length>0:e.includes("write"):!1}function _(e){return e.split("_").map((r,o)=>o===0?r.charAt(0).toUpperCase()+r.slice(1):r).join(" ")}function m(e){const t={additionalProperties:!1,type:"object"};if(!e||typeof e!="object")return t;try{const r=w.z.toJSONSchema(e,{cycles:"ref",io:"input",reused:"inline",target:"draft-2020-12",unrepresentable:"any"});return delete r.$schema,r.type==="object"?r:t}catch{return t}}function l(e){return f.createCortexGlobalAgentTools(e)}function C(e){const t=Object.keys(l(e)),r=Object.keys(i);return{missingFromRegistry:r.filter(o=>!t.includes(o)),unclassified:t.filter(o=>!r.includes(o))}}function T(e){const t=l(e.context),r=[];for(const[o,n]of Object.entries(t))!(o in i)||!c(e.scopes,o)||r.push({description:n.description??`Cortex AI ${_(o)} tool.`,inputSchema:m(n.inputSchema),name:o,title:_(o)});for(const[o,n]of Object.entries(s)){const a=t[n.canonical];!a||!c(e.scopes,o)||r.push({description:n.description,inputSchema:m(a.inputSchema),name:o,title:n.title})}return r.sort((o,n)=>o.name.localeCompare(n.name))}function h(e,t){return{content:[{text:e,type:"text"}],isError:t}}function v(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 S(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 o=l(e.context)[t];if(!o||typeof o.execute!="function")throw new p(e.name);const n=e.args&&typeof e.args=="object"?e.args:{};if(o.inputSchema&&typeof o.inputSchema.safeParse=="function"){const a=o.inputSchema.safeParse(n);if(!a.success)return h(`Invalid arguments for "${e.name}": ${v(a.error)}`,!0)}try{const a=await o.execute(n),u=JSON.stringify(a??{success:!0},null,2),d=a&&typeof a=="object"&&!Array.isArray(a)?a:null,k=d?.success===!1;return{content:[{text:u,type:"text"}],isError:k,...d?{structuredContent:d}:{}}}catch(a){const u=a instanceof Error?a.message:String(a);return h(`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 r=await t.describe_database_schema?.execute?.({includeReadOnly:!0});return{mimeType:"application/json",text:JSON.stringify(r??{},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 r=await t.list_custom_blocks?.execute?.({});return{mimeType:"application/json",text:JSON.stringify(r??{},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(o=>o.name===e.name),r=R[e.name];return!t||!r?null:{description:t.description,messages:[{content:{text:r(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=S;exports.cortexMcpScopesAllow=c;exports.getCortexMcpPrompt=j;exports.getCortexMcpToolKind=b;exports.readCortexMcpResource=O;exports.resolveCortexMcpToolName=g;