@bettercms-ai/mcp 0.76.5 → 0.77.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
@@ -2831,6 +2831,11 @@ function planFieldEdit(model, edit, callerIfMatch) {
2831
2831
  if (edit[k] === null && CLEARABLE.includes(k)) delete out[k];
2832
2832
  else out[k] = edit[k];
2833
2833
  }
2834
+ if (edit.ui && typeof edit.ui === "object") {
2835
+ const ui = mergeConfig(f.ui, edit.ui);
2836
+ if (Object.keys(ui).length > 0) out.ui = ui;
2837
+ else delete out.ui;
2838
+ }
2834
2839
  if (edit.config && typeof edit.config === "object") out = { ...out, config: mergeConfig(f.config, edit.config) };
2835
2840
  if (retype) out.type = edit.type;
2836
2841
  if (rename !== void 0) out.key = rename;
@@ -2981,7 +2986,7 @@ var NEW_TOOL_TEXT = {
2981
2986
  generate_image: "Generate an image with AI and add it to the Media Library (5 AI credits; free with the workspace's own Together key). `width`/`height` are 256-1440 in multiples of 16 (default 1024x768). Returns the new asset (id, url, altText): use the url as an 'image' field value. Always set `altText`.",
2982
2987
  refresh_search_index: "Tell search engines and site search about published changes; costs nothing. action 'indexnow' submits `urls` (this site's own, up to 1000) to IndexNow, or every change since the last submission when omitted. action 'reindex' rebuilds the project's site-search index. 503 means the index is unreachable: try again later.",
2983
2988
  import_redirects: "Bulk-create redirects for a migration, from `rows` or from `text` in `format` (our CSV export, a Netlify `_redirects` file, or a vercel.json). Call it with `dryRun: true` FIRST: nothing is written and each row comes back `would-create`, `existing` or `skipped` with a reason. Rows get create_redirect's rules. Max 5000.",
2984
- editor_setup: "Make the live build editable. A build imports only SEO/meta, media, brand and the models bcms-content.json declares; this derives the rest from the live release: pages, collections, forms, bindings, button and section components (when the authoring preference is components), Component Groups and the Content sidebar. Run it after an import or a rebuild, before the conversion brief. Idempotent: `ready` when it already ran for the live sha, else it starts (`running`; poll with `check: true` or get_deploy_status `editorSetup`). `state`: never or stale (run it), running, ready, failed. 409 = nothing is live yet: deploy first. Then get_next_steps.",
2989
+ editor_setup: "Make the live build editable: derives pages, collections, forms, bindings and components from the live release (skill bindings). Run it after an import or a rebuild, before the conversion brief. Idempotent: `ready` when it already ran for the live sha, else it starts (`running`; poll with `check: true`). `state`: never or stale (run it), running, ready, failed. 409 = nothing is live yet: deploy first. Then get_next_steps.",
2985
2990
  transform_image: "Edit an existing Media Library image with an instruction (e.g. 'remove the background', 'make it dusk'); the result is saved as a NEW asset and the original is kept (5 AI credits). Returns the new asset (id, url, altText).",
2986
2991
  write_alt_text: "Alt text for a Media Library image (mediaId) or an https image URL (url), written from its pixels (1 AI credit). A suggestion: save it with update_media."
2987
2992
  };
