@bettercms-ai/mcp 0.74.0 → 0.75.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,7 @@ 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",
2994
3008
  describes: "for editors: what it is and holds; omitted: written for you",
2995
3009
  runtimeBundle: "instead of pageId: the server-app bundle cleanup",
2996
3010
  pageRef: "page id or path ('/de/about'); a bare slug only when one page has it",
@@ -3565,6 +3579,7 @@ function publishedLines(outcome, summary) {
3565
3579
  if (outcome.status !== "live") return [`${summary} Publish status: ${outcome.status}.`];
3566
3580
  return [`${summary} Published: ${outcome.live > 0 ? `${outcome.live} ${outcome.live === 1 ? "value" : "values"} live on the served page` : "live"}.`];
3567
3581
  }
3582
+ 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
3583
  function ok(summary, data) {
3569
3584
  return {
3570
3585
  content: [
@@ -4098,6 +4113,7 @@ function buildToolDefs(deps) {
4098
4113
  description: z.string().optional().describe(PARAM_TEXT.describes),
4099
4114
  preview: componentPreviewArg.optional(),
4100
4115
  icon: dockIconArg.nullable().optional().describe(PARAM_TEXT.componentIcon),
4116
+ 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),
4101
4117
  blockJson: z.array(blockObject).default([]).describe("the component's block tree"),
4102
4118
  props: z.array(componentPropObject).default([]).describe(
4103
4119
  "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
@@ -6239,7 +6255,7 @@ ${lines.join("\n")}`, found);
6239
6255
  const { component, replaced } = await client.extractComponent(args);
6240
6256
  return ok(
6241
6257
  `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
6258
+ { ...component, nextAction: DOCK_UI_NEXT }
6243
6259
  );
6244
6260
  })
6245
6261
  )
@@ -6466,7 +6482,8 @@ function registerTools(server, deps, tools = process.env.BETTERCMS_MCP_TOOLS) {
6466
6482
  };
6467
6483
  const sets = toolsetsOf(tools);
6468
6484
  for (const def of buildToolDefs(withElicit).filter((d) => listsTool(d.name, sets))) {
6469
- server.registerTool(def.name, def.config, def.handler);
6485
+ const _meta = toolMeta(def.name);
6486
+ server.registerTool(def.name, _meta ? { ...def.config, _meta } : def.config, def.handler);
6470
6487
  }
6471
6488
  }
6472
6489
 
@@ -6579,8 +6596,9 @@ dashboard's prop editor renders. \`helpText\` is three-state: a string is yours,
6579
6596
  purpose (send it for a prop whose /kit schema has no description; nothing refills it), and omitted
6580
6597
  lets the platform write one. They are HINTS: nothing on the write path refuses a placement
6581
6598
  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.
6599
+ 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
6600
  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.
6601
+ 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
6602
 
6585
6603
  **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
6604
 
@@ -7652,6 +7670,7 @@ disagrees with the array the moment anyone drags a row in the builder.
7652
7670
  // src/prompts.ts
7653
7671
  var STRUCTURE_RULE = `### Page structure (non-negotiable)
7654
7672
  ${SECTION_DOCTRINE}`;
7673
+ 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
7674
  var CONVERT_SITE_TEXT = (projectId) => `Convert my site so every visible element is editable in BetterCMS${projectId ? `, project ${projectId}` : ""}.
7656
7675
  Do not stop at the plan or at a partial receipt \u2014 finish the whole site, never editing a class or style.
7657
7676
 
@@ -7682,8 +7701,8 @@ Do not stop at the plan or at a partial receipt \u2014 finish the whole site, ne
7682
7701
  do the same with \`npx @bettercms-ai/convert@latest --upgrade-element-styles --root . --dry-run\`. Run \`--verify-styles\` again
7683
7702
  and restore what it lists (the marker, and the inline style from \`block.style\` or \`fieldAttrs\`, are allowed; changing a class
7684
7703
  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.`;
7704
+ 11. ${DOCK_UI_STEP}
7705
+ 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
7706
  var FIELD_UI_PROMPT = {
7688
7707
  title: "Declare how fields LOOK in the editor (guided)",
7689
7708
  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 +8134,8 @@ ${COMPONENTIZE_SITE_FLOW}
8115
8134
 
8116
8135
  ${STRUCTURE_RULE}
8117
8136
 
8137
+ Last, before the summary: ${DOCK_UI_STEP}
8138
+
8118
8139
  Confirm the component library with the user before creating it. End with the summary \xA712 asks
8119
8140
  for: the group hierarchy, each family and its variants, what you left in the site's code because
8120
8141
  the platform does not model it, and which components are still waiting for the owner's approval
@@ -8253,7 +8274,8 @@ Read bettercms://playbook/schema section 11 first, then work this order:
8253
8274
  4. PRESENTATION MANIFEST \u2014 add bcms-presentation.json to public/ (container width, type
8254
8275
  scale, nav position/background, footer surface as DTCG {"$type","$value"} entries) and
8255
8276
  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,
8277
+ 5. ${DOCK_UI_STEP}
8278
+ 6. VERIFY \u2014 call get_next_steps and fix what it lists (it knows about missing manifests,
8257
8279
  a setup that has not run, and placed-but-unpublished components). You are finished only
8258
8280
  when its receipt.complete is true: run receipt.gate.nextAction, read it again after each
8259
8281
  batch, and record an item you cannot close with editor_setup { exempt } only under an allowed
@@ -8451,7 +8473,7 @@ function buildServer(deps) {
8451
8473
  // 🔴 The plain-language ask, routed — the stdio twin of the hosted connector's line
8452
8474
  // (src/routes/mcp/index.ts MCP_INSTRUCTIONS). "Make my site editable" has one recipe and
8453
8475
  // 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
8476
+ 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
8477
  // sentence as the hosted connector's MCP_INSTRUCTIONS.
8456
8478
  STRUCTURE_DEFAULT_INSTRUCTION + " " + SKILLS_ROUTING_INSTRUCTION + " " + DOCK_UI_ROUTING_INSTRUCTION + " " + PUBLISH_HOLD_INSTRUCTION
8457
8479
  }