@bettercms-ai/mcp 0.78.0 → 0.79.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.
@@ -1,5 +1,152 @@
1
1
  {
2
2
  "$comment": "GENERATED from the dashboard's Kit Library (KIT_LIBRARY + EXAMPLES) by scripts/kit-export.ts. Do not edit. Regenerate (dashboard: bun run kit:export -- --backend <backend checkout>).",
3
+ "fieldConfig": {
4
+ "read": {
5
+ "*": [
6
+ "placeholder",
7
+ "zones",
8
+ "options",
9
+ "fields"
10
+ ],
11
+ "text": [
12
+ "minLength",
13
+ "maxLength",
14
+ "pattern",
15
+ "patternMessage",
16
+ "inputType"
17
+ ],
18
+ "longtext": [
19
+ "minLength",
20
+ "maxLength",
21
+ "pattern",
22
+ "patternMessage",
23
+ "inputType"
24
+ ],
25
+ "url": [
26
+ "minLength",
27
+ "maxLength",
28
+ "pattern",
29
+ "patternMessage",
30
+ "inputType"
31
+ ],
32
+ "richtext": [
33
+ "minLength",
34
+ "maxLength",
35
+ "pattern",
36
+ "patternMessage",
37
+ "inputType"
38
+ ],
39
+ "document": [
40
+ "minLength",
41
+ "maxLength",
42
+ "pattern",
43
+ "patternMessage",
44
+ "inputType"
45
+ ],
46
+ "number": [
47
+ "min",
48
+ "max",
49
+ "step",
50
+ "scale",
51
+ "unit",
52
+ "validation"
53
+ ],
54
+ "rating": [
55
+ "min",
56
+ "max",
57
+ "step",
58
+ "scale",
59
+ "unit",
60
+ "validation"
61
+ ],
62
+ "select": [
63
+ "multiSelect"
64
+ ],
65
+ "multiselect": [
66
+ "min",
67
+ "max",
68
+ "allowCustom"
69
+ ],
70
+ "tags": [
71
+ "min",
72
+ "max",
73
+ "allowCustom"
74
+ ],
75
+ "code": [
76
+ "language",
77
+ "languages"
78
+ ],
79
+ "date": [
80
+ "timezone",
81
+ "includeTime"
82
+ ],
83
+ "datetime": [
84
+ "timezone",
85
+ "includeTime"
86
+ ],
87
+ "reference": [
88
+ "contentModelId",
89
+ "polymorphic",
90
+ "allowedModelSlugs",
91
+ "targets",
92
+ "filter"
93
+ ],
94
+ "multi-reference": [
95
+ "contentModelId",
96
+ "polymorphic",
97
+ "allowedModelSlugs",
98
+ "targets",
99
+ "filter",
100
+ "min",
101
+ "max"
102
+ ],
103
+ "array": [
104
+ "itemType",
105
+ "minItems",
106
+ "maxItems",
107
+ "item"
108
+ ],
109
+ "image": [
110
+ "media"
111
+ ],
112
+ "modular": [
113
+ "blockSlugs",
114
+ "minItems",
115
+ "maxItems"
116
+ ]
117
+ },
118
+ "host": {
119
+ "any": [
120
+ "sectionLabel",
121
+ "sectionShape",
122
+ "sectionTitle",
123
+ "format"
124
+ ],
125
+ "richtext": [
126
+ "accept",
127
+ "allowMultipleParagraphs",
128
+ "inline",
129
+ "allowTargetBlank",
130
+ "components",
131
+ "hostTag",
132
+ "features"
133
+ ]
134
+ },
135
+ "server": [
136
+ "seeded",
137
+ "shared",
138
+ "sharedValue",
139
+ "attr",
140
+ "numeric",
141
+ "iconFont",
142
+ "htmlClasses",
143
+ "units",
144
+ "lead",
145
+ "band",
146
+ "family",
147
+ "shape"
148
+ ]
149
+ },
3
150
  "categories": [
4
151
  {
5
152
  "name": "Navigation & chrome",
@@ -57,8 +57,8 @@ Listing tools needs no sign-in; the first tool **call** does.
57
57
  These hold on every task. The reference in brackets has the detail and the codes.
58
58
 
59
59
  1. **Confirm the bound project before any write**: §9 of the schema playbook, then `get_project`.
60
- Its `authoringPreference` (components, fields, or null while owed) is asked once, can be
61
- re-answered, and is the user's answer, never yours.
60
+ Its `authoringPreference` defaults to components (recorded by a deploy, a promote or setup)
61
+ and is never asked; the owner can switch it to fields.
62
62
  2. **Never create SEO fields** (§5b). Page and entry meta are native columns. [seo.md]
63
63
  3. **A page is a list of sections**, never loose top-level blocks, and never a nav, footer or
64
64
  modal: chrome is the Layout. A call to action is one `button` field, never a text field
@@ -186,8 +186,8 @@ Stop at the first step that is not clean, fix, then restart. A report that does
186
186
 
187
187
  1. **Fetch the live URL**, cache-busted: a 200 whose body contains copy you just published. The
188
188
  steps below read the control plane, and all of them can be green on a site that answers 404.
189
- 2. `get_binding_report` for every route you touched: `coverage.pending` empty, and
190
- `mode: "declared"` on a declared site. `refreshReason: "never-generated"` means no BetterCMS
189
+ 2. `get_binding_report` for every route you touched: `unaddressable` under 2%, and, where the
190
+ source declares its bindings, `coverage.pending` empty and `mode: "declared"`. `refreshReason: "never-generated"` means no BetterCMS
191
191
  release wrote one (on headless none ever will, so step 1 is the proof); `"outdated"` means
192
192
  the next release rewrites it.
193
193
  3. `get_site_composition`: every page you touched is made of sections, with the intended
@@ -265,7 +265,7 @@ Check: change the bound string, publish, reload: the drawn text reads the new va
265
265
  ## 12. In the editor's canvas
266
266
 
267
267
  The Visual Editor shows the real page with its scripts running, and owns three things there: the
268
- scroll (its canvas scrolls and the page's window follows), the pointer (a click selects a field)
268
+ scroll (the page scrolls its own window, as it does live), the pointer (a click selects a field)
269
269
  and the place on the page (picking a field in the panel scrolls to it). Motion that reads the
270
270
  window's scroll or the clock runs there as it does live. Only what takes one of those three away
271
271
  has to stand down, and the site decides that with one question:
@@ -284,7 +284,7 @@ canvas, and false on the published page. What each kind of page does there:
284
284
  | Native scroll, CSS scroll-driven animations (`animation-timeline`), Motion `useScroll` / `whileInView`, ScrollTrigger pin and scrub | runs as live | nothing |
285
285
  | Lenis, Locomotive Scroll 5 | runs; the wheel has no easing (section 6) | nothing |
286
286
  | GSAP ScrollSmoother | runs; the window still scrolls, the content eases behind it | nothing |
287
- | Locomotive Scroll 4, any script that locks the window and moves the content by a transform | the page works under its own wheel, but the canvas cannot scroll it and a field picked in the panel is not brought into view | `smooth: !inEditor()`, so the window scrolls |
287
+ | Locomotive Scroll 4, any script that locks the window and moves the content by a transform | the page works under its own wheel, but the editor cannot scroll it and a field picked in the panel is not brought into view | `smooth: !inEditor()`, so the window scrolls |
288
288
  | A wheel-driven slide deck (GSAP `Observer`, fullPage) | the same | do not create the observer when `inEditor()`; let the sections stack and scroll |
289
289
  | A scene with its own scroll layer (react-three-fiber `ScrollControls`, a fixed `overflow: auto` layer) | runs; picking a field scrolls that layer | nothing |
290
290
  | An intro or preloader that holds the page (`overflow: hidden` until it ends, a full-screen cover) | plays on every reload of the canvas, and covers the page while it does | skip it when `inEditor()` |
@@ -292,5 +292,5 @@ canvas, and false on the published page. What each kind of page does there:
292
292
  Never use `inEditor()` to change what a field renders, to hide a section or to stop a timeline:
293
293
  the canvas must show what a visitor gets. It is for scroll takeover, intros and nothing else.
294
294
 
295
- Check: open the page in the Visual Editor, scroll with the wheel, drag the canvas scrollbar to the
295
+ Check: open the page in the Visual Editor, scroll with the wheel, drag the page's scrollbar to the
296
296
  footer, then pick a field of the first section in the panel: the canvas returns to it.
@@ -235,6 +235,11 @@ coverage as `help`. The platform writes a line only for the four reasons above a
235
235
  elements with none; clear the finding by running `editor_setup`, or by writing one of the four
236
236
  kinds yourself, never with filler.
237
237
 
238
+ **Config keys.** Write only the `config` keys a field type documents (`maxLength`, `min`/`max`,
239
+ `options`, `itemType`, `zones`, a rich text's `accept`/`inline`): any other key sends that field to
240
+ the old classic editor. The import's own keys (`attr`, `numeric`, `units`, `band`, `seeded`, …) are the
241
+ server's: both MCP surfaces drop them from what you write.
242
+
238
243
  ## 6. Changing a schema safely
239
244
 
240
245
  A schema write alone is not a release. Before one, read the model with `get_content_model`, a few
@@ -323,7 +328,7 @@ production cannot store it. Translate before the first write.
323
328
  | `boolean` | `boolean` | `boolean` |
324
329
  | `slug` | `slug` (an entry's own slug stays on the entry) | `text` |
325
330
  | `url`, `link` | `link` | `link`; `url` for a media or embed address |
326
- | `email`, `phone` | `email`, `phone` | `text` |
331
+ | `email`, `phone` | `email`; a phone number is `text` with `richText: false` (`phone` opens only in the classic editor) | `text` |
327
332
  | `date`, `datetime` | `date`, `datetime` | `date` (`YYYY-MM-DD`); a datetime stays `text` |
328
333
  | `color` | `color` | `color` |
329
334
  | `select` | `select` | `select` with `config.options` |
@@ -341,14 +346,14 @@ production cannot store it. Translate before the first write.
341
346
  | `object` | `group` | `group` |
342
347
  | `array` of objects | `repeater`; item limits in `config.minItems` / `config.maxItems` (the editor shows "max N") | `table` |
343
348
  | `array` of strings | `array` (`itemType: "text"`) | `table` |
344
- | `array` of several item shapes | `modular` with `config.blockSlugs` (create the block models first) | separate components |
345
- | `slot` | `sections` or `component-ref` | `slot` (one instance; a list with `config.multiple`) |
349
+ | `array` of several item shapes | one component per shape, placed as sections; on a model, a `repeater` whose rows carry each shape's fields (`modular` opens only in the classic editor) | separate components |
350
+ | `slot` | components placed on the page (componentize); never a `sections` or `component-ref` field, which the page form cannot edit | `slot` (one instance; a list with `config.multiple`) |
346
351
  | a form `reference` | a `form` block inside the section | `form` |
347
352
 
348
353
  V2 features with no production shape are dropped and reported, never faked: a per-cell table
349
354
  type, per-breakpoint values (only `style.hideOn` differs by breakpoint) and per-field
350
355
  localization. Two have a shape now: typed table columns are a `repeater` with one field per column
351
- and `ui.layout: "table"`, and a slot with several children is a `slot` prop with `config.multiple`
356
+ (never `ui.layout: "table"`, which the editor cannot draw), and a slot with several children is a `slot` prop with `config.multiple`
352
357
  ([components.md](components.md) §1).
353
358
 
354
359
  | The plan says | Production word |
@@ -362,7 +367,7 @@ and `ui.layout: "table"`, and a slot with several children is a `slot` prop with
362
367
  | Variant, layout | Components sharing one `sectionType` ([components.md](components.md)) |
363
368
  | Listing | `collection` block (§4) |
364
369
  | Picks | `multi-reference` |
365
- | Polymorphic array, modules | `modular` field |
370
+ | Polymorphic array, modules | one component per shape |
366
371
 
367
372
  Tools and reports use the production word. Labels and help an editor reads never say
368
373
  "document": the `document` field is labelled Body.
@@ -87,9 +87,11 @@ its number, a `fixCode` and a `fix`, the call that closes it.
87
87
  (`fixCode`, `count`, the `fix` of its first row, the first `routes`), two with the same count in a
88
88
  fixed order. Run it on every row that carries that `fixCode`, call `get_next_steps`, and repeat
89
89
  until `complete`: following it reaches `complete` with no step that changes nothing. A fix no
90
- tool can make is named only when nothing else is left. A `fix` of `null` means no tool closes that row (a collection's listing
91
- route, a URL that cannot be placed, two pages nobody but the owner can choose between): put it to
92
- the user and stop retrying. Treat a row that its fix did not move the same way.
90
+ tool can make is named only when nothing else is left. A `fix` of `null` means no tool closes that row by itself: its
91
+ `detail` names what to change in the site's code, so change it, commit, push and `rebuild_project`.
92
+ Only a collection's listing route, a URL that cannot be placed and two pages nobody but the owner
93
+ can choose between are left; record each as an exception with a true reason, never stop to ask. A
94
+ row its fix did not move twice is recorded the same way.
93
95
 
94
96
  A `sections` or `style` row is a page nobody can add a section to, reorder or space, however
95
97
  complete its copy is. Its `fixCode` names the one fix, and `fix` the call when a tool can make it:
@@ -97,8 +99,8 @@ complete its copy is. Its `fixCode` names the one fix, and `fix` the call when a
97
99
  | `fixCode` | The fix |
98
100
  |---|---|
99
101
  | `run-sections-conversion` | Only the site's code can draw this page's sections: its app renders the route, a framework hydrates the page whole (`PAGE_HYDRATED`), or the release could not map its bands. In a checkout run `npx @bettercms-ai/convert@latest --sections --root .` ([componentize.md](componentize.md) §5), or call `componentize_sections` with `codemod: true` and the plan's `digest`; `--upgrade-sections` for a `style` row. Then push and release |
100
- | `rebuild` | The build that serves is older than what makes the row pass: a published section, the wrap's commit (`wrap: "wrapped"` on a row that still fails), or the in-place switch. No tool starts one: ask the user to push and Rebuild |
101
- | `enable-in-place-structure` | A static page the release can write by itself, and the project's in-place structure switch is off. Setup switches it on for a project it creates; on an existing one it is the operator's call. No repository change |
102
+ | `rebuild` | The build that serves is older than what makes the row pass: a published section, the wrap's commit (`wrap: "wrapped"` on a row that still fails), or the in-place switch. Push what changed, then `rebuild_project` and `get_deploy_status { waitSeconds: 50 }` until it is live |
103
+ | `enable-in-place-structure` | A static page the release can write by itself, and the project's in-place structure switch is off. Setup switches it on for a project it creates; on an existing one, `update_project { structureInPlace: true }`. No repository change |
102
104
  | `run-setup` | Nothing measured what the row reads, or a page is still to register: `editor_setup { force: true }`, then read again |
103
105
 
104
106
  The other rows name their fix the same way:
@@ -189,7 +191,8 @@ Where the markup says no more it numbers them (`Pricing text 4`, `Hero image 2`)
189
191
  whose classes name nothing is called by its heading's words. No build can say that a picture is
190
192
  the dashboard screenshot; the site's code can. So these never hold the gate open: they are
191
193
  `receipt.gate.naming`, and `get_next_steps` lists the step `names-from-code` until each is
192
- renamed. Do it in the same job, after the gate's own rows: an editor reads these names all day.
194
+ renamed. Do it in the same job, after the gate's own rows and before the final report: an
195
+ editor reads these names all day, and a setup that leaves them is not finished.
193
196
 
194
197
  Name from the site's code, the way a developer writes a schema. Open the section's source file
195
198
  and use what it calls things (`plan.cents`, `billingFrequency`, `StatsBand.astro`):
@@ -202,12 +205,13 @@ and use what it calls things (`plan.cents`, `billingFrequency`, `StatsBand.astro
202
205
  | A field or prop | what it HOLDS there, one to three words | `Dismiss key`, `Starter plan price`, `Dashboard screenshot`, `Billing frequency` | its own copy, a role with a number (`Text 4`), the section's name alone |
203
206
  | Help text | most fields get none. One plain sentence under 90 characters, only for a format or unit, a place the canvas cannot show, a real constraint or a fallback. No semicolons, no dashes, no values from the page | `Leave empty to use the page title.` | the label or the value said again, writing advice, a figure copied from another page |
204
207
 
205
- **Advice is not work left.** `receipt.gate.advice`, `gate.families`, `gate.kit` and `gate.naming`
208
+ **Advice is not work left**, except the names. `gate.naming` is part of the job: rename every
209
+ row before you report, as above. `receipt.gate.advice`, `gate.families` and `gate.kit`
206
210
  are things a person may want to change about the site's own content or build: a title longer than
207
211
  a search result shows, no default share image, an element outside any section, a route family that
208
212
  could be a collection, a kit entry to confirm, a name to sharpen. The site is fully editable with
209
- every one of them standing. When `gate.complete` is true the setup is DONE: say so, list the
210
- advice in a few lines as optional improvements, and never call any of it pending, blocked,
213
+ every one of them standing. When `gate.complete` is true and `gate.naming` is absent or empty the setup is
214
+ DONE: say so, list the advice in a few lines as optional improvements, and never call any of it pending, blocked,
211
215
  remaining or left over, and never end on a question about it.
212
216
 
213
217
  A section says two things: what it IS (its kind) and what it SAYS (its title). Only the kind is
@@ -11,11 +11,9 @@ for what it leaves out.
11
11
 
12
12
  ## Communication style
13
13
 
14
- Be succinct. Say what you did ("Converted 12 routes; 3 values still unmatched on /pricing"), ask
15
- direct questions, and skip narrating what you are about to do. Give numbers, not adjectives.
16
-
17
- Before starting, tell the user they can pause and resume anytime by saying
18
- **"Continue BetterCMS setup"**.
14
+ Be succinct. Say what you did ("Converted 12 routes; 3 values still unmatched on /pricing") and
15
+ skip narrating what you are about to do. Give numbers, not adjectives. Finish the setup in this
16
+ run: do not stop to ask anything the sections below give an answer for.
19
17
 
20
18
  **RESUME TRIGGER.** On "Continue BetterCMS setup", read `get_project`, `get_next_steps` and
21
19
  `get_binding_report` for the project and the gap ledger at `.bettercms/ledger.json` if it exists,
@@ -42,17 +40,16 @@ Check: the MCP answers for this project id, and this folder is its repo.
42
40
  added while you run loads only when the agent starts, and every step below works on that
43
41
  folder's source.
44
42
 
45
- ### Phase 2 — The questions the MCP requires, and no others
46
-
47
- Check: `get_project` shows `authoringPreference` set and the user has answered media delivery.
43
+ ### Phase 2 — No questions: the defaults are recorded
48
44
 
49
- Ask exactly these two, each through its tool, and never answer them yourself:
45
+ Nothing here is asked. Record and report:
50
46
 
51
- 1. **Authoring**: `set_authoring_preference` (components or fields). Recommend components for a
52
- marketing site. It is asked once per project and can be re-answered.
53
- 2. **Media delivery**: `set_media_delivery` (the BetterCMS CDN, recommended, or the API origin).
47
+ 1. **Authoring**: components. A deploy, a promote and every `editor_setup` record it when nothing
48
+ is set; call `set_authoring_preference` only if the prompt you were started with names `fields`.
49
+ 2. **Media delivery**: the BetterCMS CDN, the project's default. Leave it.
50
+ 3. **Git**: the prompt you were started with says whether you may push. Follow it; never ask.
54
51
 
55
- Everything else has a default. Do not ask about it; decide, then report what you decided:
52
+ Everything else has a default too. Do not ask about it; decide, then report what you decided:
56
53
 
57
54
  - **Scope is the whole site**: every page, section, heading, paragraph, list item, link and image
58
55
  the site renders. Not the home page first, not "the main copy".
@@ -62,8 +59,9 @@ Everything else has a default. Do not ask about it; decide, then report what you
62
59
 
63
60
  ### Phase 3 — Convert and componentize
64
61
 
65
- Check: `editor_setup { check: true }` reads `ready`, `submit_conversion_receipt` returned complete
66
- and `get_componentize_plan` proposes nothing more.
62
+ Check: `editor_setup { check: true }` reads `ready`, `get_next_steps` returns
63
+ `receipt.complete: true` and `get_componentize_plan` proposes nothing more. Where the job was to
64
+ convert the source, `submit_conversion_receipt` also returned complete.
67
65
 
68
66
  0. A build brings in only SEO and meta, media, the brand and the models `bcms-content.json`
69
67
  declares. Pages, collections, forms, bindings and components come from `editor_setup`: run it
@@ -79,10 +77,13 @@ and `get_componentize_plan` proposes nothing more.
79
77
  through you. If they are not found there and the site loads its content from another CMS,
80
78
  move that content into BetterCMS ([external-content.md](external-content.md)). Never ask the
81
79
  user for a key or read one: a site left depending on missing keys renders empty or crashes.
82
- 3. Run the `convert_site` prompt end to end. Its finish line is the only one; repeat until the
83
- receipt is complete, never stop at a partial one.
84
- 4. When the answer was components: `get_componentize_plan`, confirm it with the user, apply it
85
- with `componentize_sections`, then the repository half (`componentize_sections { codemod: true }`
80
+ 3. Work from `get_next_steps`: run `receipt.gate.nextAction`, read it again after each batch, and
81
+ repeat until `receipt.complete` is true. On a site set up from its build that gate is the only
82
+ finish line. Run the `convert_site` prompt only when the job is to convert the source (the gate
83
+ asks for it, the owner wants the code to declare its bindings, or the project is headless), and
84
+ then to its own complete receipt, never a partial one.
85
+ 4. With components recorded: `get_componentize_plan`, then apply it with
86
+ `componentize_sections` (the owner can undo it), then the repository half (`componentize_sections { codemod: true }`
86
87
  when there is no checkout) and `submit_componentize_receipt` ([componentize.md](componentize.md)).
87
88
  5. Work the rest through [finish-site.md](finish-site.md) (gap ledger, repair order, proof loop).
88
89
  The gate's `sections` and `style` rows are part of it: a setup whose pages cannot take a new
@@ -96,8 +97,9 @@ Check: the deployed site passes the numbers below on every route.
96
97
 
97
98
  1. Deploy the way this project deploys: `deploy_project` on BetterCMS hosting; on a headless
98
99
  project, push to the repo its host builds (never `deploy_project` there).
99
- 2. `get_binding_report` is the finish line: zero unmatched, an empty `coverage.pending`, and
100
- `unaddressable` below 2% on each route. `refreshReason: "never-generated"` on headless means
100
+ 2. `get_next_steps` reading `receipt.complete: true` is the finish line. `get_binding_report`
101
+ says why a route is short of it: `unaddressable` below 2% on each route, and, on a source you
102
+ converted, zero unmatched and an empty `coverage.pending`. `refreshReason: "never-generated"` on headless means
101
103
  fetch the live site instead.
102
104
  3. Run the verify loop in SKILL.md against the live URL, then the two-path smoke test (click
103
105
  through to a detail page, clean console, direct reload) in [frameworks.md](frameworks.md)
@@ -266,7 +266,7 @@ for a content value the sync got wrong is §4.
266
266
  | Need | Surface | Owner |
267
267
  |---|---|---|
268
268
  | Rebuild or revalidate a site on publish | The publish webhook, signed `X-BetterCMS-Signature` (HMAC-SHA256 of the raw body) with the project's webhook secret ([frameworks.md](frameworks.md) Revalidation) | Secret set in the dashboard; your route verifies it |
269
- | Notify another system of an event | Event webhooks, `list_webhooks` / `create_webhook`: `content.publish`, `content.unpublish`, `content.stage_changed` (workflow stage), `content.created`, `content.updated`, `content.deleted` and `deployment.triggered`; HMAC-SHA256 signed in `X-BetterCMS-Signature`; payloads carry ids, never content. The signing `secret` is returned ONCE, on create. A receiver that writes back on `content.updated` loops unless it skips its own writes. Per-subscription payload filters: dashboard | You |
269
+ | Notify another system of an event | Event webhooks, `list_webhooks` / `create_webhook`: `content.publish`, `content.unpublish`, `content.stage_changed` (workflow stage), `content.created`, `content.updated`, `content.deleted` and `deployment.triggered`; HMAC-SHA256 signed in `X-BetterCMS-Signature`; payloads carry ids, never content: an entry's `content.publish`/`content.unpublish` carries `data.entryId`, a page's carries `data.pageId` and `kind: "page"` (so `entryId` is not always present). The signing `secret` is returned ONCE, on create. A receiver that writes back on `content.updated` loops unless it skips its own writes. Per-subscription payload filters: dashboard | You |
270
270
  | Send form submissions somewhere | Per-form integrations: Google Sheets, Slack, a webhook, email | Dashboard only |
271
271
  | Go live or retire at a time | `scheduledAt` (publish a draft at a time; needs publish permission) and `unpublishAt` (return a published row to draft) on `update_content_entry` and `update_page`; `null` cancels either. Campaigns: `publish_campaign` takes both | You. An edit that omits `scheduledAt` CANCELS the pending publish, so resend it with every edit before it fires; omitting `unpublishAt` never cancels it |
272
272
  | Show one section only for a period | `window` `{ from?, until? }` on a page block (`update_page` `blockJson`) or on a Layout Section (`update_layout` `add-section`, `update-section`). Shown from `from` until `until`; each is an ISO 8601 datetime with an offset (`2026-11-27T00:00:00+01:00`), stored as UTC, and either may be left out. `null` clears it. Refusals: `invalid_window` (no offset, or not a date), `window_inverted` (`from` not before `until`), `window_in_component` (inside a component's `blockJson`: put it on the placement), `window_in_entry` (on a block inside an entry's sections value: put it on the page block or Layout Section that shows the entry). A Section a page adds or customizes takes a window like a Global one | You. Outside its window the block is in no published output: the page, its Markdown, the delivery API and search. A draft preview draws it, marked. A page BetterCMS renders per request changes at the boundary; a static site catches up within about a minute, in place when a section goes and with a build when one appears. Site code that renders its own header ignores a Section's window |
@@ -122,11 +122,11 @@ every entry and are not repeated in the tables. SEO fields are never listed.
122
122
  | Map pin | `location` (lat, lng, optional label). Never two text fields |
123
123
  | Tags, amenities, bullet lists | `array` with `itemType: "text"`. One choice from a fixed list is `select` |
124
124
  | Rows of the same shape (hours, facts, metrics, tickets) | `repeater` of `text`, `number`, `select`, `boolean` children |
125
- | Mixed ordered blocks in a case study | `modular` |
125
+ | Mixed ordered blocks in a case study | one component per block shape, placed as sections |
126
126
  | One or many other entries | `reference`, `multi-reference`. A missing target is refused on write |
127
- | Call to action | `button` (label and link in one value). A bare link is `link`; also `email`, `phone` |
127
+ | Call to action | `button` (label and link in one value). A bare link is `link`; also `email`; a phone number is `text` |
128
128
  | Date, date and time | `date`, `datetime`. Keep the zone as a `text` IANA name beside it when the site shows times |
129
- | A spec or comparison table | `repeater` with one field per column (any field type) and `ui.layout: "table"`. There is no separate table field type |
129
+ | A spec or comparison table | `repeater` with one field per column (any field type). There is no separate table field type |
130
130
 
131
131
  An external system's id (`"store_product_id"`, `"ats_job_id"`, `"feed_id"`, `"lms_course_id"`)
132
132
  is a `text` field kept as migration metadata and locked: `readOnly: "developer"` with a
@@ -261,7 +261,7 @@ Projects collection, `urlPattern` `/work/:slug` (or the established prefix):
261
261
  | `"year"` | `number` | |
262
262
  | `"services"` | `multi-reference` to Services, or `array` of text | References when service pages exist |
263
263
  | `"cover_image"`, `"excerpt"` | `image`, `longtext` | Words baked into a hero image move into fields |
264
- | `"modules"` or `"body"` | `modular`, or `document` | `modular` (text, image, gallery, quote, video, stats blocks) for image-led studies; `document` when mostly prose |
264
+ | `"modules"` or `"body"` | components, or `document` | one component per block (text, image, gallery, quote, video, stats) for image-led studies; `document` when mostly prose |
265
265
  | `"results"` | `repeater`: `"value"`, `"label"` | Only verified outcomes |
266
266
  | `"credits"` | `repeater`: `"role"`, `"name"` | |
267
267
  | `"testimonial"` | `reference` to Testimonials | |
@@ -207,7 +207,6 @@ write new rich text in that shape.
207
207
  | 409 `unique_conflict` | Another entry already holds this value in a unique field. `conflicts` names it | Match to that entry and update it; do not create a twin |
208
208
  | 409 `locale_conflict` | The translation group already has an entry in that locale. `errors` names `locale` or `translationOf` | Edit the existing translation |
209
209
  | 409 `Version conflict` (entries), `VERSION_CONFLICT` (sidebar, section zones) | Someone wrote since your read. The entry body carries `currentVersion` | Read again, re-apply your change to the fresh value, write with the new version |
210
- | 409 `AUTHORING_DECISION_REQUIRED` | Deploy or promote was called before a human chose components or fields. `message` is the question | Ask the user, then `set_authoring_preference`. Do not retry or route around it |
211
210
  | 409 `stale-plan` | The site changed after the componentize plan was read; `digest` is the current one | `get_componentize_plan` again, re-confirm, resubmit with the new digest |
212
211
  | 409 `style-edits` | The conversion receipt lists changed classes or styles. `styleEdits` holds up to 40 tokens | Restore every token, rerun `npx @bettercms-ai/convert --verify-styles`, resubmit the receipt |
213
212
  | 400 `digest-mismatch` | The receipt was built for a different brief | Read the brief again and rebuild the receipt against it |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettercms-ai/mcp",
3
- "version": "0.78.0",
3
+ "version": "0.79.0",
4
4
  "type": "module",
5
5
  "description": "BetterCMS MCP server — schema, content, forms + components authoring tools over the Management API, authed via OAuth device-code.",
6
6
  "bin": {