@@ -3012,6 +3017,7 @@ var PARAM_TEXT = {
3012
3017
  editorSetupForce: "re-run a setup already ready for the live build",
3013
3018
  editorSetupCheck: "true = only report the state; starts nothing",
3014
3019
  editorSetupExempt: "instead of running: record setup-gate exceptions [{ route, check, reason, note }]; reason null removes one. Reasons: get_next_steps `receipt.gate`",
3020
+ editorSetupDeclare: "instead of running: record binding decisions [{op, id, ...}], max 100, a result each; with `check: true` a dry run. Ops: skill bindings",
3015
3021
  pageBlockJson: "an ARRAY REPLACES the page's block composition (a `component` block per section). An OBJECT edits ONE section of the stored page instead: `insertSection` adds a component (its id, or a builtin:* id: copied into the project once) at index `at`, or `before` / `after` a section's block id (none = last), with its `style` and `overrides`; `sectionStyle` sets a section's block style (a key replaces the stored one, null unsets it). `section` in the answer names the block (skill publishing)",
3016
3022
  pageDynamic: "a dynamic (collection template) page only",
3017
3023
  previewPath: "its route pattern, e.g. /blog/{slug}; null clears",
@@ -4631,8 +4637,8 @@ ${d.outline}` : "Proposed Content structure.", d);
4631
4637
  def(
4632
4638
  "update_project",
4633
4639
  "Update the connected project",
4634
- "Update the connected project's settings \u2014 rename it, change its slug/description, SEO defaults, visibility, or `presentation`. Only the provided fields change. `presentation` themes the platform's OWN renderer (the one serving a site built in the CMS with no deployed build, and previewing structural drafts everywhere) with the same closed knobs as bcms-presentation.json \u2014 DTCG { $type, $value } tokens keyed like 'page.maxWidth', 'nav.background'. Without it that renderer is a generic 768px document theme. An invalid manifest is 422 PRESENTATION_INVALID.",
4635
- z.object({ name: z.string().optional(), slug: z.string().optional(), description: z.string().optional(), visibility: z.string().optional(), seoDefaults: z.record(z.string(), z.unknown()).optional(), presentation: z.record(z.string(), z.unknown()).optional().describe("theme knobs for the platform renderer, keyed like bcms-presentation.json") }).shape,
4640
+ "Update the connected project's settings \u2014 rename it, change its slug/description, SEO defaults, visibility, or `presentation`. Only the provided fields change. `subdomainAlias` renames the staging URL: 'astronapp' serves the site at astronapp.bettercms.site (3\u201355 lowercase letters, numbers, hyphens; 409 when taken; null goes back to the original). The original <handle>.bettercms.site keeps working; a replaced name is released. `presentation` themes the platform's OWN renderer (the one serving a site built in the CMS with no deployed build, and previewing structural drafts everywhere) with the same closed knobs as bcms-presentation.json \u2014 DTCG { $type, $value } tokens keyed like 'page.maxWidth', 'nav.background'. Without it that renderer is a generic 768px document theme. An invalid manifest is 422 PRESENTATION_INVALID.",
4641
+ z.object({ name: z.string().optional(), slug: z.string().optional(), description: z.string().optional(), visibility: z.string().optional(), seoDefaults: z.record(z.string(), z.unknown()).optional(), presentation: z.record(z.string(), z.unknown()).optional().describe("theme knobs for the platform renderer, keyed like bcms-presentation.json"), subdomainAlias: z.string().nullable().optional().describe("the staging name: 'astronapp' \u2192 astronapp.bettercms.site; null = back to the original") }).shape,
4636
4642
  async (c, a) => ok("Updated project.", await data(c, "PATCH", `/management/projects/current`, a))
4637
4643
  ),
4638
4644
  def(
@@ -4974,7 +4980,7 @@ ${d.outline}` : "Proposed Content structure.", d);
4974
4980
  required: z.boolean().optional(),
4975
4981
  config: z.record(z.string(), z.unknown()).optional().describe("per-type validation/config keys, merged into the stored config; null deletes a key"),
4976
4982
  showIf: z.record(z.string(), z.unknown()).nullable().optional().describe("same shape as on add_field; null clears it"),
4977
- ui: z.record(z.string(), z.unknown()).nullable().optional().describe("same vocabulary as on add_field; null clears it"),
4983
+ ui: z.record(z.string(), z.unknown()).nullable().optional().describe("same vocabulary as on add_field; merged per key, a null key or ui:null clears"),
4978
4984
  placement: z.enum(["cover", "title", "excerpt", "body", "inline", "panel"]).nullable().optional(),
4979
4985
  readOnly: z.union([z.literal(true), z.literal("developer")]).nullable().optional().describe(PARAM_TEXT.lockClears),
4980
4986
  lockReason: z.string().max(200).nullable().optional().describe(PARAM_TEXT.lockClears),
