@bettercms-ai/mcp 0.74.0 → 0.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2936,6 +2936,19 @@ function listsTool(name, sets) {
2936
2936
  if (ABM_TOOLS.has(name)) return sets.has("abm");
2937
2937
  return !sets.has("core") || !LISTING_TAIL.has(name);
2938
2938
  }
2939
+ var ALWAYS_LOADED = /* @__PURE__ */ new Set([
2940
+ "get_next_steps",
2941
+ "editor_setup",
2942
+ "get_deploy_status",
2943
+ "get_conversion_brief",
2944
+ "get_binding_report",
2945
+ "list_projects",
2946
+ "list_pages",
2947
+ "get_page",
2948
+ "list_content_models"
2949
+ ]);
2950
+ var toolMeta = (name) => ALWAYS_LOADED.has(name) ? { "anthropic/alwaysLoad": true } : void 0;
2951
+ var TOOL_LOADING_INSTRUCTION = `TOOL SCHEMAS: a client with tool search lists most BetterCMS tools by name only. Before the first call to a tool whose schema you have not been shown, load it with the client's tool search, and load every tool the task needs in ONE search (for a renaming pass: update_field, update_content_model, update_component, list_components). A call made without the schema is refused by the client before it reaches BetterCMS ("Invalid tool parameters"): that is this, never a BetterCMS error, and never a reason to change the arguments.`;
2939
2952
 
2940
2953
  // src/tool-text.ts
2941
2954
  var ADD_FIELD_DESCRIPTION = "Append ONE field to a content model (`modelId`) or to a page's schema (`pageId`) \u2014 additive, never overwrites; a key already in use is refused. For a section/zone add ONE 'group' (fixed block) or 'repeater' (repeating list) carrying its child `fields`, not the zone's inner fields as separate top-level fields. Pass `after` to insert it after an existing top-level field instead of at the end; `ui`, `helpText`, `showIf`, `placement` and `fieldsetId` declare how it presents in the editor \u2014 read them back with get_content_model or get_page. On a page a 'group' is stored as type 'array' with `config.zones.nonRepeatable`: entry and delivery data carry `<key>.nonRepeatable.<sub>`, while data-bcms-field paths are `<key>.<sub>`. Schema only \u2014 never places a section; use componentize_sections to turn a group into a section. `multiselect` stores a string[] of `config.options`; `icon` is a BetterCMS icon name or an uploaded icon. To change a field that exists, use update_field. V2 keys (object, openMode, advanced, helpMore, recommendedMax, config.scale): \xA714 of bettercms://playbook/schema.";
