@bettercms-ai/mcp 0.62.2 → 0.63.1

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
@@ -2730,7 +2730,7 @@ function toField(f) {
2730
2730
  };
2731
2731
  }
2732
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.";
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'). 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.";
2734
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.";
2735
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).";
2736
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";
@@ -2975,7 +2975,7 @@ function buildToolDefs(deps) {
2975
2975
  ]).describe("block type; section/slider/tabs/columns nest child blocks"),
2976
2976
  id: z.string().min(1).describe("stable unique block id"),
2977
2977
  props: z.record(z.string(), z.unknown()).describe(
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 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>"
2979
2979
  ),
2980
2980
  style: z.record(z.string(), z.unknown()).optional().describe(
2981
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."
@@ -3319,6 +3319,7 @@ function buildToolDefs(deps) {
3319
3319
  propUi: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe(PROP_UI_ARG_NOTE),
3320
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"),
3321
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"),
3322
3323
  override: z.boolean().optional().describe(OVERRIDE_NOTE),
3323
3324
  ifMatch: z.number().int().min(0).optional().describe(COMPONENT_IF_MATCH_NOTE)
3324
3325
  });
@@ -3948,7 +3949,7 @@ ${d.outline}` : "Proposed Content structure.", d);
3948
3949
  fieldsets: fieldsetsArg.nullable(),
3949
3950
  fieldUi: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe(FIELD_UI_ARG_NOTE),
3950
3951
  fieldFieldset: z.record(z.string(), z.string().min(1).nullable()).optional().describe("path \u2192 fieldset id, null takes it out"),
3951
- 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"),
3952
3953
  fieldLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("path \u2192 label"),
3953
3954
  fieldHelpText: z.record(z.string(), z.string().max(500).nullable()).optional().describe("path \u2192 help text, null clears it"),
3954
3955
  override: z.boolean().optional().describe(OVERRIDE_NOTE),
@@ -4169,7 +4170,7 @@ ${res.warnings.join("\n")}` : summary, res.data);
4169
4170
  def(
4170
4171
  "get_binding_report",
4171
4172
  "Check what on the live site is editable",
4172
- "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, or no longer the text it was matched on (`data-bcms-from`): 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.",
4173
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,
4174
4175
  async (c, a) => ok("Binding report.", await data(c, "GET", `/management/projects/current/binding-report${q({ slot: a.slot })}`))
4175
4176
  ),
@@ -5261,7 +5262,7 @@ ${lines.join("\n")}`, found);
5261
5262
  name: "update_component",
5262
5263
  config: {
5263
5264
  title: "Update a reusable component",
5264
- 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.",
5265
5266
  inputSchema: updateComponentInput.shape
5266
5267
  },
5267
5268
  handler: guard(
@@ -6219,10 +6220,12 @@ conversion is yours to write.
6219
6220
  load. Bind each item's fields on that item's OWN route (\`/blog/<slug>\`); on the index,
6220
6221
  render from the CMS and declare nothing.
6221
6222
  5b. **Where the content comes from at build time.** Two lanes, and they differ:
6222
- - GIT-CONNECTED (recommended for a converted site): the platform's provisioned workflow
6223
- writes \`bcms-content.json\` into the repo root before \`build\`, using the repo's
6224
- \`BCMS_API_KEY\` secret. Read that file, with in-code fallbacks so a local or CI build
6225
- 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.
6226
6229
  - ARCHIVE (\`deploy_project\` / \`deploy_from_upload\`): the sandbox build runs with no env
6227
6230
  and no network content step, by design. So the archive MUST SHIP its own
6228
6231
  \`bcms-content.json\` \u2014 generate it locally with a delivery key and commit it. Without
@@ -6399,7 +6402,8 @@ Importance orders a screen: required fields first, then array order.
6399
6402
  \`fieldLabel\`, \`fieldHelpText\` (keyed by dotted key path, \`'hero.title'\`) and \`fieldsets\`. A page's
6400
6403
  schema: the same call with \`pageId\` instead of \`modelId\`, and the version from \`get_page\`'s
6401
6404
  \`backingModel\`.
6402
- - 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.
6403
6407
  They write the DRAFT props; no publish is needed for the editor to show them.
6404
6408
  - Always send \`ifMatch\`, the \`optimisticVersion\` you read.
6405
6409
  - A NEW field carries its chrome in the same \`add_field\` / \`add_page_field\` call.
@@ -6523,7 +6527,7 @@ shape, then ask me, once and batched). Icon names are in bettercms://icons/dock.
6523
6527
  - fields that exist: \`update_content_model\` fieldUi / fieldFieldset / fieldOrder / fieldLabel /
6524
6528
  fieldHelpText by field path, plus \`fieldsets\`. A singleton page: send \`pageId\` instead of
6525
6529
  modelId, with its backingModel's version;
6526
- - component props: \`update_component\` propUi / propGroup / propLabel;
6530
+ - component props: \`update_component\` propUi / propGroup / propLabel / propHelpText;
6527
6531
  - a NEW field: ui / helpText / showIf in the same \`add_field\` / \`add_page_field\` call, with \`after\`.
6528
6532
  A 409 \`ui_human_owned\` names chrome a person set: leave it, or ask me and send \`override: true\`
6529
6533
  only on my word.