@bettercms-ai/mcp 0.50.0 → 0.51.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
@@ -2730,7 +2730,12 @@ function buildToolDefs(deps) {
2730
2730
  max: z.number().optional().describe("'number' fields only \u2014 inclusive ceiling"),
2731
2731
  phoneFormat: z.enum(["any", "e164"]).optional().describe("'phone' fields only"),
2732
2732
  pattern: z.string().optional().describe("'text' / 'textarea' / 'url' fields only \u2014 a regex")
2733
- }).optional().describe("per-field rules the API enforces on submit; each key is only valid on the field types listed")
2733
+ }).optional().describe("per-field rules the API enforces on submit; each key is only valid on the field types listed"),
2734
+ ui: z.strictObject({
2735
+ countryPicker: z.boolean().optional().describe("'phone' fields only \u2014 render a country picker; the value is stored as E.164"),
2736
+ defaultCountry: z.string().optional().describe("'phone' fields only \u2014 uppercase ISO 3166-1 alpha-2 the picker starts on; requires countryPicker"),
2737
+ showFlags: z.boolean().optional().describe("'phone' fields only \u2014 show the country flag beside the dial code; requires countryPicker")
2738
+ }).optional().describe("per-field presentation options; each key is only valid on the field types listed")
2734
2739
  });
2735
2740
  const formSettingsShape = {
2736
2741
  description: z.string().optional(),
@@ -3440,7 +3445,7 @@ ${d.outline}` : "Proposed Content structure.", d);
3440
3445
  def(
3441
3446
  "list_content_models",
3442
3447
  "List content models",
3443
- "List the content models (reusable schemas for dynamic collections like Blog/Products) in the connected project.",
3448
+ "List the content models (reusable schemas for dynamic collections like Blog/Products) in the connected project. A project-scoped connection sees its own project's models plus workspace-level ones, which it can read but not change.",
3444
3449
  z.object({}).shape,
3445
3450
  async (c) => ok("Content models.", await data(c, "GET", `/management/content/models`))
3446
3451
  ),
@@ -3477,7 +3482,7 @@ ${d.outline}` : "Proposed Content structure.", d);
3477
3482
  def(
3478
3483
  "update_page",
3479
3484
  "Edit a page",
3480
- "Edit a page: title, slug, SEO metaTitle/metaDescription, structured data (`schemaType`, `schema`), publish status (draft|published), and `blockJson` (its block composition \u2014 passing it REPLACES the whole array, so read get_page first). It does NOT change the field SCHEMA \u2014 use add_page_field / set_page_content for that. Renaming the slug keeps content intact. Publishing copies the draft blocks live in the same call. STRUCTURED DATA is native SEO, never a field: leave it out and the page gets Automatic JSON-LD from its content (WebSite on the home page, Blog/CollectionPage on a collection's list page, WebPage otherwise); `schemaType` picks an explicit schema.org type whose properties are filled from the content; `schema` is pasted JSON-LD and wins over both (null clears it). " + PAGE_HEAD_NOTE + " " + SECTION_DOCTRINE,
3485
+ "Edit a page: title, slug, SEO metaTitle/metaDescription, structured data (`schemaType`, `schema`), publish status (draft|published), and `blockJson` (its block composition \u2014 passing it REPLACES the whole array, so read get_page first). A `blockJson` that swaps a component block to another component comes back with `warnings` (COMPONENT_SWAP_DROPS_OVERRIDES) naming the overrides the new component does not render; they stay on the block, so swapping back restores them. It does NOT change the field SCHEMA \u2014 use add_page_field / set_page_content for that. Renaming the slug keeps content intact. Publishing copies the draft blocks live in the same call. STRUCTURED DATA is native SEO, never a field: leave it out and the page gets Automatic JSON-LD from its content (WebSite on the home page, Blog/CollectionPage on a collection's list page, WebPage otherwise); `schemaType` picks an explicit schema.org type whose properties are filled from the content; `schema` is pasted JSON-LD and wins over both (null clears it). " + PAGE_HEAD_NOTE + " " + SECTION_DOCTRINE,
3481
3486
  z.object({ ...headPatchShape, noindex: z.boolean().optional().describe("hide this page from search engines and site search"), pageId: z.string().min(1), title: z.string().optional(), slug: z.string().optional(), blockJson: z.array(blockObject).optional().describe("REPLACES the page's block composition"), metaTitle: z.string().optional(), metaDescription: z.string().optional(), schemaType: z.enum(["auto", "WebPage", "AboutPage", "ContactPage", "CollectionPage", "Blog", "BlogPosting", "Article", "Product", "FAQPage", "Event", "Organization", "LocalBusiness"]).optional().describe("structured data type; 'auto' (the default) derives it from the content"), schema: z.union([z.record(z.string(), z.unknown()), z.array(z.record(z.string(), z.unknown()))]).nullable().optional().describe("custom JSON-LD; wins over schemaType; null clears it"), status: z.enum(["draft", "published"]).optional() }).shape,
3482
3487
  async (c, a) => {
3483
3488
  const res = await c.fetchJSON(
@@ -3488,10 +3493,9 @@ ${d.outline}` : "Proposed Content structure.", d);
3488
3493
  }
3489
3494
  );
3490
3495
  const note = res.redirectNote;
3491
- return ok(
3492
- note ? `Updated page. An existing redirect from ${note.source} already points to ${note.existingDestination}; it wasn't changed, so the old address does not redirect to the new one.` : "Updated page.",
3493
- res.data
3494
- );
3496
+ const summary = note ? `Updated page. An existing redirect from ${note.source} already points to ${note.existingDestination}; it wasn't changed, so the old address does not redirect to the new one.` : "Updated page.";
3497
+ return ok(res.warnings?.length ? `${summary}
3498
+ ${res.warnings.join("\n")}` : summary, res.data);
3495
3499
  }
3496
3500
  ),
