@bettercms-ai/mcp 0.70.0 → 0.72.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.
@@ -35,7 +35,8 @@ that reads them.
35
35
  - Publishing copies the stored draft to live, so what you checked in the draft is what ships.
36
36
  - **Many at once**: `publish_entries` { action: 'publish' | 'unpublish', entryIds, pageIds } takes
37
37
  up to 100 of each in one call (pages publish only). Each item still passes its own gates, and
38
- nothing stops the run: read `failed` for every refusal and its reason.
38
+ nothing stops the run: read `failed` for every refusal and its reason, then `outcome` for what
39
+ the served pages show (the same shape `update_page` returns on a publish, below).
39
40
  - **Throw away a draft**: `discard_draft` with `entryId` or `pageId` returns it to exactly what is
40
41
  live; a page also drops its unpublished Layout overrides (`no_published_version` if it never
41
42
  went live: delete it instead).
@@ -64,6 +65,19 @@ renders rows no address names, or was built empty). That publish waits for its b
64
65
  the list shows N rows") and fails with the reason if the build still shows the page's own rows: the
65
66
  site's code renders the list itself. `get_next_steps` names what is waiting.
66
67
 
68
+ A static page whose own code fixes its sections takes all of it in place too, when the project's
69
+ in-place structure switch is on: the publish writes the page's block list into the built HTML.
70
+ The code's bands stand in the published order, a removed one is cut, a section added in
71
+ BetterCMS is drawn between them, and every root takes its block's style. No repository change
72
+ and no build; each rebuild is put right the same way. The gate's `sections` row says which
73
+ routes take it (`accepts: "in-place"`).
74
+
75
+ A collection's item pages take it per TEMPLATE: the collection page's sections are the
76
+ template's, held once, so a section added to that page (`update_page` on the collection page)
77
+ stands on every entry's route, and an entry has no sections of its own. Its publish reads one
78
+ row for all of them (`route` `blog/*`). The entry's body is a section too: add sections around
79
+ it or move it, and space it through its field's style, since its element is the field's.
80
+
67
81
  The SEO a page or entry stores is what its `<head>` says, whenever it was set: the title, the
68
82
  description, the canonical, the card fields, a `noindex` that is on. A field left empty keeps what
69
83
  the build rendered, and a site default only fills a tag the build has none of. Only a real choice
@@ -79,6 +93,57 @@ Builds run `build:bcms` instead of `build` when `package.json` declares it. Keep
79
93
  of it and every step the build needs in: `"build": "astro check && astro build"` beside
80
94
  `"build:bcms": "astro build"`. The convert codemod adds one to a repo whose build chains a check.
81
95
 
