@bettercms-ai/mcp 0.86.0 → 0.87.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
@@ -3116,7 +3116,9 @@ var WEBHOOK_EVENTS = [
3116
3116
  "content.created",
3117
3117
  "content.updated",
3118
3118
  "content.deleted",
3119
- "deployment.triggered"
3119
+ "deployment.triggered",
3120
+ "branch.created",
3121
+ "branch.applied"
3120
3122
  ];
3121
3123
  var NEW_TOOL_TEXT = {
3122
3124
  save_reusable_block: "Copy a page section (its layout, values and design) into Saved blocks in the section library, under `name`. The page is not changed; a block placed from Saved blocks is an independent section.",
@@ -3130,12 +3132,12 @@ var NEW_TOOL_TEXT = {
3130
3132
  create_webhook: "Create an outgoing webhook: BetterCMS POSTs each subscribed event to your https `url`, signed with HMAC-SHA256 in X-BetterCMS-Signature. The response carries the signing `secret` ONCE: store it now. `test: true` also sends a sample delivery. Payloads carry ids and metadata, never the content: fetch it from the API. A receiver that writes back on content.updated loops unless it skips its own writes.",
3131
3133
  list_branches: "List the project's content branches (id, name, slug, parent, status, whether main) and the `currentBranchId` this connection is on. To work inside a branch, pass its id as `branchId` on any tool call; apply_branch merges it into its parent.",
3132
3134
  create_branch: "Create a content branch: a copy of the project's content to edit without touching the live site. Returns the branch; pass its id as `branchId` on later calls to work in it, then apply_branch. 409 means the name or slug is taken.",
3133
- apply_branch: "Merge a branch into its parent. dryRun: true returns the diff (changes, summary, conflicts) and writes nothing: ALWAYS read it and show the user first. `include` limits the merge to those change keys; `fields` resolves a conflicting field per change key ('keep_branch' or 'keep_parent'). 409 names unresolved conflicts, missing dependencies, or a merge already running.",
3135
+ apply_branch: "Merge a branch into its parent. dryRun: true returns the diff (changes, summary, conflicts) and writes nothing: ALWAYS read it and show the user first. `include` limits the merge to those change keys; `fields` resolves a conflicting field per change key ('keep_branch' or 'keep_parent'). `publish: true` takes the merged changes live in the same call; `scheduleAt` merges everything and publishes at that time instead (null cancels). Both need a branch made from main and a connection allowed to publish. 409 names unresolved conflicts, missing dependencies, or a merge already running.",
3134
3136
  search_docs: "Search the BetterCMS documentation. Returns the best pages (title, url, breadcrumbs, snippet). Use it before guessing how a feature works; `meta.unavailable` means docs search is down right now.",
3135
3137
  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`.",
3136
3138
  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.",
3137
3139
  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.",
3138
- 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.",
3140
+ 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` if already run for the live sha, else starts (`running`; poll with `check: true`). `state`: never or stale (run it), running, ready, failed. 409: deploy first. Then get_next_steps. `setup.naming`: skill editor-chrome.",
3139
3141
  get_editable_plan: "Choices the platform can't make alone: `rows`, `options`, `recommended`. Explore the site per row: if confident, answer via confirm_editable_plan and tell the owner; if unsure, ask the owner in chat, recommended first. Never finish with an unsure one unasked.",
3140
3142
  confirm_editable_plan: "Record answers: each `decisions` item is an option's `decision`, unchanged. `check: true` = dry run.",
3141
3143
  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).",
@@ -3153,11 +3155,11 @@ var NEW_TOOL_TEXT = {
3153
3155
  install_integration_template: "Install a workspace template on this project: missing collections are created and the integration waits for approval (`approval.approveUrl`); `secretsNeeded` lists the env vars to set.",
3154
3156
  test_integration_fetch: "Call the vendor once through the server, as the integration: an approved host only, with the integration's `inject` secrets added server-side and every secret redacted from the answer. The request itself takes no {{...}} templates, and the token endpoint cannot be called. Use it to read the response shape before writing a sync: `items`, `externalKey` and `map` are JSON Pointers into it. Bodies over 1 MB come back truncated. 409 integration_not_approved: an admin must approve it first.",
3155
3157
  // CMS-stored custom dock UI: write → approve (the user's yes) → validate → publish. Recipe: skill dock-input.
3156
- write_dock_ui: "Save a custom dock input or panel (one .tsx `source`) as a new DRAFT version of `name` (a letter, then [a-z0-9-], \u226448), what `cms:<name>` imports resolve. Imports: react, react-dom, @bettercms-ai/kit/*, @bettercms-ai/dock-input, cms:<name>. Same bytes write nothing. Skill dock-input",
3158
+ write_dock_ui: "Save a custom dock input or panel (one .tsx `source`) as a new DRAFT version of `name` (a letter, then [a-z0-9-], \u226448), what `cms:<name>` imports resolve. Imports: react, react-dom, @bettercms-ai/kit/*, @bettercms-ai/dock-input, cms:<name>. Skill dock-input",
3157
3159
  approve_dock_ui: "Approve the newest `version` for publishing. Show the user the source first; userApproved: true only after their yes. A later write drops it",
3158
- validate_dock_ui: "Check one version (default newest) without publishing: allowed imports, size caps, a name the repo also has. Answers the build receipt",
3159
- publish_dock_ui: "Publish an APPROVED version (default newest): validates first, refusing policy.not_approved, build.name_conflict or the build's refusals, then rebuilds the project's dock UI release",
3160
- list_dock_ui: "The project's dock UI sources, with usage against the limits",
3160
+ validate_dock_ui: "Check one version (default newest) without publishing: imports, size caps, a name the repo also has. Answers the build receipt",
3161
+ publish_dock_ui: "Publish an APPROVED version (default newest); rebuilds the dock UI release. `notShown`: published, not drawn, and the fix to tell the user",
3162
+ list_dock_ui: "The project's dock UI sources (`notShown` when not drawn), usage, limits",
3161
3163
  // A V2 TypeSchema into a model (lane fields, through a change set) or a component's props (lane props).
3162
3164
  write_schema: "Write a kit V2 TypeSchema into a model (lane fields, as a change set; model: null creates) or a component's props (lane props). Run dryRun first: a write whose `loss` is not all in acceptLoss is refused schema.loss_not_accepted"
3163
3165
  };
@@ -3184,6 +3186,7 @@ var PARAM_TEXT = {
3184
3186
  componentIcon: "the block icon, a dock icon name; null clears. \xA73 of bettercms://playbook/schema",
3185
3187
  componentTabs: "/kit tabs no top-level prop sits in: [{id,label,icon?,order?}]. \xA73 of bettercms://playbook/schema",
3186
3188
  componentKitEntry: "the bettercms://kit entry id its V2 dock UI follows, confirmed by you: import only SUGGESTS one, so check its htmlSignals and pitfalls first (kit_entry_unknown otherwise); null clears. The setup gate's dock row needs one",
3189
+ componentDock: "propOrder: each top-level prop key once; tabs 'none': no tab strip; design 'fold': Design under the content, 'tab': own tab; null resets",
3187
3190
  describes: "for editors: what it is and holds; omitted: written for you",
3188
3191
  runtimeBundle: "instead of pageId: the server-app bundle cleanup",
3189
3192
  pageRef: "page id or path ('/de/about'); a bare slug only when one page has it",
@@ -3211,9 +3214,10 @@ var PARAM_TEXT = {
3211
3214
  focalX: "focal point, 0-1 across from the left edge; null resets to the centre",
3212
3215
  focalY: "focal point, 0-1 down from the top edge; null resets to the centre",
3213
3216
  mergeHref: "pageId only: the link field paired with `path` (the label). Both become ONE `button` field keyed `key` (default: their stem, primaryCtaHref \u2192 primaryCta). Each entry's DRAFT gets {label, href} and keeps the old values; the live copy changes on publish. ifMatch is the page's version. 409 CODE_RENDERED_ROUTE: do its `next` recipe in code",
3214
- confirmedInput: "true only after the user explicitly approved adding this custom input (`ui.input`); else setting or changing one is refused input_needs_confirmation. Removing one needs no approval",
3217
+ confirmedInput: "true only after the user explicitly approved adding this custom input (`ui.input`), or a hide/fold/required\u2192optional (else a named 409). Removing one needs no approval",
3215
3218
  dockUiUserApproved: "true only after the user said yes to this version",
3216
3219
  dockUiTarget: "per scope: field {modelId|pageId|componentId, path}, section {componentId}, page {pageId}, layout {}",
3220
+ dockUiAction: "unpublish: off the dock; delete: an unpublished source",
3217
3221
  writeSchemaModel: "model or component id/slug; null creates a model",
3218
3222
  writeSchemaAcceptLoss: "loss keys or paths you accept",
3219
3223
  writeSchemaIfMatch: "the dry run's `version`; needed to write over a stored row"
@@ -3852,7 +3856,7 @@ var UPDATE_MODEL_DESCRIPTION = "Edit a content model: rename it, change descript
3852
3856
  var FIELD_REMOVAL_NOTE = "top-level field keys (from get_content_model) to DELETE from this model with every entry's value for them; needs allowFieldRemoval: true. An unknown key refuses the whole call. Never for a rename: rename the label instead.";
3853
3857
  var ALLOW_FIELD_REMOVAL_NOTE = "true = the user accepts that removeFields discards those fields' data in every entry. There is no dry run on this route: confirm first.";
3854
3858
  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.";
3855
- 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/tab (428 without).";
3859
+ 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*, dock, override or a prop ui icon/itemLabel/group/tab (428 without).";
3856
3860
  var PROP_UI_ARG_NOTE = "top-level prop key \u2192 ui patch, the same vocabulary as a prop's `ui` (group and tab included): each key merges, a null key clears it, null clears the whole ui. Patches keep every other prop as stored; sending `props` instead REPLACES the whole array, so echo each prop's stored `ui` (its `ui.group` AND `ui.tab`) or the prop leaves its tab";
3857
3861
  var OVERRIDE_NOTE = "replace chrome a person set (otherwise 409 ui_human_owned names those paths). Send it only on the user's word.";
3858
3862
  var FIELD_UI_ARG_NOTE = "path \u2192 ui patch, a field's `ui` vocabulary: each key merges, a null key clears it, null clears the whole ui";
@@ -4480,7 +4484,8 @@ function buildToolDefs(deps) {
4480
4484
  propHelpText: z.record(z.string(), z.string().max(200).nullable()).optional().describe("prop key \u2192 help text; null = no help on purpose (stays empty, never refilled)"),
4481
4485
  override: z.boolean().optional().describe(OVERRIDE_NOTE),
4482
4486
  ifMatch: z.number().int().min(0).optional().describe(COMPONENT_IF_MATCH_NOTE),
4483
- confirmed: z.boolean().optional().describe(PARAM_TEXT.confirmedInput)
4487
+ confirmed: z.boolean().optional().describe(PARAM_TEXT.confirmedInput),
4488
+ dock: z.object({ propOrder: z.array(z.string().min(1)).optional(), tabs: z.enum(["none"]).nullable().optional(), design: z.enum(["tab", "fold"]).nullable().optional() }).strict().optional().describe(PARAM_TEXT.componentDock)
4484
4489
  });
4485
4490
  const setComponentSourceInput = z.object({
4486
4491
  componentId: z.string().min(1).describe("component id (from list_components)"),
@@ -4856,8 +4861,13 @@ ${d.outline}` : "Proposed Content structure.", d);
4856
4861
  "publish_dock_ui",
4857
4862
  "Publish custom dock UI",
4858
4863
  NEW_TOOL_TEXT.publish_dock_ui,
4859
- z.object({ name: dockUiName, version: z.number().int().min(1).optional() }).shape,
4860
- async (c, a) => ok("Published the dock UI version.", await data(c, "POST", `/management/dock-ui/${encodeURIComponent(s(a.name))}/publish`, { version: a.version }))
4864
+ z.object({ name: dockUiName, version: z.number().int().min(1).optional(), action: z.enum(["unpublish", "delete"]).optional().describe(PARAM_TEXT.dockUiAction) }).shape,
4865
+ async (c, a) => {
4866
+ const at = `/management/dock-ui/${encodeURIComponent(s(a.name))}`;
4867
+ if (a.action === "unpublish") return ok("Unpublished the dock UI source.", await data(c, "POST", `${at}/unpublish`, {}));
4868
+ if (a.action === "delete") return ok("Deleted the dock UI source.", await data(c, "DELETE", at));
4869
+ return ok("Published the dock UI version.", await data(c, "POST", `${at}/publish`, { version: a.version }));
4870
+ }
4861
4871
  ),
4862
4872
  def(
4863
4873
  "list_dock_ui",
@@ -5694,9 +5704,11 @@ ${d.outline}` : "Proposed Content structure.", d);
5694
5704
  branchId: z.string().min(1).describe("the branch to merge (from list_branches)"),
5695
5705
  dryRun: z.boolean().optional().describe("true = return the diff and write nothing"),
5696
5706
  include: z.array(z.string().min(1)).max(500).optional().describe("change keys to merge (from the diff); all when omitted"),
5697
- fields: z.record(z.string(), z.record(z.string(), z.enum(["keep_branch", "keep_parent"]))).optional().describe("change key \u2192 field \u2192 'keep_branch' | 'keep_parent', for each conflict")
5707
+ fields: z.record(z.string(), z.record(z.string(), z.enum(["keep_branch", "keep_parent"]))).optional().describe("change key \u2192 field \u2192 'keep_branch' | 'keep_parent', for each conflict"),
5708
+ publish: z.boolean().optional().describe("true = also take the merged changes live, in the same call (a branch made from main)"),
5709
+ scheduleAt: z.string().datetime({ offset: true }).nullable().optional().describe("ISO time to merge everything and publish then, instead of now; null cancels a schedule")
5698
5710
  }).shape,
5699
- async (c, a) => a.dryRun === true ? ok("Branch diff (nothing was written).", await data(c, "GET", `/management/branches/${s(a.branchId)}/diff`)) : ok("Branch applied.", await c.fetchJSON(c.url(`/management/branches/${s(a.branchId)}/apply`), { method: "POST", body: JSON.stringify({ include: a.include, fields: a.fields }) }))
5711
+ async (c, a) => a.dryRun === true ? ok("Branch diff (nothing was written).", await data(c, "GET", `/management/branches/${s(a.branchId)}/diff`)) : a.scheduleAt !== void 0 ? ok(a.scheduleAt ? "Branch scheduled." : "Branch unscheduled.", await data(c, "PUT", `/management/branches/${s(a.branchId)}/schedule`, { at: a.scheduleAt })) : ok(a.publish ? "Branch applied and published." : "Branch applied.", await c.fetchJSON(c.url(`/management/branches/${s(a.branchId)}/apply`), { method: "POST", body: JSON.stringify({ include: a.include, fields: a.fields, publish: a.publish }) }))
5700
5712
  ),
