@bettercms-ai/mcp 0.62.1 → 0.63.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
@@ -2515,6 +2515,8 @@ var fieldType = z.enum([
2515
2515
  // `modular` needs config.blockSlugs; `location` takes no config.
2516
2516
  "modular",
2517
2517
  "location",
2518
+ // One button as one value: { label, href, variant?, size?, arrow?, icon?, newTab? }. A leaf.
2519
+ "button",
2518
2520
  // All LEAF types, so the pass-through branch above covers them. `document` is THE article
2519
2521
  // body — the rich document canvas, pages or collections, at most one per schema, top level
2520
2522
  // only. It was the conspicuous omission: the dashboard shipped an editor for it while no
@@ -2728,7 +2730,7 @@ function toField(f) {
2728
2730
  };
2729
2731
  }
2730
2732
  var BRAND_KIT_NOTE = "`brandKit` (colors, typography, radius and any typeScale/spacing/elevation/motion/graphics) is edited in the BetterCMS dashboard AND by set_brand_assets and set_brand_graphics; every write is a version a person can restore. ASSETS: a media asset OF THIS PROJECT by id; null clears one; a slot a person chose needs `replace: true`. GRAPHICS: `kind`, optional `angle`, 2-8 stops naming `colors.*` or `extras` keys \u2014 never a CSS string and never a hex. CONSUME, DO NOT COPY: a hosted page exposes `--brand-color-<key>`, `--brand-shadow-<key>` and `--brand-gradient-<key>`; style sections with their `style` tokens (`nav.backgroundGradient` and `footer.backgroundGradient` take a gradient key), never copied values. Never invent brand facts: read them from get_project.";
2731
- var UPDATE_MODEL_DESCRIPTION = "Edit a content model: rename it, change description/slug/urlPattern, set its `fieldsets`, or reshape how its EXISTING fields look in the editor. Never adds, removes or retypes a field (use add_field). Send modelId, or pageId for the model a page's schema lives in. `fieldsets` REPLACES the list (null drops all); a field joins one with fieldFieldset or add_field's `fieldsetId`. The field* args address fields by dotted key path ('hero.title'). A taken slug is 409 `slug_taken`. Section 14 of bettercms://playbook/schema.";
2733
+ var UPDATE_MODEL_DESCRIPTION = "Edit a content model: rename it, change description/slug/urlPattern, set its `fieldsets`, or reshape how its EXISTING fields look in the editor. Never adds, removes or retypes a field (use add_field). Send modelId, or pageId for the model a page's schema lives in. `fieldsets` REPLACES the list (null drops all); a field joins one with fieldFieldset or add_field's `fieldsetId`. The field* args address fields by dotted key path ('hero.title'). Read with get_content_model, then pass `ifMatch` = the version from that read. A taken slug is 409 `slug_taken`. Section 14 of bettercms://playbook/schema.";
2732
2734
  var MODEL_IF_MATCH_NOTE = "optimisticVersion from get_content_model (with pageId: get_page backingModel). The write applies only at that version; a 409 version-conflict means it changed since: read it again and re-apply, never retry blindly. REQUIRED with field*, override or a fieldset kind/tab/icon/collapsed (428 without); otherwise, omitted, the last write wins.";
2733
2735
  var COMPONENT_IF_MATCH_NOTE = "optimisticVersion from get_component. The write applies only at that version; a 409 version-conflict means it changed since: read it again and re-apply. REQUIRED with prop*, override or a prop ui icon/itemLabel/group (428 without).";
2734
2736
  var PROP_UI_ARG_NOTE = "top-level prop key \u2192 ui patch, the same vocabulary as a prop's `ui`: each key merges, a null key clears it, null clears the whole ui";
@@ -2973,7 +2975,7 @@ function buildToolDefs(deps) {
2973
2975
  ]).describe("block type; section/slider/tabs/columns nest child blocks"),
2974
2976
  id: z.string().min(1).describe("stable unique block id"),
