@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 +87 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
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
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
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.
|
|
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.
|
|
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 \`'
|
|
5273
|
-
what the author scans: a gallery, a logo wall, a team.
|
|
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.
|
|
6038
|
+
var SERVER_VERSION = "1.5.0";
|
|
6010
6039
|
var SERVER_DISPLAY = {
|
|
6011
6040
|
title: "BetterCMS",
|
|
6012
6041
|
websiteUrl: "https://bettercms.ai",
|