@bettercms-ai/mcp 0.45.0 → 0.47.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
@@ -2102,6 +2102,19 @@ ${JSON.stringify(STRUCTURE_EXAMPLE_PAYLOAD, null, 2)}
2102
2102
  `;
2103
2103
 
2104
2104
  // src/tools.ts
2105
+ async function askHuman(deps, q) {
2106
+ if (!deps.elicit) return { prompt: q.prompt };
2107
+ try {
2108
+ const res = await deps.elicit({
2109
+ message: q.message,
2110
+ requestedSchema: { type: "object", properties: { [q.key]: q.property }, required: [q.key] }
2111
+ });
2112
+ const picked = res.action === "accept" ? res.content?.[q.key] : void 0;
2113
+ if (q.accept(picked)) return { value: picked };
2114
+ } catch {
2115
+ }
2116
+ return { prompt: q.prompt };
2117
+ }
2105
2118
  var FRAMEWORK_CHOICES = ["astro", "next", "react-ts", "other"];
2106
2119
  var FRAMEWORK_LABELS = {
2107
2120
  astro: "Astro \u2014 recommended default, static by default and fastest to publish",
@@ -2115,32 +2128,20 @@ var FRAMEWORK_PROMPT = [
2115
2128
  "",
2116
2129
  "Do not choose on their behalf. Sites cannot be built as plain HTML/CSS \u2014 every project is backed by one of these starters, which is what keeps its content editable in the CMS."
2117
2130
  ].join("\n");
2118
- async function askFramework(deps) {
2119
- if (!deps.elicit) return { prompt: FRAMEWORK_PROMPT };
2120
- try {
2121
- const res = await deps.elicit({
2122
- message: "Which technology should this site be built with?",
2123
- requestedSchema: {
2124
- type: "object",
2125
- properties: {
2126
- framework: {
2127
- type: "string",
2128
- title: "Technology",
2129
- description: "The frontend stack this project's starter is based on.",
2130
- enum: [...FRAMEWORK_CHOICES],
2131
- enumNames: FRAMEWORK_CHOICES.map((c) => FRAMEWORK_LABELS[c])
2132
- }
2133
- },
2134
- required: ["framework"]
2135
- }
2136
- });
2137
- const picked = res.action === "accept" ? res.content?.framework : void 0;
2138
- if (typeof picked === "string" && FRAMEWORK_CHOICES.includes(picked)) {
2139
- return { framework: picked };
2140
- }
2141
- } catch {
2142
- }
2143
- return { prompt: FRAMEWORK_PROMPT };
2131
+ function askFramework(deps) {
2132
+ return askHuman(deps, {
2133
+ key: "framework",
2134
+ message: "Which technology should this site be built with?",
2135
+ property: {
2136
+ type: "string",
2137
+ title: "Technology",
2138
+ description: "The frontend stack this project's starter is based on.",
2139
+ enum: [...FRAMEWORK_CHOICES],
2140
+ enumNames: FRAMEWORK_CHOICES.map((c) => FRAMEWORK_LABELS[c])
2141
+ },
2142
+ accept: (v) => typeof v === "string" && FRAMEWORK_CHOICES.includes(v),
2143
+ prompt: FRAMEWORK_PROMPT
2144
+ });
2144
2145
  }
2145
2146
  var AUTHORING_CHOICES = ["components", "fields"];
2146
2147
  var AUTHORING_LABELS = {
@@ -2156,32 +2157,41 @@ var AUTHORING_PROMPT = [
2156
2157
  "Recommend components unless the user says the site is schema-first.",
2157
2158
  "Do not choose on their behalf. This is asked once per project."
2158
2159
  ].join("\n");
2159
- async function askAuthoring(deps) {
2160
- if (!deps.elicit) return { prompt: AUTHORING_PROMPT };
2161
- try {
2162
- const res = await deps.elicit({
2163
- message: "Which authoring architecture should this site use? Components is recommended for a marketing site.",
2164
- requestedSchema: {
2165
- type: "object",
2166
- properties: {
2167
- preference: {
2168
- type: "string",
2169
- title: "Authoring architecture",
2170
- description: "How this site's pages are composed. Components is recommended unless this is a schema-first site (a blog, catalogue or directory). Asked once per project.",
2171
- enum: [...AUTHORING_CHOICES],
2172
- enumNames: AUTHORING_CHOICES.map((c) => AUTHORING_LABELS[c])
2173
- }
2174
- },
2175
- required: ["preference"]
2176
- }
2177
- });
2178
- const picked = res.action === "accept" ? res.content?.preference : void 0;
2179
- if (typeof picked === "string" && AUTHORING_CHOICES.includes(picked)) {
2180
- return { preference: picked };
2181
- }
2182
- } catch {
2183
- }
2184
- return { prompt: AUTHORING_PROMPT };
2160
+ function askAuthoring(deps) {
2161
+ return askHuman(deps, {
2162
+ key: "preference",
2163
+ message: "Which authoring architecture should this site use? Components is recommended for a marketing site.",
2164
+ property: {
2165
+ type: "string",
2166
+ title: "Authoring architecture",
2167
+ description: "How this site's pages are composed. Components is recommended unless this is a schema-first site (a blog, catalogue or directory). Asked once per project.",
2168
+ enum: [...AUTHORING_CHOICES],
2169
+ enumNames: AUTHORING_CHOICES.map((c) => AUTHORING_LABELS[c])
2170
+ },
2171
+ accept: (v) => typeof v === "string" && AUTHORING_CHOICES.includes(v),
2172
+ prompt: AUTHORING_PROMPT
2173
+ });
2174
+ }
2175
+ var USE_CDN_PROMPT = [
2176
+ "Ask the user where this site's media should be served from, then call set_media_delivery again with their answer as `useCdn`:",
2177
+ " 1. true \u2014 the BetterCMS CDN (recommended, and the default). Media URLs are minted on the CDN host, or on the project's own CDN base if it stores media in its own bucket.",
2178
+ " 2. false \u2014 the API origin. Media URLs are minted on the API host instead. Same bytes, same service; only the hostname differs.",
2179
+ "",
2180
+ "This applies to URLs minted FROM NOW ON. Already-published content keeps the URLs it has, and they keep working."
2181
+ ].join("\n");
2182
+ function askMediaDelivery(deps) {
2183
+ return askHuman(deps, {
2184
+ key: "useCdn",
2185
+ message: "Serve this site's media from the BetterCMS CDN (recommended), or from the API origin?",
2186
+ property: {
2187
+ type: "boolean",
2188
+ title: "Use the CDN",
2189
+ description: "On (default): media URLs are minted on the CDN \u2014 or on this project's own CDN base if it uses its own bucket. Off: they are minted on the API origin instead. Same bytes either way; only the host changes, and only for URLs minted from now on.",
2190
+ default: true
2191
+ },
2192
+ accept: (v) => typeof v === "boolean",
2193
+ prompt: USE_CDN_PROMPT
2194
+ });
2185
2195
  }
2186
2196
  var fieldType = z.enum([
2187
2197
  "text",
@@ -3299,7 +3309,7 @@ ${d.outline}` : "Proposed Content structure.", d);
3299
3309
  if (framework === void 0) {
3300
3310
  const asked = await askFramework(deps);
3301
3311
  if ("prompt" in asked) return fail(asked.prompt);
3302
- framework = asked.framework;
3312
+ framework = asked.value;
3303
3313
  }