2975
2977
  props: z.record(z.string(), z.unknown()).describe(
2976
- "per-type props: heading {text, level}; text/richtext {html} (NOT {text}), and both also take level 1-6, rendering the html AS that <hN> with inline marks kept (one line of inline copy only) \u2014 use it for a title that carries a styled span; image {src, alt}; button {text, href, variant? primary|secondary|tertiary|brand|dark|light|outline|link, size? sm|md|lg, fullWidth?, arrow? boolean, icon? e.g. mail|download|play}; spacer {height}; video {url}; form {formId}; component {componentId, overrides?}; navbar {links:[{label,href}], logo?, cta?}; footer {columns, copyright?}; section {children: block[]}; columns {columns: block[][], gap} \u2014 a column may NOT hold columns/section/slider/tabs; slider {slides:[{id,children}]}; tabs {tabs:[{id,label,children}]}; collection {cardComponentId?, detailComponentId?, titleField?, excerptField?, limit?, order?, emptyText?} \u2014 lists this page's published entries as cards, and renders ONE entry on /<page>/<entrySlug>"
2978
+ "per-type props: heading {text, level}; text/richtext {html} (NOT {text}), and both also take level 1-6, rendering the html AS that <hN> with inline marks kept (one line of inline copy only) \u2014 use it for a title that carries a styled span; image {src, alt}; button {text, href, variant? primary|secondary|tertiary|brand|dark|light|outline|link, size? sm|md|lg, fullWidth?, arrow? boolean, icon? ui.icon name, newTab? boolean}; spacer {height}; video {url}; form {formId}; component {componentId, overrides?}; navbar {links:[{label,href}], logo?, cta?}; footer {columns, copyright?}; section {children: block[]}; columns {columns: block[][], gap} \u2014 a column may hold ANY block; slider {slides:[{id,children}]}; tabs {tabs:[{id,label,children}]}; collection {cardComponentId?, detailComponentId?, titleField?, excerptField?, limit?, order?, emptyText?} \u2014 lists this page's published entries as cards, and renders ONE entry on /<page>/<entrySlug>"
2977
2979
  ),
2978
2980
  style: z.record(z.string(), z.unknown()).optional().describe(
2979
2981
  "design tokens: theme, bg (none|surface|muted|accent|dark|custom), bgCustom hex, paddingTop/paddingBottom/paddingSides px, contentWidth (narrow|default|wide|full), align, corner, shadow, borderTop/borderBottom, border (all four sides, in the text colour). A real marketing band is a `section` block carrying bg + padding + contentWidth."
@@ -3317,6 +3319,7 @@ function buildToolDefs(deps) {
3317
3319
  propUi: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe(PROP_UI_ARG_NOTE),
3318
3320
  propGroup: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe("prop key \u2192 its ui.group ({id, label?, kind?, icon?, collapsed?}), null takes it out"),
3319
3321
  propLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("prop key \u2192 label"),
3322
+ propHelpText: z.record(z.string(), z.string().max(200).nullable()).optional().describe("prop key \u2192 help text, null clears it"),
3320
3323
  override: z.boolean().optional().describe(OVERRIDE_NOTE),
3321
3324
  ifMatch: z.number().int().min(0).optional().describe(COMPONENT_IF_MATCH_NOTE)
3322
3325
  });