96
+ ### Add a section, or set its spacing
97
+
98
+ One call each, on any page (a page BetterCMS draws, a code-built one, a collection template):
99
+
100
+ - **Add a section here**: `update_page { pageId, blockJson: { insertSection: { componentId, after: "<block id>" } } }`.
101
+ `componentId` is a component from `list_components`; a `builtin:*` id is copied into the project
102
+ once and the copy is placed. Place it with `at` (an index into the page's sections), `before`
103
+ or `after` (a section's block id from `get_page`); none of the three adds it last. `style` and
104
+ `overrides` set its block style and its copy in the same call.
105
+ - **Set spacing on this section**: `update_page { pageId, blockJson: { sectionStyle: { blockId, style } } }`.
106
+ Each key of `style` replaces the stored one and `null` unsets it: `paddingTop`, `paddingBottom`,
107
+ `paddingSides` (px, 0 to 240), `spaceTop`, `spaceBottom`, `spaceSides` (the spacing tokens),
108
+ `bg`, `bgCustom`, `contentWidth`, `contentAlign`, `theme`, `corner`, `shadow`, `border`,
109
+ `opacity`, `hideOn`. A key the block style does not have is refused (`INVALID_STYLE`).
110
+
111
+ The server edits the page as stored, so nothing else on it is resent or overwritten; send
112
+ `ifMatch` to refuse a page that changed since you read it. Both save a DRAFT: add
113
+ `status: "published"` to the same call, or publish after. Then read the answer, top down:
114
+
115
+ 1. `held`: the draft is saved and the live page will NOT show it. It names the reason and the one
116
+ action (below). Do the action; do not report the change as made.
117
+ 2. `outcome` (on a publish): `status` `live` is every value found on the served page. `partial`
118
+ lists what is not in `notLive`, each with its `reason` and `action` (the table under Reading
119
+ `get_deploy_status`). `next: "get_deploy_status"` means it is not judged whole: nothing has
120
+ looked at it yet, or a section change is still being written (`NOT_CHECKED`). It resolves by
121
+ itself to live or held: poll `publishes` there before you say anything is live.
122
+ 3. `section`: the block's `id` and `index`, to place the next one beside it.
123
+ 4. `warnings`: `COMPONENT_DRAFT` is a section that renders nothing until `publish_component`.
124
+
125
+ Refusals: `SECTION_NOT_FOUND` (the answer lists the page's section ids), `POSITION_OUT_OF_RANGE`,
126
+ `COMPONENT_NOT_FOUND`, `BUILTIN_NOT_FOUND`. A whole `blockJson` array still replaces the page's
127
+ sections; use it to move or remove one, with `ifMatch`.
128
+
129
+ ### A section change that is held at the write
130
+
131
+ `update_page` and `compose_pages` save the draft and answer `held` first (with `holds`) when a
132
+ write adds, moves or removes a section the live page cannot show yet. Each hold has a `reason`,
133
+ a `message`, the `sections` it is about and the one `action`. Never report such a write as done:
134
+
135
+ - `CODE_FIXED_SECTIONS`: the site's code fixes this page's sections, so no publish ships the
136
+ change. Read the route's `sections` row in `get_next_steps` (`receipt.gate`): its `fixCode`
137
+ names the lane ([finish-site.md](finish-site.md)). `enable-in-place-structure` is the user's
138
+ switch and needs no code. `run-sections-conversion` is the wrap: `componentize_sections` with
139
+ `codemod: true` and the `digest` of `get_componentize_plan`, or, when that answers `skipped`
140
+ (no repository the platform may commit to), `npx @bettercms-ai/convert@latest --sections --root .`
141
+ in a checkout ([componentize.md](componentize.md) §5). After its build, publish.
142
+ - `SECTION_NEEDS_BUILD`: the build draws the page's sections from BetterCMS. Publish: the build
143
+ starts by itself, and a move or a removal needs none.
144
+ - `STRUCTURE_UNMEASURED`: nothing has measured the page. Publish, then read the publish's outcome
145
+ in `get_deploy_status`.
146
+
82
147
  ### Reading `get_deploy_status`
83
148
 
84
149
  - `release` is the newest release: `id`, `sha`, `status`, `phaseMarks`, `error`, `createdAt`.
@@ -92,6 +157,40 @@ of it and every step the build needs in: `"build": "astro check && astro build"`
92
157
  and ready on staging while production keeps the older sha, until `promote_project` runs.
93
158
  - The poll is finished only when `slot` is `current`, or when `awaitingPromote` is false and
94
159
  `productionSha` equals the sha you deployed.
160
+ - `publishes` is the newest publishes, value by value. `status` `live` means every published value
161
+ was found in the file the site serves that page from. `partial` means some were not: `summary`
162
+ counts them ("3 of 4 live · 1 held") and `notLive` lists each with its `route`, `address`,
163
+ `state`, `reason` and the one `action` that fixes it. Never tell the user a publish is live
164
+ while it reads `partial`: relay the reason and do the action, then poll again.
165
+
166
+ | `reason` | `state` | The one action |
167
+ |---|---|---|
168
+ | `NOT_STAMPED` | held | Rebuild: the built page has no element for the field and does not show it |
169
+ | `VALUE_REFUSED` | held | Bind the field to an element that can hold its value |
170
+ | `APP_DOES_NOT_READ` | held | Convert in code: make the page's code read the published field |
171
+ | `ROWS_NEED_BUILD`, `SECTION_NEEDS_BUILD`, `NEEDS_BUILD` | held | Wait for the build the publish started |
172
+ | `STRUCTURE_UNBOUND` | refused | The fix the route's `sections` gate row names: the in-place switch, or the sections conversion |
173
+ | `SECTION_MISSING` | held | The same: the served page does not render the section |
174
+ | `SECTION_NOT_ON_PAGE` | held | In place: a section of the site's own code has no band on this page. The sections conversion, or remove the placement |
175
+ | `SECTION_NOT_RENDERABLE` | held | In place: the added section's component is not published, or it needs a form. `publish_component`, then publish the page again |
176
+ | `STYLE_NOT_PAINTABLE` | held | Rebuild once the section root carries the style marker (`sectionRootAttrs`, or the codemod's `--upgrade-sections`: [components.md](components.md)) |
177
+ | `WINDOW_NOT_OPEN` | held | Nothing: it goes live when its window opens |
178
+ | `ROUTE_NOT_OWNED` | held | Change one of the two page paths that share the URL |
179
+ | `APP_UNREACHABLE`, `NOT_CHECKED`, `EDGE_PENDING`, `IMAGE_UNAVAILABLE`, `STRUCTURE_UNMEASURED` | unverified | Poll again; publish again if it stays |
180
+
181
+ Each row's `action.code` is one of these, and `get_next_steps` lists every held or refused value
182
+ as `publish-held` until it is gone:
183
+
184
+ | `action.code` | What you do |
185
+ |---|---|
186
+ | `rebuild` | No tool rebuilds: change the site's code if the reason names it, then ask the user to Rebuild from Publishing (or `deploy_project` from a checkout) |
187
+ | `wait_for_build` | Poll `get_deploy_status` until the build the publish started is live |
188
+ | `convert_in_code` | Make the page's code read the field ([bindings.md](bindings.md)), then release |
189
+ | `control_section_order` | The route's `sections` gate row names it: the in-place switch (the user's), or `componentize_sections` with `codemod: true` (else `convert --sections` in a checkout), then release |
190
+ | `editor_setup` | `editor_setup` |
191
+ | `rebind` | Bind the field to an element that can hold it, in the site's code |
192
+ | `resolve_route` | Change one of the two page paths |
193
+ | `wait`, `retry` | Poll again; publish again if it stays |
95
194
 
96
195
  ### Deploy to Vercel or Netlify
97
196
 
@@ -53,7 +53,8 @@ values as they are; rewriting copy is a separate, reported recommendation.
53
53
  | `<meta name="description">` | `metaDescription` | Copy verbatim. Aim for 120–160 characters; the audit flags only a missing one and one over 160. When the source has none, take a `generate_seo_meta` suggestion and list it for review |
54
54
  | `og:title`, `og:description`, `og:image`, `og:type` | `og.title`, `og.description`, `og.image`, `og.type` | Write a key only when it differs from the meta title, the description or the site default. Upload the image first and store its media URL |
55
55
  | `twitter:card`, `twitter:title`, `twitter:description`, `twitter:image` | `twitter.card`, `twitter.title`, `twitter.description`, `twitter.image` | Same rule as OG |
56
- | `twitter:site` | `update_seo` `seoDefaults.twitterHandle` | Once per site |
56
+ | `twitter:site` | `update_seo` `seoDefaults.twitterHandle` | Once per site. A BetterCMS-built import fills it, the card and `og:type` from the home page's head where the project holds none |
57
+ | `keywords`, `author`, `theme-color`, a site verification tag | nothing | No native key stores them. The binding report's `elementGaps` lists each by name (`HEAD_META_UNMAPPED`); report them |
57
58
  | A site-wide share image | `update_seo` `seoDefaults.ogImage` | Then pages inherit it and `og.image` stays empty |
58
59
  | Canonical pointing at the page itself | nothing | A hosted page gets its own canonical |
59
60
  | Canonical pointing at another URL | `canonical` | Keep it: someone consolidated those URLs on purpose |
@@ -109,8 +109,8 @@ Value (`set_page_content`):
109
109
  "hero_eyebrow": "Freight visibility for mid-size shippers",
110
110
  "hero_title": "Know where every container is before your customer asks.",
111
111
  "hero_lede": "Live ETAs from 140 carriers, delays flagged before they cost you, and one shared view for ops and sales.",
112
- "hero_primary": { "label": "Book a demo", "href": "/demo", "variant": "primary", "arrow": true },
113
- "hero_secondary": { "label": "Start free trial", "href": "https://app.kestrelfreight.com/signup", "variant": "secondary" },
112
+ "hero_primary": { "label": "Book a demo", "href": "/demo", "arrow": true },
113
+ "hero_secondary": { "label": "Start free trial", "href": "https://app.kestrelfreight.com/signup" },
114
114
  "hero_image": {
115
115
  "url": "/img/hero-harbor-2560.jpg",
116
116
  "altText": "Container cranes over a quiet harbor at dusk",
@@ -149,6 +149,8 @@ Bound template (classes unchanged, shortened here):
149
149
  - The image binding sits on the `<img>`, not the `<picture>`. The alt text travels in the image
150
150
  value (`altText`): a publish writes it to the bound element's `alt`. When `get_conversion_brief`
151
151
  lists a separate alt path, declare that path in `data-bcms-props` with kind `text`.
152
+ - Neither button value names a `variant`: the anchors keep the site's own `btn-primary` and
153
+ `btn-ghost`, and the release reads those two looks into the project's Button family.
152
154
  - An `href` is kind `url`, never `text`. The label gets its own `<span>` so a save does not erase
153
155
  the arrow; the arrow is drawn by `BcmsButton` ([components.md](components.md)), so remove the
154
156
  hand-written `<svg>` once the button renders through it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettercms-ai/mcp",
3
- "version": "0.70.0",
3
+ "version": "0.72.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": {
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@bettercms-ai/device-auth": "^0.2.0",
35
- "@bettercms-ai/sdk": "^1.28.0",
35
+ "@bettercms-ai/sdk": "^1.29.0",
36
36
  "@modelcontextprotocol/sdk": "^1.30.0",
37
37
  "zod": "^4.3.6"
38
38
  },