@@ -2991,6 +3004,8 @@ var PARAM_TEXT = {
2991
3004
  fromBuiltin: "a builtin:* id from list_components: copies it into the project; fields you send win",
2992
3005
  componentPreview: "a slot row's look: {title?, subtitle?, media?} TOP-LEVEL prop keys, fallback?, icon?; null clears. \xA73 of bettercms://playbook/schema",
2993
3006
  componentIcon: "the block icon, a dock icon name; null clears. \xA73 of bettercms://playbook/schema",
3007
+ componentTabs: "/kit tabs no top-level prop sits in: [{id,label,icon?,order?}]. \xA73 of bettercms://playbook/schema",
3008
+ componentKitEntry: "the bettercms://kit entry id its V2 dock UI follows (422 kit_entry_unknown otherwise); null clears. The setup gate's dock row needs one",
2994
3009
  describes: "for editors: what it is and holds; omitted: written for you",
2995
3010
  runtimeBundle: "instead of pageId: the server-app bundle cleanup",
2996
3011
  pageRef: "page id or path ('/de/about'); a bare slug only when one page has it",
@@ -3565,6 +3580,7 @@ function publishedLines(outcome, summary) {
3565
3580
  if (outcome.status !== "live") return [`${summary} Publish status: ${outcome.status}.`];
3566
3581
  return [`${summary} Published: ${outcome.live > 0 ? `${outcome.live} ${outcome.live === 1 ? "value" : "values"} live on the served page` : "live"}.`];
3567
3582
  }
3583
+ var DOCK_UI_NEXT = "Give every prop of this component its V2 ui (control, tab, group, collapsed, layout, preview, itemLabel, icon) by playbook \xA714 and the closest bettercms://kit entry: get_component, then ONE update_component with propUi, propTab and propHelpText (the field-ui prompt). The setup gate's dock row stays open until it is done.";
3568
3584
  function ok(summary, data) {
3569
3585
  return {
3570
3586
  content: [
@@ -4098,6 +4114,8 @@ function buildToolDefs(deps) {
4098
4114
  description: z.string().optional().describe(PARAM_TEXT.describes),
4099
4115
  preview: componentPreviewArg.optional(),
4100
4116
  icon: dockIconArg.nullable().optional().describe(PARAM_TEXT.componentIcon),
4117
+ tabs: z.array(z.object({ id: z.string(), label: z.string(), icon: z.string().optional(), order: z.number().int().optional() })).nullable().optional().describe(PARAM_TEXT.componentTabs),
4118
+ kitEntry: z.string().min(1).max(100).nullable().optional().describe(PARAM_TEXT.componentKitEntry),
4101
4119
  blockJson: z.array(blockObject).default([]).describe("the component's block tree"),
4102
4120
  props: z.array(componentPropObject).default([]).describe(
4103
4121
  "overridable fields. DECLARE A PROP \u2014 WITH A `target` \u2014 for every string, link and image a marketer will ever touch: only the leaves a prop targets are click-to-edit. Each one may carry `ui` to say how its control presents in the inspector \u2014 a collapsed group, what titles and thumbnails a table's rows, whether that table's rows can be dragged at all. 'button' is ONE unit {label, href, variant?, size?, arrow?, icon?, newTab?} on a button block's 'props', never a text prop plus a url prop. 'link' an href, 'date' YYYY-MM-DD, 'color' a hex, 'file' {url, name?}, 'icon' an icon name or an image, 'multiselect' a string[] from config.options (required). " + SLOT_MULTIPLE_NOTE + " " + PROP_V2_NOTE
@@ -4116,6 +4134,7 @@ function buildToolDefs(deps) {
4116
4134
  description: z.string().optional(),
4117
4135
  preview: componentPreviewArg.optional(),
4118
4136
  icon: dockIconArg.nullable().optional().describe(PARAM_TEXT.componentIcon),
4137
+ kitEntry: z.string().min(1).max(100).nullable().optional().describe(PARAM_TEXT.componentKitEntry),
4119
4138
  blockJson: z.array(blockObject).optional().describe("REPLACES the block tree"),
4120
4139
  props: z.array(componentPropObject).optional().describe(
4121
4140
  "REPLACES the prop array \u2014 include every prop you want to keep, WITH its stored `ui`, or that prop's authoring chrome is dropped along with the prop. Read get_component first."
@@ -6236,10 +6255,12 @@ ${lines.join("\n")}`, found);
6236
6255
  },
6237
6256
  handler: guard(
6238
6257
  async (args) => withClient(async (client) => {
6239
- const { component, replaced } = await client.extractComponent(args);
6258
+ const res = await client.extractComponent(args);
6259
+ const { component, replaced } = res;
6260
+ const kit = res.kit;
6240
6261
  return ok(
6241
6262
  `Created component '${component.name}' (id ${component.id}) and repointed ${replaced} sections to it. Page drafts changed; publish those pages to make it live.`,
6242
- component
6263
+ { ...component, ...kit ? { kit } : {}, nextAction: DOCK_UI_NEXT }
6243
6264
  );
6244
6265
  })
6245
6266
  )
@@ -6466,7 +6487,8 @@ function registerTools(server, deps, tools = process.env.BETTERCMS_MCP_TOOLS) {
6466
6487
  };
6467
6488
  const sets = toolsetsOf(tools);
6468
6489
  for (const def of buildToolDefs(withElicit).filter((d) => listsTool(d.name, sets))) {
6469
- server.registerTool(def.name, def.config, def.handler);
6490
+ const _meta = toolMeta(def.name);
6491
+ server.registerTool(def.name, _meta ? { ...def.config, _meta } : def.config, def.handler);
6470
6492
  }
6471
6493
  }
6472
6494
 
@@ -6499,6 +6521,10 @@ DERIVED by \`editor_setup\`, where the componentize lane in \xA710 reuses every
6499
6521
  mcp notes and avoid its pitfalls (tag rules below).
6500
6522
  Its props carry NO target: give each one a target
6501
6523
  into YOUR blockJson (\xA73) before create_component
6524
+ Every component names its kitEntry
6525
+ (bettercms://kit/entry/{id}); its ui follows that
6526
+ entry; override the match when it's wrong; exempt
6527
+ with no-kit-match only with a note
6502
6528
  Repeated visual region on a page? -> a COMPONENT with sectionType (a Section)
6503
6529
  ...and it comes in more than one look? -> siblings sharing that sectionType = VARIANTS
6504
6530
  A stack of mixed, reorderable content? -> kind:'block' models + a \`modular\` field
@@ -6579,8 +6605,9 @@ dashboard's prop editor renders. \`helpText\` is three-state: a string is yours,
6579
6605
  purpose (send it for a prop whose /kit schema has no description; nothing refills it), and omitted
6580
6606
  lets the platform write one. They are HINTS: nothing on the write path refuses a placement
6581
6607
  that leaves a required prop empty, so \`get_site_composition\` is what reports one.
6582
- A /kit schema's document-level \`preview\` \u2192 the component's \`preview\`: how ONE instance reads as a ROW in a parent's slot list. {title?, subtitle?, media?} each name a TOP-LEVEL prop key (422 \`preview_key_unknown\` otherwise), \`fallback?\` is a literal row label (\u2264 120) and \`icon?\` a dock icon name; null clears it.
6608
+ A /kit schema's document-level \`preview\` \u2192 the component's \`preview\`: how ONE instance reads as a ROW in a parent's slot list. {title?, subtitle?, media?} each name a TOP-LEVEL prop key or a dotted path into one (\`slides.0.heading\`; 422 \`preview_key_unknown\` otherwise), \`fallback?\` is a literal row label (\u2264 120) and \`icon?\` a dock icon name; null clears it.
6583
6609
  A /kit schema's document-level \`icon\` \u2192 the component's \`icon\` (a dock icon name from bettercms://icons/dock, null clears): the block icon on the root crumb, slot rows and slot child frames.
6610
+ A /kit schema's \`groups\` \u2192 each prop's \`ui.tab\` (defined on the first top-level prop in it; a nested field's \`group\` \u2192 its \`ui.tab: {id}\`); a group NO top-level prop sits in \u2192 the component's \`tabs\` ([{id, label, icon?, order?}], 422 \`component_tab_invalid\` for a prop's own tab). A slot's \`of.allow\` \u2192 \`config.allow\` (block names); a scalar list's item settings (\`of[0]\` validation, description, localized) \u2192 \`config.item\`.
6584
6611
 
6585
6612
  **Prop types, one rule each.** 'select' REQUIRES \`config.options\` (a default must be one of them). 'group'/'table'/'object' REQUIRE \`config.fields\` (\`[{key,label,type,options?,fields?}]\`); a group writes an object, a table an array of them (\`config.mode: 'data'\`: ONE V2 table value, no fields). 'number' takes optional \`config.min/max/step\`. 'slot' holds ONE nested component instance: \`target.path: 'props'\` on an EMPTY component block (\`props.componentId: ""\`), \`config.componentIds\` restricts fills, defaultValue \`{componentId, overrides?}\`. 'form' holds ONE Form document: \`target.path: 'props'\` on an EMPTY form block (\`props.formId: ""\`), defaultValue \`{formId}\`, never the hydrated \`{formId, form}\` (400); do NOT re-declare the form's FIELDS as props, the Form document owns them. 'button' is ONE unit \`{label, href, variant?, size?, arrow?, icon?, newTab?}\` on a button block's 'props', never a text prop plus a url prop. 'link' an href, 'date' YYYY-MM-DD, 'color' a hex, 'file' \`{url, name?}\`, 'icon' an icon name or an image, 'multiselect' a string[] from \`config.options\` (required).
6586
6613
 
@@ -7652,6 +7679,7 @@ disagrees with the array the moment anyone drags a row in the builder.
7652
7679
  // src/prompts.ts
7653
7680
  var STRUCTURE_RULE = `### Page structure (non-negotiable)
7654
7681
  ${SECTION_DOCTRINE}`;
7682
+ var DOCK_UI_STEP = "EDITOR CHROME: for every component, run the field-ui lane (the `field-ui` prompt, playbook \xA714): `get_component`, then one `update_component` with `propUi`, `propTab` and `propHelpText` giving each prop its V2 ui (control, tab, group, collapsed, layout, preview, itemLabel, icon) after the closest `bettercms://kit` entry. The gate's `dock` row stays open until each has it, or a true `default-controls` exception.";
7655
7683
  var CONVERT_SITE_TEXT = (projectId) => `Convert my site so every visible element is editable in BetterCMS${projectId ? `, project ${projectId}` : ""}.
7656
7684
  Do not stop at the plan or at a partial receipt \u2014 finish the whole site, never editing a class or style.
7657
7685
 
@@ -7682,8 +7710,8 @@ Do not stop at the plan or at a partial receipt \u2014 finish the whole site, ne
7682
7710
  do the same with \`npx @bettercms-ai/convert@latest --upgrade-element-styles --root . --dry-run\`. Run \`--verify-styles\` again
7683
7711
  and restore what it lists (the marker, and the inline style from \`block.style\` or \`fieldAttrs\`, are allowed; changing a class
7684
7712
  or an existing style is not). Deploy, poll, call \`get_next_steps\` again and run \`receipt.gate.nextAction\` until \`receipt.complete\` is true; what else it lists, tell me by name. Not listed: say so; invent no work. Its \`receipt.gate\` fails a route on \`sections\` or \`style\` while I could not add a section to that page, reorder it or space it: run each such row's \`fix\` too. A write that answers \`held\`, or a publish whose \`outcome\` is \`partial\`, is not done until its action is taken.
7685
- 11. Ask me whether to commit and push to my repository, commit only, or leave it uncommitted (unpushed, the
7686
- next rebuild from my repo drops it). Touch git only as I answer.`;
7713
+ 11. ${DOCK_UI_STEP}
7714
+ 12. Ask me whether to commit and push to my repository, commit only, or leave it uncommitted (unpushed, the next rebuild from my repo drops it). Touch git only as I answer.`;
7687
7715
  var FIELD_UI_PROMPT = {
7688
7716
  title: "Declare how fields LOOK in the editor (guided)",
7689
7717
  description: "Set the editor chrome of an existing schema and its component props: groups, tabs, rows, icons, item names, what titles and thumbnails a list's rows, help text, conditional visibility. Derives it from your source, then from the field shapes, and asks only what it cannot know.",
@@ -8115,6 +8143,8 @@ ${COMPONENTIZE_SITE_FLOW}
8115
8143
 
8116
8144
  ${STRUCTURE_RULE}
8117
8145
 
8146
+ Last, before the summary: ${DOCK_UI_STEP}
8147
+
8118
8148
  Confirm the component library with the user before creating it. End with the summary \xA712 asks
8119
8149
  for: the group hierarchy, each family and its variants, what you left in the site's code because
8120
8150
  the platform does not model it, and which components are still waiting for the owner's approval
@@ -8253,7 +8283,8 @@ Read bettercms://playbook/schema section 11 first, then work this order:
8253
8283
  4. PRESENTATION MANIFEST \u2014 add bcms-presentation.json to public/ (container width, type
8254
8284
  scale, nav position/background, footer surface as DTCG {"$type","$value"} entries) and
8255
8285
  redeploy, or structural draft previews render in a generic theme, not this site's design.
8256
- 5. VERIFY \u2014 call get_next_steps and fix what it lists (it knows about missing manifests,
8286
+ 5. ${DOCK_UI_STEP}
8287
+ 6. VERIFY \u2014 call get_next_steps and fix what it lists (it knows about missing manifests,
8257
8288
  a setup that has not run, and placed-but-unpublished components). You are finished only
8258
8289
  when its receipt.complete is true: run receipt.gate.nextAction, read it again after each
8259
8290
  batch, and record an item you cannot close with editor_setup { exempt } only under an allowed
@@ -8451,7 +8482,7 @@ function buildServer(deps) {
8451
8482
  // 🔴 The plain-language ask, routed — the stdio twin of the hosted connector's line
8452
8483
  // (src/routes/mcp/index.ts MCP_INSTRUCTIONS). "Make my site editable" has one recipe and
8453
8484
  // one definition of done; without this an agent converts the page it landed on and stops.
8454
- instructions: RECEIPT_FIRST_INSTRUCTION + " When the user asks to make a site or all of its pages editable, to convert it, or to bind its fields: this is playbook \xA713. Read `bettercms://playbook/schema` \xA713, call get_binding_report and get_conversion_brief { complete: true }, convert EVERY route the brief lists, and finish only when get_binding_report shows coverage.pending empty on every route \u2014 not when the first page works. On a workspace-wide connection pass projectId on every call; never ask the user to re-scope the connection. " + LAYOUT_CHROME_INSTRUCTION + " " + CTA_BUTTON_INSTRUCTION + " When the user asks to componentize the whole site, to turn every section into a component, or to build a component library from the site: this is playbook \xA712. Read `bettercms://playbook/schema` \xA712, start with get_site_composition, and use the batch tools \u2014 componentize_sections, compose_pages, update_layout with `commands`, publish_components \u2014 rather than one call per component. Finish with get_site_composition and tell the user what the platform does not model (cookie banners, modals, breadcrumbs, pagination) and which components still need the owner's approval in the dashboard before they can be published. " + // The default after authoring (the structure standard): organise what you made. Same
8485
+ instructions: TOOL_LOADING_INSTRUCTION + " " + RECEIPT_FIRST_INSTRUCTION + " When the user asks to make a site or all of its pages editable, to convert it, or to bind its fields: this is playbook \xA713. Read `bettercms://playbook/schema` \xA713, call get_binding_report and get_conversion_brief { complete: true }, convert EVERY route the brief lists, and finish only when get_binding_report shows coverage.pending empty on every route \u2014 not when the first page works. On a workspace-wide connection pass projectId on every call; never ask the user to re-scope the connection. " + LAYOUT_CHROME_INSTRUCTION + " " + CTA_BUTTON_INSTRUCTION + " When the user asks to componentize the whole site, to turn every section into a component, or to build a component library from the site: this is playbook \xA712. Read `bettercms://playbook/schema` \xA712, start with get_site_composition, and use the batch tools \u2014 componentize_sections, compose_pages, update_layout with `commands`, publish_components \u2014 rather than one call per component. Finish with get_site_composition and tell the user what the platform does not model (cookie banners, modals, breadcrumbs, pagination) and which components still need the owner's approval in the dashboard before they can be published. " + // The default after authoring (the structure standard): organise what you made. Same
8455
8486
  // sentence as the hosted connector's MCP_INSTRUCTIONS.
8456
8487
  STRUCTURE_DEFAULT_INSTRUCTION + " " + SKILLS_ROUTING_INSTRUCTION + " " + DOCK_UI_ROUTING_INSTRUCTION + " " + PUBLISH_HOLD_INSTRUCTION
8457
8488
  }