@@ -3946,7 +3949,7 @@ ${d.outline}` : "Proposed Content structure.", d);
3946
3949
  fieldsets: fieldsetsArg.nullable(),
3947
3950
  fieldUi: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe(FIELD_UI_ARG_NOTE),
3948
3951
  fieldFieldset: z.record(z.string(), z.string().min(1).nullable()).optional().describe("path \u2192 fieldset id, null takes it out"),
3949
- fieldOrder: z.strictObject({ parentPath: z.string().min(1).optional(), order: z.array(z.string().min(1)).min(1).max(200) }).optional().describe("the full new order of one parent's child keys (top level without parentPath)"),
3952
+ fieldOrder: z.strictObject({ parentPath: z.string().min(1).optional(), order: z.array(z.string().min(1)).min(1).max(200) }).optional().describe("the full new order of one parent's child keys (top level without parentPath). An array with both zones needs 'KEY.zones.nonRepeatable' or 'KEY.zones.repeatable'; its bare key is a 400"),
3950
3953
  fieldLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("path \u2192 label"),
3951
3954
  fieldHelpText: z.record(z.string(), z.string().max(500).nullable()).optional().describe("path \u2192 help text, null clears it"),
3952
3955
  override: z.boolean().optional().describe(OVERRIDE_NOTE),
@@ -4167,7 +4170,7 @@ ${res.warnings.join("\n")}` : summary, res.data);
4167
4170
  def(
4168
4171
  "get_binding_report",
4169
4172
  "Check what on the live site is editable",
4170
- "The receipt for 'is this site actually EDITABLE?'. Each release scans the built HTML for the element rendering each CMS field value; per slot this returns `mode` ('text-match' = guessed from rendered text, 'declared' = template-declared), what was bound, and `unmatched`: each path with its reason \u2014 not-declared, ambiguous-text, no-element, declared-only, needs-wrap, refused, layout-owned, page-owned, layout-locator-stale, locale-unvarianted, svg-unavailable, report-outdated. DEPLOY FIRST: before any release there is no report (pages 0, mode null, refreshRequired true). `refreshReason`: `never-generated` \u2014 only a release BetterCMS builds and serves writes a report, so a HEADLESS site never gets one, redeploy or not; `outdated` \u2014 the next release rewrites it. It certifies only that every non-empty field has SOME element carrying its path. `unaddressable` is the copy it cannot see: visible live text no field owns, by route and bucket, biggest first (null = unmeasured: diff visible text against entry values). Over `count / visible > 0.02`, fix it in the SOURCE (wrap the run in an element the codemod can bind, or declare `<BcmsField path=\u2026>`), deploy, re-read \u2014 playbook \xA711. `builtRoutes`: routes the live build has HTML for (null = cannot say). `layoutIncomplete`: Layout paths the last content apply could not write (their publish waits); `layoutUnread`: Layout fields no route reads (`layoutUnreadJudged` false = not judged). `canvas.lane`: the editor's live-preview lane (`bridge`, `draft-route`; `none` \u2192 get_next_steps). `coverage.error` `nothing-bound`: no element of this build is bound, so its percentage describes another site.",
4173
+ "The receipt for 'is this site actually EDITABLE?'. Each release scans the built HTML for the element rendering each CMS field value; per slot this returns `mode` ('text-match' = guessed from rendered text, 'declared' = template-declared), what was bound, and `unmatched`: each path with its reason \u2014 not-declared, ambiguous-text, no-element, declared-only, needs-wrap, refused, layout-owned, page-owned, layout-locator-stale, locale-unvarianted, svg-unavailable, partial and unstamped (a page list the content apply left as built: `kind` `list <bound>/<total>`), mismatch (a text-match stamp whose element shows MORE than the value: never written), report-outdated. DEPLOY FIRST: before any release there is no report (pages 0, mode null, refreshRequired true). `refreshReason`: `never-generated` \u2014 only a release BetterCMS builds and serves writes a report, so a HEADLESS site never gets one, redeploy or not; `outdated` \u2014 the next release rewrites it. It certifies only that every non-empty field has SOME element carrying its path. `unaddressable` is the copy it cannot see: visible live text no field owns, by route and bucket, biggest first (null = unmeasured: diff visible text against entry values). Over `count / visible > 0.02`, fix it in the SOURCE (wrap the run in an element the codemod can bind, or declare `<BcmsField path=\u2026>`), deploy, re-read \u2014 playbook \xA711. `builtRoutes`: routes the live build has HTML for (null = cannot say). `layoutIncomplete`: Layout paths the last content apply could not write (their publish waits); `layoutUnread`: Layout fields no route reads (`layoutUnreadJudged` false = not judged). `canvas.lane`: the editor's live-preview lane (`bridge`, `draft-route`; `none` \u2192 get_next_steps). `coverage.error` `nothing-bound`: no element of this build is bound, so its percentage describes another site.",
4171
4174
  z.object({ slot: z.enum(["current", "staging"]).optional().describe("which release tree to read \u2014 everything here, `unaddressable` included, is measured PER SLOT, so on a promote-gated project pass 'current' to read the tree the editor frames. Defaults to the slot this project's releases land in. The response's `sha` is the build the report describes; `refreshRequired` is true when that is not the build this slot serves.") }).shape,
4172
4175
  async (c, a) => ok("Binding report.", await data(c, "GET", `/management/projects/current/binding-report${q({ slot: a.slot })}`))
4173
4176
  ),