3304
3314
  return ok("Created project.", await data(c, "POST", `/management/projects`, { ...a, framework }));
3305
3315
  }
@@ -3318,7 +3328,7 @@ ${d.outline}` : "Proposed Content structure.", d);
3318
3328
  if (preference === void 0) {
3319
3329
  const asked = await askAuthoring(deps);
3320
3330
  if ("prompt" in asked) return fail(asked.prompt);
3321
- preference = asked.preference;
3331
+ preference = asked.value;
3322
3332
  }
3323
3333
  return ok("Recorded the authoring architecture.", await data(c, "PATCH", `/management/projects/current/authoring-preference`, { preference }));
3324
3334
  }
@@ -3330,6 +3340,24 @@ ${d.outline}` : "Proposed Content structure.", d);
3330
3340
  z.object({ declaredBindings: z.boolean().describe("true = trust the template's declared bindings; false = text-match (the default)") }).shape,
3331
3341
  async (c, a) => ok("Recorded the binding mode.", await data(c, "PATCH", `/management/projects/current/binding-mode`, { declaredBindings: a.declaredBindings }))
3332
3342
  ),
3343
+ def(
3344
+ "set_media_delivery",
3345
+ "Choose where this site's media is served from",
3346
+ "Record whether this project's media (images, video, files) is served from the BetterCMS CDN or from the API origin. `useCdn: true` is the DEFAULT and the recommendation: URLs are minted on the CDN host, or on the project's own CDN base when it stores media in its own bucket (BYOK). `useCdn: false` mints them on the API origin instead \u2014 the same service serving the same bytes, only under a different hostname. ASK THE USER; do not pick for them. Called without `useCdn`, this tool asks them directly (or hands you the question to ask). \u{1F534} IT APPLIES TO URLs MINTED FROM NOW ON \u2014 new uploads, entry saves, get_media. It does NOT rewrite URLs already stored in published content, and it does not need to: the old URLs keep resolving. To move an existing image, re-save the field that holds it. \u{1F534} IT IS THE BACKEND HALF ONLY. A deployed frontend builds its own transform URLs with @bettercms-ai/image-url, which has its own media host \u2014 set `PUBLIC_BCMS_MEDIA_URL` (or the integration's `mediaUrl` option) to match, or that site's resized images stay on the CDN while everything else moves. Read the current answer from get_project (`useCdn`). Re-callable; the last answer wins.",
3347
+ // Optional in the schema for the same reason `framework` and `preference` are: a
3348
+ // required arg is rejected by the SDK before the handler runs, which would kill the
3349
+ // elicitation below and leave the model guessing.
3350
+ z.object({ useCdn: z.boolean().optional().describe("REQUIRED in effect \u2014 the USER's answer. true = CDN (the default), false = API origin. Ask them; never default it.") }).shape,
3351
+ async (c, a) => {
3352
+ let useCdn = a.useCdn;
3353
+ if (useCdn === void 0) {
3354
+ const asked = await askMediaDelivery(deps);
3355
+ if ("prompt" in asked) return fail(asked.prompt);
3356
+ useCdn = asked.value;
3357
+ }
3358
+ return ok("Recorded the media delivery origin.", await data(c, "PATCH", `/management/projects/current/media-delivery`, { useCdn }));
3359
+ }
3360
+ ),
3333
3361
  def(
3334
3362
  "submit_conversion_receipt",
3335
3363
  "Record what the conversion codemod could and could not do",
@@ -5269,8 +5297,9 @@ delivered value \u2014 they change editor chrome only.
5269
5297
  Without it a repeater of ten testimonials reads "Item 1 \u2026 Item 10" and the author has to open
5270
5298
  each one to find the one they meant. \`media\` may point at an \`image\`, \`file\` or url-ish
5271
5299
  \`text\` child; the type is not constrained.
5272
- - **\`ui.layout\`** \u2014 \`'list'\` (the default; omit it) or \`'grid'\`. Grid when the THUMBNAIL is
5273
- what the author scans: a gallery, a logo wall, a team. List when the words are.
5300
+ - **\`ui.layout\`** \u2014 \`'list'\` (the default; omit it), \`'grid'\`, or \`'table'\`. Grid when the THUMBNAIL is
5301
+ what the author scans: a gallery, a logo wall, a team. Table when a repeatable row schema's
5302
+ fields are columns, such as pricing or comparison data. List when the words are.
5274
5303
  - **\`ui.reorderable\`** \u2014 omit it (\u2261 \`true\`) unless the ORDER IS PART OF THE MEANING. \`false\`
5275
5304
  LOCKS the list so its items cannot be dragged: a "three steps" band that must stay three steps
5276
5305
  in that order, a nav whose order is semantic, a timeline. Locking a list nobody should reorder
@@ -5923,7 +5952,7 @@ Read bettercms://playbook/schema section 14 first \u2014 it has the exact prop s
5923
5952
  add_field / add_page_field; none of them affect delivery):
5924
5953
  ui.collapsed start a nesting field's panel closed
5925
5954
  ui.preview {title,media} which CHILD FIELD KEY titles a collapsed row, and which is its thumb
5926
- ui.layout 'list'|'grid' how its items are arranged; omit for list
5955
+ ui.layout 'list'|'grid'|'table' how its items are arranged; table is for repeatable row schemas; omit for list
5927
5956
  ui.reorderable omit (= true) unless the ORDER IS THE MEANING; false LOCKS the list
5928
5957
  helpText one line under the field: what to WRITE here
5929
5958
  showIf hide a field until another field has a value
@@ -6006,7 +6035,7 @@ and \`ui\` only changes editor chrome, so there is nothing to flip.`
6006
6035
 
6007
6036
  // src/server.ts
6008
6037
  var SERVER_NAME = "bettercms";
6009
- var SERVER_VERSION = "1.4.0";
6038
+ var SERVER_VERSION = "1.5.0";
6010
6039
  var SERVER_DISPLAY = {
6011
6040
  title: "BetterCMS",
6012
6041
  websiteUrl: "https://bettercms.ai",