@@ -5510,11 +5516,13 @@ ${d.outline}` : "Proposed Content structure.", d);
5510
5516
  z.object({
5511
5517
  force: z.boolean().optional().describe(PARAM_TEXT.editorSetupForce),
5512
5518
  check: z.boolean().optional().describe(PARAM_TEXT.editorSetupCheck),
5513
- exempt: z.array(z.record(z.string(), z.unknown())).optional().describe(PARAM_TEXT.editorSetupExempt)
5519
+ exempt: z.array(z.record(z.string(), z.unknown())).optional().describe(PARAM_TEXT.editorSetupExempt),
5520
+ declare: z.array(z.record(z.string(), z.unknown())).optional().describe(PARAM_TEXT.editorSetupDeclare)
5514
5521
  }).shape,
5515
5522
  // The route answers `{ state, … }` with no `data` envelope, so the whole body is the result.
5516
5523
  // `exempt` records setup-gate exceptions instead of running anything (the gate's one write).
5517
- async (c, a) => a.exempt ? ok("Setup gate exceptions.", await c.fetchJSON(c.url(`/management/projects/setup-gate/exceptions`), { method: "POST", body: JSON.stringify({ exempt: a.exempt }) })) : ok("Editor setup.", await c.fetchJSON(c.url(`/management/projects/editor-setup`), a.check === true ? { method: "GET" } : { method: "POST", body: JSON.stringify({ force: a.force }) }))
5524
+ // `declare` records binding decisions; with `check: true` it is a dry run (the body's `dryRun`).
5525
+ async (c, a) => a.exempt ? ok("Setup gate exceptions.", await c.fetchJSON(c.url(`/management/projects/setup-gate/exceptions`), { method: "POST", body: JSON.stringify({ exempt: a.exempt }) })) : a.declare ? ok("Binding decisions.", await c.fetchJSON(c.url(`/management/projects/binding-decisions`), { method: "POST", body: JSON.stringify({ declare: a.declare, ...a.check === true ? { dryRun: true } : {} }) })) : ok("Editor setup.", await c.fetchJSON(c.url(`/management/projects/editor-setup`), a.check === true ? { method: "GET" } : { method: "POST", body: JSON.stringify({ force: a.force }) }))
5518
5526
  ),
5519
5527
  // ── Deploy to the user's own host (parity with remote /mcp) ──
5520
5528
  // A person connects Vercel or Netlify in the dashboard; nothing here touches OAuth or a host
@@ -6528,8 +6536,8 @@ DERIVED by \`editor_setup\`, where the componentize lane in \xA710 reuses every
6528
6536
  (bettercms://kit/entry/{id}). The import's entry
6529
6537
  is only a SUGGESTION: check its htmlSignals and
6530
6538
  pitfalls, confirm or replace it (update_component
6531
- kitEntry rebuilds the derived ui); exempt with
6532
- no-kit-match only with a note
6539
+ kitEntry rebuilds the derived ui and carries its
6540
+ rules; a gate.kit drift row lists rules still missing); exempt with no-kit-match or kit-drift-accepted only with a note
6533
6541
  Repeated visual region on a page? -> a COMPONENT with sectionType (a Section)
6534
6542
  ...and it comes in more than one look? -> siblings sharing that sectionType = VARIANTS
6535
6543
  A stack of mixed, reorderable content? -> kind:'block' models + a \`modular\` field
@@ -7858,8 +7866,8 @@ Read \`bettercms://playbook/schema\` \xA712 first \u2014 this is the short form
7858
7866
  Use Global, Navigation, Actions, Content, Forms, Media, Sections, Page-specific. \`category\`
7859
7867
  is the separate picker TAB.
7860
7868
  4. **Fields** = \`props\`: text, richtext, image, url, boolean, number, select, group, table, slot.
7861
- Always a \`defaultValue\`; \`required\` / \`helpText\` / \`placeholder\` are editor hints, not write
7862
- -time enforcement. \`select\` for styling-only variants, a separate component when the markup
7869
+ Always a \`defaultValue\`; \`helpText\` / \`placeholder\` are editor hints. A placement you SET empty on a
7870
+ \`required\` prop is refused (\`required_value\`). \`select\` for styling-only variants, a separate component when the markup
7863
7871
  differs. Nesting goes up to 10 levels; a \`table\` row may hold a data table or a polymorphic list.
7864
7872
  5. **Quality.** The componentize lane and \`npx @bettercms-ai/convert --componentize\` move markup
7865
7873
  VERBATIM \u2014 classes, responsive utilities, aria attributes, alt text all survive. Never
@@ -8487,7 +8495,7 @@ function buildServer(deps) {
8487
8495
  // 🔴 The plain-language ask, routed — the stdio twin of the hosted connector's line
8488
8496
  // (src/routes/mcp/index.ts MCP_INSTRUCTIONS). "Make my site editable" has one recipe and
8489
8497
  // one definition of done; without this an agent converts the page it landed on and stops.
8490
- 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
8498
+ instructions: TOOL_LOADING_INSTRUCTION + " " + RECEIPT_FIRST_INSTRUCTION + " When the user asks to make a site or all of its pages editable and they were DERIVED at import: editor_setup, then get_next_steps until `receipt.gate.complete`. To convert its source or 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
8491
8499
  // sentence as the hosted connector's MCP_INSTRUCTIONS.
8492
8500
  STRUCTURE_DEFAULT_INSTRUCTION + " " + SKILLS_ROUTING_INSTRUCTION + " " + DOCK_UI_ROUTING_INSTRUCTION + " " + PUBLISH_HOLD_INSTRUCTION
8493
8501
  }