@bettercms-ai/mcp 0.51.1 → 0.52.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 +66 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1999,6 +1999,7 @@ import { DeviceAuthPendingError } from "@bettercms-ai/device-auth";
|
|
|
1999
1999
|
// src/structure-playbook.ts
|
|
2000
2000
|
var STRUCTURE_PLAYBOOK_URI = "bettercms://playbook/structure";
|
|
2001
2001
|
var STRUCTURE_DEFAULT_INSTRUCTION = `After you create collections or pages, organise them per the structure playbook: read ${STRUCTURE_PLAYBOOK_URI}, call suggest_content_structure (read-only), show the user its outline, then apply it with set_content_structure and the version it returned (the If-Match). A project that already has a structure keeps it: file only what you created, with move_to_folder, unless the user asks for a full re-organisation.`;
|
|
2002
|
+
var SKILLS_ROUTING_INSTRUCTION = "Before writing code in a repo that uses BetterCMS, read the installed `bettercms` skill if there is one: it explores the repo and names the one or two bettercms-* skills the task needs.";
|
|
2002
2003
|
var STRUCTURE_EXAMPLE_PAYLOAD = {
|
|
2003
2004
|
version: 0,
|
|
2004
2005
|
doc: {
|
|
@@ -2921,6 +2922,15 @@ function buildToolDefs(deps) {
|
|
|
2921
2922
|
const submitComponentizeReceiptInput = z.object({
|
|
2922
2923
|
receipt: z.record(z.string(), z.unknown()).describe("The receipt `npx @bettercms-ai/convert --componentize --receipt <file>` wrote, verbatim.")
|
|
2923
2924
|
});
|
|
2925
|
+
const componentValidationRouteInput = z.object({
|
|
2926
|
+
componentId: z.string().min(1).describe("component id (from list_components)"),
|
|
2927
|
+
path: z.string().min(1).max(500).optional().describe("route in the app that serves the preview; omit for the default"),
|
|
2928
|
+
nativeViewports: z.array(z.object({
|
|
2929
|
+
name: z.string().min(1).max(64),
|
|
2930
|
+
width: z.number().int().positive().max(1e4),
|
|
2931
|
+
height: z.number().int().positive().max(1e4)
|
|
2932
|
+
})).min(1).max(8).optional().describe("viewports to check; omit for the defaults")
|
|
2933
|
+
});
|
|
2924
2934
|
const clearComponentSourceInput = z.object({
|
|
2925
2935
|
componentId: z.string().min(1).describe("component id (from list_components)")
|
|
2926
2936
|
});
|
|
@@ -3433,7 +3443,7 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
3433
3443
|
def(
|
|
3434
3444
|
"submit_conversion_receipt",
|
|
3435
3445
|
"Record what the conversion codemod could and could not do",
|
|
3436
|
-
"Hand BetterCMS the codemod's own account of a conversion run, so the coverage meter can say WHY a path is not declared instead of only that it is not. Submit the receipt `npx @bettercms-ai/convert` wrote (`--receipt out.json`) for the SAME `briefDigest` get_conversion_brief { complete: true } returned: `{ briefDigest, receipt }`, where the receipt carries `paths: { declared, rewritten, alreadyDeclared, pending[{ route, scope, path, kind, file, reason, message, fix }] }`. `paths.declared` must equal rewritten + alreadyDeclared + pending.length, and each pending `reason` is one of the converter's own (IN_EXPRESSION, AMBIGUOUS_LITERAL, REPEATER_FIXED_LENGTH, PARSE_ERROR, \u2026) \u2014 a path with no receipt row simply reads `not-declared`. \u{1F534} A RECEIPT WITH PENDING PATHS IS A PROGRESS REPORT, NOT A FINISH LINE. The response answers `complete` and `pendingTotal`, and echoes the first 40 pending rows WITH their `fix` \u2014 `{ action, file, line, col?, snippet, why? }`, where `action` is one of `wrap-span` (wrap the literal in a `<span data-bcms-field=\u2026>`), `declare-attr` (add `data-bcms-field=\u2026` to the element at file:line), `bind-expression` (replace the expression with the framework's bcmsField helper), `declare-richtext` (bind the container with the richtext helper), `bind-data` (the literal comes from the data file at file:line \u2014 bind that field) or `manual` (with a one-sentence `why`). Apply every fix in the source, rerun the codemod, resubmit. `complete: true` is the only receipt that ends a conversion \u2014 do not report a site converted on anything less. It is a RECORD, not a release: it changes nothing about the site, and the meter picks it up on the next get_binding_report after the next deploy. A 404 `unknown-brief` means that digest was never issued here, so convert against a brief this project actually returned. \u{1F534} SUBMIT THE
|
|
3446
|
+
"Hand BetterCMS the codemod's own account of a conversion run, so the coverage meter can say WHY a path is not declared instead of only that it is not. Submit the receipt `npx @bettercms-ai/convert` wrote (`--receipt out.json`) for the SAME `briefDigest` get_conversion_brief { complete: true } returned: `{ briefDigest, receipt }`, where the receipt carries `paths: { declared, rewritten, alreadyDeclared, pending[{ route, scope, path, kind, file, reason, message, fix }] }`. `paths.declared` must equal rewritten + alreadyDeclared + pending.length, and each pending `reason` is one of the converter's own (IN_EXPRESSION, AMBIGUOUS_LITERAL, REPEATER_FIXED_LENGTH, PARSE_ERROR, \u2026) \u2014 a path with no receipt row simply reads `not-declared`. \u{1F534} A RECEIPT WITH PENDING PATHS IS A PROGRESS REPORT, NOT A FINISH LINE. The response answers `complete` and `pendingTotal`, and echoes the first 40 pending rows WITH their `fix` \u2014 `{ action, file, line, col?, snippet, why? }`, where `action` is one of `wrap-span` (wrap the literal in a `<span data-bcms-field=\u2026>`), `declare-attr` (add `data-bcms-field=\u2026` to the element at file:line), `bind-expression` (replace the expression with the framework's bcmsField helper), `declare-richtext` (bind the container with the richtext helper), `bind-data` (the literal comes from the data file at file:line \u2014 bind that field) or `manual` (with a one-sentence `why`). Apply every fix in the source, rerun the codemod, resubmit. `complete: true` is the only receipt that ends a conversion \u2014 do not report a site converted on anything less. It is a RECORD, not a release: it changes nothing about the site, and the meter picks it up on the next get_binding_report after the next deploy. A 404 `unknown-brief` means that digest was never issued here, so convert against a brief this project actually returned. \u{1F534} SUBMIT THE `--forms` RECEIPT TOO, AS A SECOND CALL. A run of `npx @bettercms-ai/convert --forms` writes a receipt whose `paths` are all zero and whose account is in a `forms` block (`{ wired, alreadyWired, pending[{ id, name, reason }], notes, wiredForms[{ id, file }] }`). Write it to its own file (`--receipt forms-receipt.json`) and submit it here under the same `briefDigest`: it is stored BESIDE the binding receipt and never touches coverage, and the next release reads `wiredForms` to say which component renders each form. Then read `forms.pending` and publish every form `forms.notes` names in the Forms tab. Requires artifact:write, the same authority as set_binding_mode; on a workspace-wide connection pass `projectId`.",
|
|
3437
3447
|
z.object({
|
|
3438
3448
|
briefDigest: z.string().min(1).describe("The `briefDigest` get_conversion_brief { complete: true } returned. Must match the receipt's own."),
|
|
3439
3449
|
receipt: z.record(z.string(), z.unknown()).describe("The receipt `npx @bettercms-ai/convert --receipt out.json` wrote, verbatim.")
|
|
@@ -4247,7 +4257,7 @@ ${notes.join("\n")}` : summary, created);
|
|
|
4247
4257
|
name: "publish_layout",
|
|
4248
4258
|
config: {
|
|
4249
4259
|
title: "Publish the Global Layout draft",
|
|
4250
|
-
description: "Publish the connected project's GLOBAL Layout draft (navigation, footer, every reserved section) so the live site builds from it. Until this is called the layout stays draft and get_layout copy:'published' answers PUBLISHED_LAYOUT_UNAVAILABLE \u2014 site chrome authored with update_layout is NOT live. Read get_layout first and pass its revision as ifMatch; a stale revision returns 409 \u2014 re-read, never retry blindly. A 422 lists validation issues to fix with update_layout first. Verify with get_layout copy:'published' and check the copy echo \u2014 this tool's own response is the write's echo, not a receipt. Page overrides go live with the page (update_page status:'published'). A 403 PUBLISH_NOT_GRANTED means this connection can author drafts but cannot publish \u2014 say so and let the user allow publishing or publish from the dashboard.",
|
|
4260
|
+
description: "Publish the connected project's GLOBAL Layout draft (navigation, footer, every reserved section) so the live site builds from it. Until this is called the layout stays draft and get_layout copy:'published' answers PUBLISHED_LAYOUT_UNAVAILABLE \u2014 site chrome authored with update_layout is NOT live. Read get_layout first and pass its revision as ifMatch; a stale revision returns 409 \u2014 re-read, never retry blindly. A 422 lists validation issues to fix with update_layout first. A new project's chrome is draft: publish_component navigation-default and footer-default first (each can publish while the other is draft), then publish_layout. navigation.logo is a REQUIRED image \u2014 new projects get a generated one; if it is empty (required_value) or not a stored asset (image_asset_missing), upload one and set it with update_layout as { id, url, name, altText }. Verify with get_layout copy:'published' and check the copy echo \u2014 this tool's own response is the write's echo, not a receipt. Page overrides go live with the page (update_page status:'published'). A 403 PUBLISH_NOT_GRANTED means this connection can author drafts but cannot publish \u2014 say so and let the user allow publishing or publish from the dashboard.",
|
|
4251
4261
|
inputSchema: publishLayoutInput.shape
|
|
4252
4262
|
},
|
|
4253
4263
|
handler: guard(async (args) => withClient(async (client) => {
|
|
@@ -4548,6 +4558,56 @@ ${lines.join("\n")}`, found);
|
|
|
4548
4558
|
})
|
|
4549
4559
|
)
|
|
4550
4560
|
},
|
|
4561
|
+
{
|
|
4562
|
+
name: "get_component_readiness",
|
|
4563
|
+
config: {
|
|
4564
|
+
title: "Read a component's Output validation readiness",
|
|
4565
|
+
description: "Read what stands between a component and a validated Output: `validation.gap` and `validation.fix` say why a validation run cannot take it yet (COMPONENT_SOURCE_NOT_RECORDED: record its file with set_component_source, or place it and deploy a build whose markup carries its data-bcms-block / data-bcms-field attributes), and the readiness says where its request, evidence and approval stand. Call it after authoring a component and after every validation run. outputReady means the Output renders; publishReady also needs the owner's Visual Approval in the dashboard, which no tool can give.",
|
|
4566
|
+
inputSchema: clearComponentSourceInput.shape
|
|
4567
|
+
},
|
|
4568
|
+
handler: guard(
|
|
4569
|
+
async (args) => withClient(async (client) => {
|
|
4570
|
+
const res = await client.fetchJSON(
|
|
4571
|
+
client.url(`/management/components/${encodeURIComponent(args.componentId)}/readiness`)
|
|
4572
|
+
);
|
|
4573
|
+
return ok("Component validation readiness.", res.data);
|
|
4574
|
+
})
|
|
4575
|
+
)
|
|
4576
|
+
},
|
|
4577
|
+
{
|
|
4578
|
+
name: "declare_component_route",
|
|
4579
|
+
config: {
|
|
4580
|
+
title: "Declare where a component's preview is served",
|
|
4581
|
+
description: "Declare where this component's preview is served in the app (the dashboard's \"Show preview\"). Omit `path` to use the server's default route; pass `nativeViewports` only to check sizes other than the defaults. The origin is the project's preview URL and is never taken from you. Answers the readiness.",
|
|
4582
|
+
inputSchema: componentValidationRouteInput.shape
|
|
4583
|
+
},
|
|
4584
|
+
handler: guard(
|
|
4585
|
+
async (args) => withClient(async (client) => {
|
|
4586
|
+
const res = await client.fetchJSON(
|
|
4587
|
+
client.url(`/management/components/${encodeURIComponent(args.componentId)}/adapter`),
|
|
4588
|
+
{ method: "PUT", body: JSON.stringify({ path: args.path, nativeViewports: args.nativeViewports }) }
|
|
4589
|
+
);
|
|
4590
|
+
return ok("Preview route declared.", res.data);
|
|
4591
|
+
})
|
|
4592
|
+
)
|
|
4593
|
+
},
|
|
4594
|
+
{
|
|
4595
|
+
name: "request_component_validation",
|
|
4596
|
+
config: {
|
|
4597
|
+
title: "Request Output validation for a component",
|
|
4598
|
+
description: "Ask for this component's Output to be validated by YOU, the connected agent (provider user-agent). Record its source first (set_component_source, or the componentize codemod plus a deploy): a component nothing renders is refused with 422 and the fix, and no request is opened. After a 202, run `npx @bettercms-ai/preview-runtime validate --local --request <requestId>` in the app's repository with BCMS_API_KEY set in the environment (never pass a key in a tool call), then read get_component_readiness. Your evidence is self-attested: publishing still needs the owner's Visual Approval in the dashboard \u2014 never claim you approved it. CI validation (github-app) runs in the owner's repository at their cost, so it is refused here with VALIDATION_PROVIDER_DASHBOARD_ONLY: ask the owner to start it from the dashboard. 409 COMPONENT_ORCHESTRATION_V2_REQUIRED means continue in Agent Dock.",
|
|
4599
|
+
inputSchema: clearComponentSourceInput.shape
|
|
4600
|
+
},
|
|
4601
|
+
handler: guard(
|
|
4602
|
+
async (args) => withClient(async (client) => {
|
|
4603
|
+
const res = await client.fetchJSON(
|
|
4604
|
+
client.url(`/management/components/${encodeURIComponent(args.componentId)}/implementation-requests`),
|
|
4605
|
+
{ method: "POST", body: JSON.stringify({}) }
|
|
4606
|
+
);
|
|
4607
|
+
return ok("Validation requested. Run `npx @bettercms-ai/preview-runtime validate --local --request <requestId>` in the repository next.", res.data);
|
|
4608
|
+
})
|
|
4609
|
+
)
|
|
4610
|
+
},
|
|
4551
4611
|
{
|
|
4552
4612
|
name: "publish_component",
|
|
4553
4613
|
config: {
|
|
@@ -5240,8 +5300,9 @@ hand. THE ORDER, and every step of it matters:
|
|
|
5240
5300
|
fallback, and declares each binding.
|
|
5241
5301
|
2b. IF THE BRIEF CARRIES \`forms\`, wire them too:
|
|
5242
5302
|
\`npx @bettercms-ai/convert --forms --brief brief.json --root . --receipt forms-receipt.json\`
|
|
5243
|
-
\u2014 ITS OWN receipt file,
|
|
5244
|
-
|
|
5303
|
+
\u2014 ITS OWN receipt file, submitted with submit_conversion_receipt as a second call: the
|
|
5304
|
+
server stores its \`forms\` block beside the binding receipt (coverage is untouched), and
|
|
5305
|
+
the next release reads \`wiredForms\` to say which component renders each form.
|
|
5245
5306
|
A site imported by phase 1 has a DRAFT form row per \`<form>\` in its build, so the Forms tab
|
|
5246
5307
|
is full while the repository's markup still posts wherever it always did \u2014 to nothing, or to
|
|
5247
5308
|
somebody else's endpoint. This pass writes the endpoint, the form id, a marker per field and
|
|
@@ -6182,7 +6243,7 @@ function buildServer(deps) {
|
|
|
6182
6243
|
// one definition of done; without this an agent converts the page it landed on and stops.
|
|
6183
6244
|
instructions: "When the user asks to make a site or all of its pages editable, to convert it, or to bind its fields: this is playbook \xA713. Read `bettercms://playbook/schema` \xA713, call get_binding_report and get_conversion_brief { complete: true }, convert EVERY route the brief lists, and finish only when get_binding_report shows coverage.pending empty on every route \u2014 not when the first page works. On a workspace-wide connection pass projectId on every call; never ask the user to re-scope the connection. When the user asks to componentize the whole site, to turn every section into a component, or to build a component library from the site: this is playbook \xA712. Read `bettercms://playbook/schema` \xA712, start with get_site_composition, and use the batch tools \u2014 create_components, compose_pages, update_layout with `commands`, publish_components \u2014 rather than one call per component. Finish with get_site_composition and tell the user what the platform does not model (cookie banners, modals, breadcrumbs, pagination) and which components still need the owner's approval in the dashboard before they can be published. BetterCMS never executes a customer's Section renderer or app code. An ordinary MCP connection is not a push runner: explicitly poll list_section_validation_requests, claim one request at an exact git commit, run implementation and responsive checks inside the user's own repository and real app shell, then submit manifest + validation with that requestId and complete it\u2014or truthfully fail it when implementation/evidence is missing. Never invent a manifest, a passing validation, or visual evidence; these tools cannot grant the separate human Visual Approval required for publication. " + // The default after authoring (the structure standard): organise what you made. Same
|
|
6184
6245
|
// sentence as the hosted connector's MCP_INSTRUCTIONS.
|
|
6185
|
-
STRUCTURE_DEFAULT_INSTRUCTION
|
|
6246
|
+
STRUCTURE_DEFAULT_INSTRUCTION + " " + SKILLS_ROUTING_INSTRUCTION
|
|
6186
6247
|
}
|
|
6187
6248
|
);
|
|
6188
6249
|
server.registerResource(
|