5701
5713
  def(
5702
5714
  "search_docs",
@@ -6872,8 +6884,14 @@ ${lines.join("\n")}`, found);
6872
6884
  },
6873
6885
  handler: guard(
6874
6886
  async (args) => withClient(async (client) => {
6875
- const { componentId, ifMatch, confirmed, ...input } = args;
6876
- const res = confirmed === true || typeof input.kitEntry === "string" ? await client.fetchJSON(client.url(`/management/components/${componentId}`), {
6887
+ const { componentId, ifMatch, confirmed, dock, ...rest } = args;
6888
+ const input = {
6889
+ ...rest,
6890
+ ...dock?.propOrder !== void 0 ? { propOrder: dock.propOrder } : {},
6891
+ ...dock?.tabs !== void 0 ? { dockTabs: dock.tabs } : {},
6892
+ ...dock?.design !== void 0 ? { dockDesign: dock.design } : {}
6893
+ };
6894
+ const res = confirmed === true || typeof input.kitEntry === "string" || dock !== void 0 ? await client.fetchJSON(client.url(`/management/components/${componentId}`), {
6877
6895
  method: "PATCH",
6878
6896
  body: JSON.stringify(input),
6879
6897
  headers: { ...ifMatch !== void 0 ? { "If-Match": `W/"${ifMatch}"` } : {}, ...confirmedHeaders(confirmed) }