@bettercms-ai/mcp 0.71.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.
@@ -126,13 +126,30 @@ value to its asset. Check it, never redo it: the binding report's `elementCovera
126
126
  | `IMG_DATA_URI`, `IMG_NO_SOURCE` | An `<img>` inlined as `data:`, or filled in by a script | Ship it as a file with a real `src` |
127
127
  | `BG_PSEUDO_ELEMENT` | A background on `::before` / `::after` | Move it onto the element, or render an `<img>` |
128
128
  | `BG_ON_TEXT` | A background on an element that is itself a text field | Wrap the text, so the box and the words are two elements |
129
- | `SVG_TOO_LARGE` | An inline `<svg>` illustration past the icon size | Ship it as an `.svg` file in an `<img>` |
130
- | `SVG_SPRITE`, `SVG_NOT_ICON`, `SVG_DECORATIVE` | A `<use>` sprite, a chart, a nameless glyph | Decoration: leave it, or give a logo `role="img"` and an `aria-label` |
129
+ | `SVG_TOO_LARGE` | An inline `<svg>` past 64 KB | Ship it as an `.svg` file in an `<img>` |
130
+ | `SVG_SPRITE` | A `<use>` naming a symbol the page does not hold, or a sprite FILE the build does not ship (another host's, a relative path) | Inline the symbol in the page, serve the sprite from the site root, or ship the icon as its own `<svg>` |
131
+ | `IMAGE_NOT_ASSET` | A header or footer picture the Media library holds no asset for | Nothing lost: the rest of the region is the Layout's and this picture stays a field of each page. Setup tries it again on the next build |
132
+ | `SVG_NOT_ICON`, `SVG_DECORATIVE` | An `<svg>` holding a script, an HTML island or a remote image; an `aria-hidden` glyph in the nav or footer, or a nameless one on an older import | Decoration: leave it, or give a logo `role="img"` and an `aria-label` |
131
133
  | `BG_SPRITE`, `BG_TINY`, `BG_PATTERN`, `BG_DATA_URI`, `BG_STATE` | A sprite, an icon-sized box, a tile, an inlined texture, a hover image | Decoration: leave it |
132
134
 
133
- The first four rows count against the image coverage; the last two do not. A gap you cannot
135
+ The first four rows count against the image coverage; the rest do not. A gap you cannot
134
136
  close is reported with its code, never described as done.
135
137
 
138
+ An icon a sprite file draws (`<use href="/sprite.svg#shield">`, a same-site `.svg` the build
139
+ ships) is an image field like any inline icon. An icon a FONT draws (`<i class="ph ph-tag">`) is
140
+ a text field holding the class, written to the `class` attribute alone: change the class to
141
+ change the glyph.
142
+
143
+ `elementGaps` is the same record for everything that is not a picture (`rows` carry a `sample`
144
+ where an image row has a `src`). `EMBED_NOT_VIDEO` (an `<iframe>` that is no video provider's),
145
+ `AUDIO_SOURCE` and `ICON_FONT` (a font icon inside a paragraph that is one rich-text value) are
146
+ shown and editable nowhere, and count against coverage. The rest are declined on purpose and
147
+ never count: `SKIP_LINK`, `FRAMEWORK_UI` (a docs framework's pager, table of contents or
148
+ search), `FRAGMENT_LINK` (an in-page `#` destination under those rules), `ORDINAL_MARKER` (a
149
+ step number the template counts out, on a project imported before step numbers were fields),
150
+ `TEMPLATE_TEXT`, `NOSCRIPT_TEXT` and `HEAD_META_UNMAPPED` (a `<meta>` no native SEO field holds,
151
+ by name).
152
+
136
153
  ## Media host
137
154
 
138
155
  Use `mediaBaseUrl` from `get_project`: media is served from the CDN origin or the API origin of
@@ -42,6 +42,8 @@ webhook. Use the project's webhook secret from the dashboard (the package docs n
42
42
  | `readComponents()` | Component definitions from `bcms-content.json` |
43
43
  | `BcmsField` | `path`, `kind` (`richtext`/`image`), `as`; server-safe; no wrapper around a single DOM element ([animation.md](animation.md) §10) |
44
44
  | `BcmsButton` | A `button` field: `path`, `value`, `className`; one `<a>` with `data-variant` / `data-size`. Style only the looks the site has ([components.md](components.md) §5); server-safe |
45
+ | `BcmsSections`, `BcmsSection` (from `@bettercms-ai/next/sections`) | The page's own sections, drawn in the CMS page's order (below); a server component |
46
+ | `sectionsFor(slug)`, `sectionsStaticProps(slug, own?)`, `sectionsServerSideProps(slug, own)` | The block list, components and forms `BcmsSections` takes, from the snapshot |
45
47
  | `sectionRootAttrs(block)` | Spread on a section root your own code renders: `data-bcms-block`, `data-bcms-style` and the block's style, plus `data-bcms-hide` when `style.hideOn` is set: ship `RESPONSIVE_CSS` (exported here) in your global CSS ([components.md](components.md) §5) |
46
48
  | `bind`, `bindProps` | Binding attributes; instance-aware inside components |
47
49
  | `BcmsForm` + `getForm(idOrName)` | Forms from the snapshot ([forms-and-interactions.md](forms-and-interactions.md)) |
@@ -60,6 +62,63 @@ Rules:
60
62
  - The package ships no sitemap or robots helper. Write `app/sitemap.ts` and `app/robots.ts`
61
63
  yourself from published content, and leave out pages marked noindex.
62
64
 
65
+ ## Sections in the CMS order
66
+
67
+ A page whose sections are written in its own file follows the CMS page's block list through
68
+ `<BcmsSections>`. `bettercms init` writes this wrap (`npx @bettercms-ai/convert --sections`);
69
+ write it by hand for a page the codemod refused ([componentize.md](componentize.md) §5).
70
+
71
+ App Router, in a server component:
72
+
73
+ ```tsx
74
+ import { sectionsFor } from "@bettercms-ai/next";
75
+ import { BcmsSections, BcmsSection } from "@bettercms-ai/next/sections";
76
+
77
+ export default function Home() {
78
+ return (
79
+ <main>
80
+ <BcmsSections {...sectionsFor("home")}>
81
+ <BcmsSection bind="hero">{(root) => <Hero rootProps={root} />}</BcmsSection>
82
+ <BcmsSection bind="code:sec1"><LogoCloud /></BcmsSection>
83
+ <BcmsSection bind="features"><section className="features">…</section></BcmsSection>
84
+ </BcmsSections>
85
+ </main>
86
+ );
87
+ }
88
+ ```
89
+
90
+ Pages Router: the page also renders in the browser, so the list is a page prop.
91
+
92
+ ```tsx
93
+ import { sectionsStaticProps, type SectionsData } from "@bettercms-ai/next";
94
+ import { BcmsSections, BcmsSection } from "@bettercms-ai/next/sections";
95
+
96
+ export default function Home({ bcmsSections }: { bcmsSections: SectionsData }) {
97
+ return <BcmsSections {...bcmsSections}>…</BcmsSections>;
98
+ }
99
+ export const getStaticProps = sectionsStaticProps("home");
100
+ ```
101
+
102
+ A page with its own data function passes it in, and the list joins the props it answers:
103
+ `export const getStaticProps = sectionsStaticProps("home", pageStaticProps)`, or
104
+ `sectionsServerSideProps` for `getServerSideProps`. `--sections` writes this for you.
105
+
106
+ - A bind is the family of the section's first bound field (`features-title` → `features`): the
107
+ `bind` on the page's `component` block. A block bound to a section renders it at the block's
108
+ position; a section added in the editor (no bind) renders through `BcmsBlocks`.
109
+ - The stamp goes on the section's own root. One DOM element (`<section>`) gets
110
+ `data-bcms-block`, `data-bcms-style` and the block's style cloned onto it. A COMPONENT child
111
+ cannot be handed unknown props, so write it as a function and spread `root` on the element the
112
+ component renders (`<section {...rootProps}>`). Without the function it is stamped on a box:
113
+ `display:contents` while its block has no style, one `display:flow-root` box carrying the
114
+ style once it has ([animation.md](animation.md) §10).
115
+ - A section no block names: `code:<bind>` always renders where it is written. A bare `<bind>`
116
+ does too until the list names one of the page's sections; from then on it is a removed one.
117
+ Write a bind bare only when the page already places it (`get_page`), otherwise `code:`.
118
+ - `blocks` null (no snapshot) renders every child in source order, untouched.
119
+ - Never inside a `"use client"` file: it reads its children's own elements, which a client
120
+ component cannot do across the server boundary.
121
+
63
122
  ## Lists, pagination and missing content
64
123
 
65
124
  - `listEntries(model, { page, perPage })`: `page` is 1-based; `perPage` defaults to 20 and the
@@ -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,14 +93,52 @@ 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
+
82
129
  ### A section change that is held at the write
83
130
 
84
- `update_page` and `compose_pages` save the draft and answer with `holds` when a `blockJson` adds,
85
- moves or removes a section the live page cannot show yet. Each hold has a `reason`, a `message`,
86
- the `sections` it is about and the one `action`. Never report such a write as done:
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:
87
134
 
88
135
  - `CODE_FIXED_SECTIONS`: the site's code fixes this page's sections, so no publish ships the
89
- change. The action is `componentize_sections` with `codemod: true`; after its build, publish.
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.
90
142
  - `SECTION_NEEDS_BUILD`: the build draws the page's sections from BetterCMS. Publish: the build
91
143
  starts by itself, and a move or a removal needs none.
92
144
  - `STRUCTURE_UNMEASURED`: nothing has measured the page. Publish, then read the publish's outcome
@@ -117,13 +169,29 @@ the `sections` it is about and the one `action`. Never report such a write as do
117
169
  | `VALUE_REFUSED` | held | Bind the field to an element that can hold its value |
118
170
  | `APP_DOES_NOT_READ` | held | Convert in code: make the page's code read the published field |
119
171
  | `ROWS_NEED_BUILD`, `SECTION_NEEDS_BUILD`, `NEEDS_BUILD` | held | Wait for the build the publish started |
120
- | `STRUCTURE_UNBOUND` | refused | Let BetterCMS control section order (componentize with the codemod) |
172
+ | `STRUCTURE_UNBOUND` | refused | The fix the route's `sections` gate row names: the in-place switch, or the sections conversion |
121
173
  | `SECTION_MISSING` | held | The same: the served page does not render the section |
122
- | `STYLE_NOT_PAINTABLE` | held | Run editor setup so section roots carry `sectionRootAttrs` |
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)) |
123
177
  | `WINDOW_NOT_OPEN` | held | Nothing: it goes live when its window opens |
124
178
  | `ROUTE_NOT_OWNED` | held | Change one of the two page paths that share the URL |
125
179
  | `APP_UNREACHABLE`, `NOT_CHECKED`, `EDGE_PENDING`, `IMAGE_UNAVAILABLE`, `STRUCTURE_UNMEASURED` | unverified | Poll again; publish again if it stays |
126
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 |
194
+
127
195
  ### Deploy to Vercel or Netlify
128
196
 
129
197
  To serve a `managed` or `hybrid` site from the owner's own Vercel or Netlify account, call
@@ -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 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettercms-ai/mcp",
3
- "version": "0.71.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": {