3497
3501
  // ── Code + deploy (parity with remote /mcp; needs artifact:write) ──
@@ -3945,9 +3949,11 @@ ${d.outline}` : "Proposed Content structure.", d);
3945
3949
  ...args.slug !== void 0 ? { slug: args.slug } : {},
3946
3950
  ...args.data !== void 0 ? { data: args.data } : {}
3947
3951
  });
3948
- const entry = args.status !== void 0 && args.status !== "draft" ? await client.updateEntry(created.id, { status: args.status }) : created;
3952
+ const published = args.status !== void 0 && args.status !== "draft" ? await client.updateEntry(created.id, { status: args.status }) : created;
3953
+ const { warnings, ...entry } = published;
3954
+ const notes = [.../* @__PURE__ */ new Set([...created.warnings ?? [], ...warnings ?? []])];
3949
3955
  return ok(
3950
- `Created entry '${entry.slug}' (id ${entry.id}, status ${entry.status}).`,
3956
+ [`Created entry '${entry.slug}' (id ${entry.id}, status ${entry.status}).`, ...notes].join("\n"),
3951
3957
  entry
3952
3958
  );
3953
3959
  })
@@ -3977,7 +3983,7 @@ ${d.outline}` : "Proposed Content structure.", d);
3977
3983
  name: "list_content_entries",
3978
3984
  config: {
3979
3985
  title: "List content entries (incl. drafts)",
3980
- description: "List content entries \u2014 including drafts \u2014 filtered by model and/or page. Use it to SEE existing content before editing. For a singleton page, pass its pageId to get its single entry.",
3986
+ description: "List content entries \u2014 including drafts \u2014 filtered by model and/or page. Use it to SEE existing content before editing. For a singleton page, pass its pageId to get its single entry. A project-scoped connection sees its own project's entries plus workspace-level ones, which it can read but not change.",
3981
3987
  inputSchema: listEntriesInput.shape
3982
3988
  },
3983
3989
  handler: guard(
@@ -4014,13 +4020,13 @@ ${d.outline}` : "Proposed Content structure.", d);
4014
4020
  },
4015
4021
  handler: guard(
4016
4022
  async (args) => withClient(async (client) => {
4017
- const entry = await client.updateEntry(args.entryId, {
4023
+ const { warnings, ...entry } = await client.updateEntry(args.entryId, {
4018
4024
  ...args.data !== void 0 ? { data: args.data } : {},
4019
4025
  ...args.status !== void 0 ? { status: args.status } : {},
4020
4026
  ...args.slug !== void 0 ? { slug: args.slug } : {},
4021
4027
  ...args.meta !== void 0 ? { meta: args.meta } : {}
4022
4028
  });
4023
- return ok(`Updated entry '${entry.slug}' (id ${entry.id}, status ${entry.status}).`, entry);
4029
+ return ok([`Updated entry '${entry.slug}' (id ${entry.id}, status ${entry.status}).`, ...warnings ?? []].join("\n"), entry);
4024
4030
  })
4025
4031
  )
4026
4032
  },
@@ -5553,7 +5559,9 @@ Author a form (then the user embeds it with \`<BcmsForm form={getForm('Name')} /
5553
5559
  \`options\`), radio (needs \`options\`), number, phone, date, url, consent, hidden. Optional per
5554
5560
  field: required?, placeholder?, helpText? (shown under the input), defaultValue?,
5555
5561
  \`validation\` (\`emailPolicy\` on email, \`min\`/\`max\` on number, \`phoneFormat\` on phone,
5556
- \`pattern\` on text/textarea/url \u2014 a rule on any other type is a 400), and
5562
+ \`pattern\` on text/textarea/url \u2014 a rule on any other type is a 400),
5563
+ \`ui\` (\`countryPicker\` + \`defaultCountry\` on phone \u2014 renders a country picker and stores
5564
+ E.164; \`defaultCountry\` without \`countryPicker\` is a 400), and
5557
5565
  \`showIf: { field, equals }\` for conditional display.
5558
5566
  3. **Settings** \u2014 name (used by getForm('Name')), submitLabel?, successMessage?, redirectUrl?.
5559
5567
  4. **Confirm**, then \`create_form\` { name, fields, ... } (returns the new id), or