@bettercms-ai/mcp 0.66.6 → 0.67.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 +40 -24
- package/dist/index.js.map +1 -1
- package/dist/skills/bettercms/references/bindings.md +10 -5
- package/dist/skills/bettercms/references/components.md +8 -3
- package/dist/skills/bettercms/references/getting-started.md +2 -1
- package/dist/skills/bettercms/references/localization.md +5 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2695,6 +2695,7 @@ var NEW_TOOL_TEXT = {
|
|
|
2695
2695
|
var DELETE_PAGE_RUNTIME_BUNDLE = " Or, with no pageId, `runtimeBundle`: 'report' lists the duplicate pages derived from a server app's own bundle (`app/` folders); 'apply' retires them and their empty folders.";
|
|
2696
2696
|
var PARAM_TEXT = {
|
|
2697
2697
|
runtimeBundle: "instead of pageId: the server-app bundle cleanup",
|
|
2698
|
+
pageRef: "page id or path ('/de/about'); a bare slug only when one page has it",
|
|
2698
2699
|
editorSetupForce: "re-run a setup already ready for the live build",
|
|
2699
2700
|
editorSetupCheck: "true = only report the state; starts nothing",
|
|
2700
2701
|
pageDynamic: "a dynamic (collection template) page only",
|
|
@@ -2705,7 +2706,7 @@ var PARAM_TEXT = {
|
|
|
2705
2706
|
where: "field filters: {field: value} is equality, {field: {op: value}} with op eq|ne|gt|gte|lt|lte|contains|in, and an array means `in`. System fields slug, status, publishedAt, locale work anywhere; a data field needs modelId. Compares the served copy (live for published, working for draft)",
|
|
2706
2707
|
sort: "comma list, a leading - sorts descending, e.g. '-publishedAt,title'",
|
|
2707
2708
|
page: "1-based page of `limit` rows; the response's meta says totalPages",
|
|
2708
|
-
locale: "BCP-47
|
|
2709
|
+
locale: "language, a BCP-47 tag (zh-CN \u2192 zh-cn), one of the project's locales; null = the default",
|
|
2709
2710
|
translationOf: "the SOURCE's id this translates (never another translation); null unlinks. One per locale: set locale on the translation and translationOf to the source",
|
|
2710
2711
|
pageScheduledAt: "ISO datetime to publish this page's draft at (needs publish permission); null cancels. An edit that omits scheduledAt CANCELS a pending schedule",
|
|
2711
2712
|
pageUnpublishAt: "ISO datetime at which the published page returns to draft; null cancels; omitting it never cancels it",
|
|
@@ -2716,7 +2717,7 @@ var PARAM_TEXT = {
|
|
|
2716
2717
|
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"
|
|
2717
2718
|
};
|
|
2718
2719
|
var INPUT_CONFIRMED_HEADER = "x-bcms-confirmed-input";
|
|
2719
|
-
var BUTTON_FIELD_NOTE = "'button' is ONE call to action, value {label, href, variant?, size?, arrow?, icon?, newTab?}: never a text field beside a link field.";
|
|
2720
|
+
var BUTTON_FIELD_NOTE = "'button' is ONE call to action, value {label, href, variant?, size?, arrow?, icon?, newTab?} (arrow:false, icon:null turn one off): never a text field beside a link field.";
|
|
2720
2721
|
var UPDATE_FIELD_DESCRIPTION = "Change ONE existing field of a content model (`modelId`) or of a page's schema (`pageId`), addressed by `path`: its key, or dot-separated keys through groups and repeaters ('hero.title'). Set its label, helpText, required, validation `config` (merged by key; null deletes one), showIf, ui or placement (null clears those). The rest of the schema is written back unchanged at the version read; a 409 means it changed since, so read again. `type` changes how every stored value is read and needs allowRetype: true. `key` renames it: stored values stay under the old key, so the route answers 409 with the entry count unless allowFieldRemoval: true discards them \u2014 migrate the values first. Ask the user before a retype or a rename. A page's label + link pair (primaryCtaText + primaryCtaHref) becomes one button with `mergeHref`.";
|
|
2721
2722
|
var GET_SEO_DESCRIPTION = "Get the connected project's site-wide SEO settings \u2014 every key update_seo writes: seoDefaults (metaTitle, metaDescription, ogImage, twitterHandle, og, twitter, collectionSchemaTypes), siteSchema (the site's JSON-LD), robotsConfig, sitemapConfig, rssConfig, indexnowConfig and searchConfig.";
|
|
2722
2723
|
var UPDATE_SEO_DESCRIPTION = "Set the connected project's site-wide SEO defaults and config. Each key you send is REPLACED whole (not deep-merged), so read get_seo and send the complete object. `seoDefaults` applies to every page that does not override it, and its `collectionSchemaTypes` maps a collection's model id to the schema.org type of its entries. `siteSchema` is the site's own JSON-LD (Organization/WebSite, emitted on the home page). `robotsConfig`, `sitemapConfig` and `rssConfig` shape robots.txt, sitemap.xml and the feed; `indexnowConfig` pings search engines on publish; `searchConfig` is site search. A key the plan does not include answers 403 FEATURE_LOCKED. Rebuilds the site.";
|
|
@@ -3299,6 +3300,7 @@ function buildToolDefs(deps) {
|
|
|
3299
3300
|
throw err;
|
|
3300
3301
|
}
|
|
3301
3302
|
}
|
|
3303
|
+
const pageIdOf = async (client, ref) => UUID.test(ref) ? ref : (await client.fetchJSON(client.url(`/management/pages/resolve?ref=${encodeURIComponent(ref)}`))).data.id;
|
|
3302
3304
|
function guard(fn) {
|
|
3303
3305
|
return async (args) => {
|
|
3304
3306
|
try {
|
|
@@ -3419,10 +3421,10 @@ function buildToolDefs(deps) {
|
|
|
3419
3421
|
translationOf: z.string().nullable().optional().describe(PARAM_TEXT.translationOf)
|
|
3420
3422
|
});
|
|
3421
3423
|
const getPageInput = z.object({
|
|
3422
|
-
pageId: z.string().min(1).describe(
|
|
3424
|
+
pageId: z.string().min(1).describe(PARAM_TEXT.pageRef)
|
|
3423
3425
|
});
|
|
3424
3426
|
const setPageContentInput = z.object({
|
|
3425
|
-
pageId: z.string().min(1).describe(
|
|
3427
|
+
pageId: z.string().min(1).describe(PARAM_TEXT.pageRef),
|
|
3426
3428
|
data: z.record(z.string(), z.unknown()).describe(
|
|
3427
3429
|
"field values keyed by field key. A nested 'array' (zone) value is an OBJECT { nonRepeatable: { childKey: value, \u2026 }, repeatable: [ { childKey: value }, \u2026 ] } \u2014 nonRepeatable holds the fixed-block values, repeatable is the list of item objects (omit a zone you didn't define). A primitive 'array' (itemType) is a plain list. An 'image' value is an asset URL or asset id (from upload_asset) \u2014 the server resolves it to { id, url, name, altText }. Read get_page first to see each field's zones."
|
|
3428
3430
|
),
|
|
@@ -3722,8 +3724,8 @@ function buildToolDefs(deps) {
|
|
|
3722
3724
|
const composePagesInput = z.object({
|
|
3723
3725
|
pages: z.array(
|
|
3724
3726
|
z.object({
|
|
3725
|
-
pageId: z.string().min(1).optional().describe(
|
|
3726
|
-
slug: z.string().min(1).optional().describe("page slug,
|
|
3727
|
+
pageId: z.string().min(1).optional().describe(PARAM_TEXT.pageRef),
|
|
3728
|
+
slug: z.string().min(1).optional().describe("a page slug, when one page has it; or pass `pageId`"),
|
|
3727
3729
|
blockJson: z.array(blockObject).describe("REPLACES this page's whole block composition")
|
|
3728
3730
|
})
|
|
3729
3731
|
).min(1).max(50)
|
|
@@ -4333,7 +4335,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
4333
4335
|
"update_page",
|
|
4334
4336
|
"Edit a page",
|
|
4335
4337
|
"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 and send its `optimisticVersion` as `ifMatch`). 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_field (pageId) / set_page_content for that. Renaming the slug keeps content intact. Publishing copies the draft blocks live in the same call \u2014 and on a singleton it publishes the page's content entry too; if the entry's workflow stage refuses, the page still goes live and `warnings` carries ENTRY_STILL_DRAFT with the entry id and the reason. 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,
|
|
4336
|
-
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(), scheduledAt: z.string().datetime().nullable().optional().describe(PARAM_TEXT.pageScheduledAt), unpublishAt: z.string().datetime().nullable().optional().describe(PARAM_TEXT.pageUnpublishAt), locale: z.string().nullable().optional().describe(PARAM_TEXT.locale), translationOf: z.string().nullable().optional().describe(PARAM_TEXT.translationOf), ifMatch: z.number().int().min(0).optional().describe(UPDATE_PAGE_IF_MATCH_NOTE), dynamic: z.object({ previewPath: z.string().nullable().optional().describe(PARAM_TEXT.previewPath), modelId: z.string().min(1).optional().describe(PARAM_TEXT.pageModelId) }).optional().describe(PARAM_TEXT.pageDynamic) }).shape,
|
|
4338
|
+
z.object({ ...headPatchShape, noindex: z.boolean().optional().describe("hide this page from search engines and site search"), pageId: z.string().min(1).describe(PARAM_TEXT.pageRef), 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(), scheduledAt: z.string().datetime().nullable().optional().describe(PARAM_TEXT.pageScheduledAt), unpublishAt: z.string().datetime().nullable().optional().describe(PARAM_TEXT.pageUnpublishAt), locale: z.string().nullable().optional().describe(PARAM_TEXT.locale), translationOf: z.string().nullable().optional().describe(PARAM_TEXT.translationOf), ifMatch: z.number().int().min(0).optional().describe(UPDATE_PAGE_IF_MATCH_NOTE), dynamic: z.object({ previewPath: z.string().nullable().optional().describe(PARAM_TEXT.previewPath), modelId: z.string().min(1).optional().describe(PARAM_TEXT.pageModelId) }).optional().describe(PARAM_TEXT.pageDynamic) }).shape,
|
|
4337
4339
|
async (c, a) => {
|
|
4338
4340
|
const res = await c.fetchJSON(
|
|
4339
4341
|
c.url(`/management/pages/${s(a.pageId)}/meta`),
|
|
@@ -4723,7 +4725,7 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
4723
4725
|
def(
|
|
4724
4726
|
"get_binding_report",
|
|
4725
4727
|
"Check what on the live site is editable",
|
|
4726
|
-
"The receipt for 'is this site actually EDITABLE?'. Each release scans the built HTML for the element rendering each CMS field value; per slot this returns `mode` ('text-match' = guessed from rendered text, 'declared' = template-declared), what was bound, and `unmatched`: each path with its reason \u2014 not-declared, ambiguous-text, no-element, declared-only, needs-wrap, refused, layout-owned, page-owned, layout-locator-stale, locale-unvarianted, svg-unavailable, partial and unstamped (a page list the content apply left as built: `kind` `list <bound>/<total>`), mismatch (a text-match stamp whose element shows MORE than the value, or no longer the text it was matched on (`data-bcms-from`): never written), report-outdated. DEPLOY FIRST: before any release there is no report (pages 0, mode null, refreshRequired true). `refreshReason`: `never-generated` \u2014 only a release BetterCMS builds and serves writes a report, so a HEADLESS site never gets one, redeploy or not; `outdated` \u2014 the next release rewrites it. It certifies only that every non-empty field has SOME element carrying its path. `unaddressable` is the copy it cannot see: visible live text no field owns, by route and bucket, biggest first (null = unmeasured: diff visible text against entry values). Over `count / visible > 0.02`, fix it in the SOURCE (wrap the run in an element the codemod can bind, or declare `<BcmsField path=\u2026>`), deploy, re-read \u2014 playbook \xA711. `canvas.lane`: the editor's live-preview lane (`bridge`, `draft-route`; `none` \u2192 get_next_steps). `coverage.error` `nothing-bound`: no element of this build is bound, so its percentage describes another site.",
|
|
4728
|
+
"The receipt for 'is this site actually EDITABLE?'. Each release scans the built HTML for the element rendering each CMS field value; per slot this returns `mode` ('text-match' = guessed from rendered text, 'declared' = template-declared), what was bound, and `unmatched`: each path with its reason \u2014 not-declared, ambiguous-text, no-element, declared-only, needs-wrap, refused, layout-owned, page-owned, layout-locator-stale, locale-unvarianted, svg-unavailable, partial and unstamped (a page list the content apply left as built: `kind` `list <bound>/<total>`), mismatch (a text-match stamp whose element shows MORE than the value, or no longer the text it was matched on (`data-bcms-from`): never written), report-outdated. DEPLOY FIRST: before any release there is no report (pages 0, mode null, refreshRequired true). `refreshReason`: `never-generated` \u2014 only a release BetterCMS builds and serves writes a report, so a HEADLESS site never gets one, redeploy or not; `outdated` \u2014 the next release rewrites it. It certifies only that every non-empty field has SOME element carrying its path. Global Sections count only on routes that render them (`layoutRoutes`). `unaddressable` is the copy it cannot see: visible live text no field owns, by route and bucket, biggest first (null = unmeasured: diff visible text against entry values). Over `count / visible > 0.02`, fix it in the SOURCE (wrap the run in an element the codemod can bind, or declare `<BcmsField path=\u2026>`), deploy, re-read \u2014 playbook \xA711. `canvas.lane`: the editor's live-preview lane (`bridge`, `draft-route`; `none` \u2192 get_next_steps). `coverage.error` `nothing-bound`: no element of this build is bound, so its percentage describes another site.",
|
|
4727
4729
|
z.object({ slot: z.enum(["current", "staging"]).optional().describe("which release tree to read \u2014 everything here, `unaddressable` included, is measured PER SLOT, so on a promote-gated project pass 'current' to read the tree the editor frames. Defaults to the slot this project's releases land in. The response's `sha` is the build the report describes; `refreshRequired` is true when that is not the build this slot serves. Also per slot: `builtRoutes`: routes the live build has HTML for (null = cannot say). `layoutIncomplete`: Layout paths the last content apply could not write (their publish waits); `layoutUnread`: Layout fields no route reads (`layoutUnreadJudged` false = not judged).") }).shape,
|
|
4728
4730
|
async (c, a) => ok("Binding report.", await data(c, "GET", `/management/projects/current/binding-report${q({ slot: a.slot })}`))
|
|
4729
4731
|
),
|
|
@@ -4994,7 +4996,7 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
4994
4996
|
def(
|
|
4995
4997
|
"get_deploy_status",
|
|
4996
4998
|
"Get deploy/build status",
|
|
4997
|
-
"Get the connected project's deploy/build status: state (idle|queued|building|failed), whether it's publishing, the live commit sha, when it went live, and any build error. `editorSetup`: whether editing is set up for the live build (never or stale = call editor_setup). Poll this after deploy_project until state is idle with your sha live. `release` is the newest release (id, sha, status, phaseMarks, error, createdAt): while an uploaded build ingests or releases, state is `building`, `release.sha` the new sha and `lastLiveSha` still the old one; a failed deploy shows `release.status` failed with its `error`, and `held` state (`held.by`: staff, archived or no-subdomain) means the release waits until that is cleared. `runtime` ({state: ok|crash-loop, restarts, oomKilled}) appears for a server app only: crash-loop means its container keeps restarting (often out of memory) though the build went live fine. Also returns `canvas.lane` (the editor's live-preview lane for this site: `bridge` / `draft-route` / `none`) and `editorUrl`, the visual editor to hand the user.",
|
|
4999
|
+
"Get the connected project's deploy/build status: state (idle|queued|building|failed), whether it's publishing, the live commit sha, when it went live, and any build error. `editorSetup`: whether editing is set up for the live build (never or stale = call editor_setup). Poll this after deploy_project until state is idle with your sha live. `release` is the newest release (id, sha, status, phaseMarks, error, createdAt): while an uploaded build ingests or releases, state is `building`, `release.sha` the new sha and `lastLiveSha` still the old one (re-sending the live sha is a no-op); a failed deploy shows `release.status` failed with its `error`, and `held` state (`held.by`: staff, archived or no-subdomain) means the release waits until that is cleared. `runtime` ({state: ok|crash-loop, restarts, oomKilled}) appears for a server app only: crash-loop means its container keeps restarting (often out of memory) though the build went live fine. Also returns `canvas.lane` (the editor's live-preview lane for this site: `bridge` / `draft-route` / `none`) and `editorUrl`, the visual editor to hand the user.",
|
|
4998
5000
|
z.object({}).shape,
|
|
4999
5001
|
// Flat body, no `data` envelope: `data()` read it as undefined.
|
|
5000
5002
|
async (c) => ok("Deploy status.", await c.fetchJSON(c.url(`/management/projects/deploy-status`), { method: "GET" }))
|
|
@@ -5052,7 +5054,7 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
5052
5054
|
name: "list_pages",
|
|
5053
5055
|
config: {
|
|
5054
5056
|
title: "List pages in the current project",
|
|
5055
|
-
description: "List the pages in the project this MCP key is bound to, each with its full field SCHEMA, pageType (singleton|dynamic), and status. Use it to verify WHERE content lands and to read field keys/types before set_page_content / add_field.",
|
|
5057
|
+
description: "List the pages in the project this MCP key is bound to, each with its id, `path`, `language`, full field SCHEMA, pageType (singleton|dynamic), and status. Use it to verify WHERE content lands and to read field keys/types before set_page_content / add_field.",
|
|
5056
5058
|
inputSchema: {}
|
|
5057
5059
|
},
|
|
5058
5060
|
handler: guard(
|
|
@@ -5064,6 +5066,9 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
5064
5066
|
id: p.id,
|
|
5065
5067
|
title: p.title,
|
|
5066
5068
|
slug: p.slug,
|
|
5069
|
+
// Its URL and language: slugs are unique per folder, so `about` can be seven pages.
|
|
5070
|
+
path: p.path,
|
|
5071
|
+
language: p.language,
|
|
5067
5072
|
pageType: p.pageType,
|
|
5068
5073
|
status: p.status,
|
|
5069
5074
|
fields: p.fields
|
|
@@ -5076,7 +5081,7 @@ ${res.warnings.join("\n")}` : summary, res.data);
|
|
|
5076
5081
|
name: "get_page",
|
|
5077
5082
|
config: {
|
|
5078
5083
|
title: "Get a page (with its field schema)",
|
|
5079
|
-
description: "Get one page
|
|
5084
|
+
description: "Get one page INCLUDING its full field schema (keys, types, nested group/repeater children), pageType, `path` and `language`. Read this before set_page_content so you write correctly-keyed values, or before add_field so you know the existing keys.",
|
|
5080
5085
|
inputSchema: getPageInput.shape
|
|
5081
5086
|
},
|
|
5082
5087
|
handler: guard(
|
|
@@ -5643,18 +5648,20 @@ ${notes.join("\n")}` : summary, created);
|
|
|
5643
5648
|
},
|
|
5644
5649
|
handler: guard(
|
|
5645
5650
|
async (args) => withClient(async (client) => {
|
|
5646
|
-
const
|
|
5647
|
-
const [known, pageList] = await Promise.all([
|
|
5648
|
-
client.listComponents({ select: "summary" }),
|
|
5649
|
-
needsSlug ? client.listPages() : Promise.resolve([])
|
|
5650
|
-
]);
|
|
5651
|
+
const known = await client.listComponents({ select: "summary" });
|
|
5651
5652
|
const ids = new Set(known.map((c) => c.id));
|
|
5652
|
-
const bySlug = new Map(pageList.map((p) => [p.slug, p.id]));
|
|
5653
5653
|
const results = [];
|
|
5654
5654
|
for (const page of args.pages) {
|
|
5655
|
-
const
|
|
5656
|
-
if (!
|
|
5657
|
-
results.push({ ok: false,
|
|
5655
|
+
const ref = page.pageId ?? page.slug;
|
|
5656
|
+
if (!ref) {
|
|
5657
|
+
results.push({ ok: false, error: "Pass a pageId or path from list_pages." });
|
|
5658
|
+
continue;
|
|
5659
|
+
}
|
|
5660
|
+
let pageId;
|
|
5661
|
+
try {
|
|
5662
|
+
pageId = await pageIdOf(client, ref);
|
|
5663
|
+
} catch (err) {
|
|
5664
|
+
results.push({ ok: false, slug: page.slug, error: errorText(err) });
|
|
5658
5665
|
continue;
|
|
5659
5666
|
}
|
|
5660
5667
|
const unknown = [...new Set(componentIdsIn(page.blockJson))].filter((id) => !ids.has(id));
|
|
@@ -5672,7 +5679,7 @@ ${notes.join("\n")}` : summary, created);
|
|
|
5672
5679
|
client.url(`/management/pages/${pageId}/meta`),
|
|
5673
5680
|
{ method: "PATCH", body: JSON.stringify({ blockJson: page.blockJson }) }
|
|
5674
5681
|
);
|
|
5675
|
-
results.push({ ok: true, pageId, slug: written.data?.slug ?? page.slug, blocks: page.blockJson.length });
|
|
5682
|
+
results.push({ ok: true, pageId, slug: written.data?.slug ?? page.slug, path: written.data?.path, blocks: page.blockJson.length });
|
|
5676
5683
|
} catch (err) {
|
|
5677
5684
|
results.push({ ok: false, pageId, slug: page.slug, error: errorText(err) });
|
|
5678
5685
|
}
|
|
@@ -5924,11 +5931,20 @@ ${lines.join("\n")}`, found);
|
|
|
5924
5931
|
// through the client's request plumbing — no bespoke SDK method per endpoint.
|
|
5925
5932
|
...lifecycleTools()
|
|
5926
5933
|
];
|
|
5934
|
+
const withPageRef = (def) => ({
|
|
5935
|
+
...def,
|
|
5936
|
+
handler: (args) => {
|
|
5937
|
+
const ref = args?.pageId;
|
|
5938
|
+
if (typeof ref !== "string" || !ref || UUID.test(ref)) return def.handler(args);
|
|
5939
|
+
return guard(async () => def.handler({ ...args, pageId: await withClient((c) => pageIdOf(c, ref)) }))(void 0);
|
|
5940
|
+
}
|
|
5941
|
+
});
|
|
5927
5942
|
return [
|
|
5928
5943
|
...defs.filter((d) => !LISTING_TAIL.has(d.name)),
|
|
5929
5944
|
...defs.filter((d) => LISTING_TAIL.has(d.name))
|
|
5930
|
-
].map(withProjectId);
|
|
5945
|
+
].map(withPageRef).map(withProjectId);
|
|
5931
5946
|
}
|
|
5947
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
5932
5948
|
function registerTools(server, deps, tools = process.env.BETTERCMS_MCP_TOOLS) {
|
|
5933
5949
|
const withElicit = {
|
|
5934
5950
|
...deps,
|
|
@@ -7335,7 +7351,7 @@ the host has a stale cached MCP \u2014 tell the user to \`rm -rf ~/.npm/_npx\` a
|
|
|
7335
7351
|
|
|
7336
7352
|
${SCHEMA_PROPOSAL_FLOW}
|
|
7337
7353
|
|
|
7338
|
-
After creating, report each page's id,
|
|
7354
|
+
After creating, report each page's id, path, type, and field count, and the project it
|
|
7339
7355
|
landed in. On 409 slug_taken, offer an alternative slug; on 401/403, the MCP key needs
|
|
7340
7356
|
(re)authorizing.`
|
|
7341
7357
|
}
|
|
@@ -7367,7 +7383,7 @@ ${PAGE_FLOW}
|
|
|
7367
7383
|
|
|
7368
7384
|
${STRUCTURE_RULE}
|
|
7369
7385
|
|
|
7370
|
-
After creating, report the page id,
|
|
7386
|
+
After creating, report the page id, path, type, field count, and the project it landed in.
|
|
7371
7387
|
On 409 slug_taken, offer an alternative slug; on 401/403, the MCP key needs (re)authorizing.`
|
|
7372
7388
|
}
|
|
7373
7389
|
}
|