@@ -5259,7 +5262,7 @@ ${lines.join("\n")}`, found);
5259
5262
  name: "update_component",
5260
5263
  config: {
5261
5264
  title: "Update a reusable component",
5262
- description: "Update a component by id \u2014 blockJson, props, name, or category. Read get_component first. Passing `blockJson`/`props` REPLACES them. This writes the DRAFT: call publish_component to push the change live.",
5265
+ description: "Update a component by id \u2014 blockJson, props, name, or category. Read with get_component, then pass `ifMatch` = the version from that read. Passing `blockJson`/`props` REPLACES them. This writes the DRAFT: call publish_component to push the change live.",
5263
5266
  inputSchema: updateComponentInput.shape
5264
5267
  },
5265
5268
  handler: guard(
@@ -6217,10 +6220,12 @@ conversion is yours to write.
6217
6220
  load. Bind each item's fields on that item's OWN route (\`/blog/<slug>\`); on the index,
6218
6221
  render from the CMS and declare nothing.
6219
6222
  5b. **Where the content comes from at build time.** Two lanes, and they differ:
6220
- - GIT-CONNECTED (recommended for a converted site): the platform's provisioned workflow
6221
- writes \`bcms-content.json\` into the repo root before \`build\`, using the repo's
6222
- \`BCMS_API_KEY\` secret. Read that file, with in-code fallbacks so a local or CI build
6223
- without it still renders.
6223
+ - GIT-CONNECTED (recommended for a converted site): the platform's build writes
6224
+ \`bcms-content.json\` into the repo root before \`build\`, with a key it mints per build: no
6225
+ repo secret feeds it. (The secret a connect sets, \`BCMS_API_KEY_<RUNG>\` such as
6226
+ \`BCMS_API_KEY_PROD\`, is the validation workflows' key; they fall back to the shared
6227
+ \`BCMS_API_KEY\` only for a repo whose last connect predates it.) Read that file, with in-code
6228
+ fallbacks so a local or CI build without it still renders.
6224
6229
  - ARCHIVE (\`deploy_project\` / \`deploy_from_upload\`): the sandbox build runs with no env
6225
6230
  and no network content step, by design. So the archive MUST SHIP its own
6226
6231
  \`bcms-content.json\` \u2014 generate it locally with a delivery key and commit it. Without
@@ -6397,7 +6402,8 @@ Importance orders a screen: required fields first, then array order.
6397
6402
  \`fieldLabel\`, \`fieldHelpText\` (keyed by dotted key path, \`'hero.title'\`) and \`fieldsets\`. A page's
6398
6403
  schema: the same call with \`pageId\` instead of \`modelId\`, and the version from \`get_page\`'s
6399
6404
  \`backingModel\`.
6400
- - A component's props: \`update_component\` with \`propUi\`, \`propGroup\`, \`propLabel\` (keyed by prop key).
6405
+ - A component's props: \`update_component\` with \`propUi\`, \`propGroup\`, \`propLabel\`, \`propHelpText\` (keyed by prop key).
6406
+ Name props from the code (\`plan.cents\` \u2192 Cents), help text as \`e.g. "49"\`; repeated cards are one \`table\` prop.
6401
6407
  They write the DRAFT props; no publish is needed for the editor to show them.
6402
6408
  - Always send \`ifMatch\`, the \`optimisticVersion\` you read.
6403
6409
  - A NEW field carries its chrome in the same \`add_field\` / \`add_page_field\` call.
@@ -6521,7 +6527,7 @@ shape, then ask me, once and batched). Icon names are in bettercms://icons/dock.
6521
6527
  - fields that exist: \`update_content_model\` fieldUi / fieldFieldset / fieldOrder / fieldLabel /
6522
6528
  fieldHelpText by field path, plus \`fieldsets\`. A singleton page: send \`pageId\` instead of
6523
6529
  modelId, with its backingModel's version;
6524
- - component props: \`update_component\` propUi / propGroup / propLabel;
6530
+ - component props: \`update_component\` propUi / propGroup / propLabel / propHelpText;
6525
6531
  - a NEW field: ui / helpText / showIf in the same \`add_field\` / \`add_page_field\` call, with \`after\`.
6526
6532
  A 409 \`ui_human_owned\` names chrome a person set: leave it, or ask me and send \`override: true\`
6527
6533
  only on my word.