@bettercms-ai/mcp 0.83.0 → 0.83.2

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.
Files changed (40) hide show
  1. package/README.md +86 -86
  2. package/SKILL.md +160 -160
  3. package/dist/index.js +38 -9
  4. package/dist/index.js.map +1 -1
  5. package/dist/kit-catalog.json +1509 -1509
  6. package/dist/skills/bettercms/SKILL.md +206 -206
  7. package/dist/skills/bettercms/references/accessibility.md +171 -171
  8. package/dist/skills/bettercms/references/animation.md +296 -296
  9. package/dist/skills/bettercms/references/astro.md +148 -148
  10. package/dist/skills/bettercms/references/bindings.md +366 -366
  11. package/dist/skills/bettercms/references/componentize.md +185 -185
  12. package/dist/skills/bettercms/references/components.md +415 -415
  13. package/dist/skills/bettercms/references/content-experiments.md +62 -62
  14. package/dist/skills/bettercms/references/content-modeling.md +382 -382
  15. package/dist/skills/bettercms/references/detection.md +465 -465
  16. package/dist/skills/bettercms/references/dock-input.md +121 -121
  17. package/dist/skills/bettercms/references/editor-chrome.md +92 -92
  18. package/dist/skills/bettercms/references/external-content.md +78 -78
  19. package/dist/skills/bettercms/references/finish-site.md +377 -377
  20. package/dist/skills/bettercms/references/forms-and-interactions.md +245 -245
  21. package/dist/skills/bettercms/references/frameworks.md +159 -159
  22. package/dist/skills/bettercms/references/getting-started.md +156 -156
  23. package/dist/skills/bettercms/references/integrations.md +364 -364
  24. package/dist/skills/bettercms/references/kit-catalog.md +3259 -3259
  25. package/dist/skills/bettercms/references/kit-scenarios.md +712 -712
  26. package/dist/skills/bettercms/references/localization.md +85 -85
  27. package/dist/skills/bettercms/references/media-and-brand.md +181 -181
  28. package/dist/skills/bettercms/references/next.md +189 -189
  29. package/dist/skills/bettercms/references/other-frameworks.md +85 -85
  30. package/dist/skills/bettercms/references/portable-text.md +213 -213
  31. package/dist/skills/bettercms/references/programmatic-seo.md +98 -98
  32. package/dist/skills/bettercms/references/publishing.md +289 -289
  33. package/dist/skills/bettercms/references/seo.md +357 -357
  34. package/dist/skills/bettercms/references/shopify-headless.md +52 -52
  35. package/dist/skills/bettercms/references/site-blueprints.md +895 -895
  36. package/dist/skills/bettercms/references/site-review.md +74 -74
  37. package/dist/skills/bettercms/references/worked-examples-advanced.md +509 -509
  38. package/dist/skills/bettercms/references/worked-examples.md +885 -885
  39. package/dist/skills/bettercms/references/write-limits.md +217 -217
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -1,86 +1,86 @@
1
- # @bettercms-ai/mcp
2
-
3
- BetterCMS [Model Context Protocol](https://modelcontextprotocol.io) server. Lets an
4
- AI assistant author your content schema and entries through the BetterCMS Management
5
- API, authenticated with a short-lived, per-project `content:manage` key minted via
6
- the OAuth 2.0 Device Authorization Grant (RFC 8628) — no pasted long-lived key.
7
-
8
- ## Tools
9
-
10
- | Tool | Description |
11
- |------|-------------|
12
- | `create_model` | Create a content model (schema) with optional initial fields. |
13
- | `add_field` | Append a field to an existing model (read-modify-write; never removes fields). |
14
- | `create_entry` | Create a content entry; applies `data`/`status` via a follow-up update. |
15
- | `create_form` / `update_form` | Author a form (fields + settings) to embed with `<BcmsForm>`. |
16
- | `create_component` / `update_component` | Author a reusable component (blockJson tree + props) to render with `<BcmsBlocks>`. |
17
- | `list_forms` / `get_form` / `list_components` / `get_component` | Discover forms & components to wire into site code. |
18
-
19
- See [SKILL.md](./SKILL.md) for how an AI agent should author pages, forms, and components.
20
-
21
- All operations are **additive** — there are no destructive tools. Deleting models,
22
- fields, or entries is dashboard-only by design.
23
-
24
- ## Usage
25
-
26
- Add to your MCP client config (e.g. Claude Desktop / Claude Code):
27
-
28
- ```json
29
- {
30
- "mcpServers": {
31
- "bettercms": {
32
- "command": "npx",
33
- "args": ["-y", "@bettercms-ai/mcp"],
34
- "env": { "BETTERCMS_API_URL": "https://api.bettercms.ai" }
35
- }
36
- }
37
- }
38
- ```
39
-
40
- ### Registry auth (current)
41
-
42
- This package is published to **GitHub Packages**, which requires authentication
43
- **even for public scoped packages**. For `npx @bettercms-ai/mcp` to resolve, the
44
- host machine needs an `~/.npmrc` with:
45
-
46
- ```
47
- @bettercms-ai:registry=https://npm.pkg.github.com/
48
- //npm.pkg.github.com/:_authToken=<a GitHub PAT with read:packages>
49
- ```
50
-
51
- > **TODO (future):** publish `@bettercms-ai/mcp` (and `types`/`sdk`) to the public
52
- > **npmjs.org** registry so `npx @bettercms-ai/mcp` works with zero `.npmrc` setup
53
- > for any host/user. Until then the GitHub Packages auth above is required.
54
-
55
- On the first tool call the server prints an authorization prompt to **stderr**
56
- (visible in your MCP client's logs):
57
-
58
- ```
59
- ┌─ BetterCMS authorization required ─────────────────────────
60
- │ Visit: https://bettercms.ai/activate
61
- │ Enter code: WDJB-MJHT
62
- │ Or open: https://bettercms.ai/activate?code=WDJB-MJHT
63
- └────────────────────────────────────────────────────────────
64
- ```
65
-
66
- Approve it in the dashboard (pick a workspace/project) and the server caches the
67
- minted token. It auto-refreshes on expiry; if the refresh token is revoked it
68
- re-runs the device flow.
69
-
70
- ## Environment
71
-
72
- | Var | Default | Purpose |
73
- |-----|---------|---------|
74
- | `BETTERCMS_API_URL` | `https://api.bettercms.ai` | API origin (device-auth + Management API). |
75
- | `BETTERCMS_MCP_CREDENTIALS` | `~/.bettercms/mcp-credentials.json` | Token cache path (0600). |
76
- | `BETTERCMS_MCP_CLIENT_NAME` | `BetterCMS MCP` | Label shown on the approval screen. |
77
- | `BETTERCMS_MCP_TOOLS` | _(unset)_ | Toolsets to register, a comma list: `core` (core tools only), `abm` (adds the ABM campaign lane), `all`. Unset registers every tool except the ABM lane. The hosted server takes the same list as `?tools=`. |
78
-
79
- ## Develop
80
-
81
- ```bash
82
- bun install
83
- bun run --filter @bettercms-ai/mcp typecheck
84
- bun run --filter @bettercms-ai/mcp test
85
- bun run --filter @bettercms-ai/mcp build
86
- ```
1
+ # @bettercms-ai/mcp
2
+
3
+ BetterCMS [Model Context Protocol](https://modelcontextprotocol.io) server. Lets an
4
+ AI assistant author your content schema and entries through the BetterCMS Management
5
+ API, authenticated with a short-lived, per-project `content:manage` key minted via
6
+ the OAuth 2.0 Device Authorization Grant (RFC 8628) — no pasted long-lived key.
7
+
8
+ ## Tools
9
+
10
+ | Tool | Description |
11
+ |------|-------------|
12
+ | `create_model` | Create a content model (schema) with optional initial fields. |
13
+ | `add_field` | Append a field to an existing model (read-modify-write; never removes fields). |
14
+ | `create_entry` | Create a content entry; applies `data`/`status` via a follow-up update. |
15
+ | `create_form` / `update_form` | Author a form (fields + settings) to embed with `<BcmsForm>`. |
16
+ | `create_component` / `update_component` | Author a reusable component (blockJson tree + props) to render with `<BcmsBlocks>`. |
17
+ | `list_forms` / `get_form` / `list_components` / `get_component` | Discover forms & components to wire into site code. |
18
+
19
+ See [SKILL.md](./SKILL.md) for how an AI agent should author pages, forms, and components.
20
+
21
+ All operations are **additive** — there are no destructive tools. Deleting models,
22
+ fields, or entries is dashboard-only by design.
23
+
24
+ ## Usage
25
+
26
+ Add to your MCP client config (e.g. Claude Desktop / Claude Code):
27
+
28
+ ```json
29
+ {
30
+ "mcpServers": {
31
+ "bettercms": {
32
+ "command": "npx",
33
+ "args": ["-y", "@bettercms-ai/mcp"],
34
+ "env": { "BETTERCMS_API_URL": "https://api.bettercms.ai" }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ### Registry auth (current)
41
+
42
+ This package is published to **GitHub Packages**, which requires authentication
43
+ **even for public scoped packages**. For `npx @bettercms-ai/mcp` to resolve, the
44
+ host machine needs an `~/.npmrc` with:
45
+
46
+ ```
47
+ @bettercms-ai:registry=https://npm.pkg.github.com/
48
+ //npm.pkg.github.com/:_authToken=<a GitHub PAT with read:packages>
49
+ ```
50
+
51
+ > **TODO (future):** publish `@bettercms-ai/mcp` (and `types`/`sdk`) to the public
52
+ > **npmjs.org** registry so `npx @bettercms-ai/mcp` works with zero `.npmrc` setup
53
+ > for any host/user. Until then the GitHub Packages auth above is required.
54
+
55
+ On the first tool call the server prints an authorization prompt to **stderr**
56
+ (visible in your MCP client's logs):
57
+
58
+ ```
59
+ ┌─ BetterCMS authorization required ─────────────────────────
60
+ │ Visit: https://bettercms.ai/activate
61
+ │ Enter code: WDJB-MJHT
62
+ │ Or open: https://bettercms.ai/activate?code=WDJB-MJHT
63
+ └────────────────────────────────────────────────────────────
64
+ ```
65
+
66
+ Approve it in the dashboard (pick a workspace/project) and the server caches the
67
+ minted token. It auto-refreshes on expiry; if the refresh token is revoked it
68
+ re-runs the device flow.
69
+
70
+ ## Environment
71
+
72
+ | Var | Default | Purpose |
73
+ |-----|---------|---------|
74
+ | `BETTERCMS_API_URL` | `https://api.bettercms.ai` | API origin (device-auth + Management API). |
75
+ | `BETTERCMS_MCP_CREDENTIALS` | `~/.bettercms/mcp-credentials.json` | Token cache path (0600). |
76
+ | `BETTERCMS_MCP_CLIENT_NAME` | `BetterCMS MCP` | Label shown on the approval screen. |
77
+ | `BETTERCMS_MCP_TOOLS` | _(unset)_ | Toolsets to register, a comma list: `core` (core tools only), `abm` (adds the ABM campaign lane), `all`. Unset registers every tool except the ABM lane. The hosted server takes the same list as `?tools=`. |
78
+
79
+ ## Develop
80
+
81
+ ```bash
82
+ bun install
83
+ bun run --filter @bettercms-ai/mcp typecheck
84
+ bun run --filter @bettercms-ai/mcp test
85
+ bun run --filter @bettercms-ai/mcp build
86
+ ```
package/SKILL.md CHANGED
@@ -1,160 +1,160 @@
1
- # BetterCMS MCP — authoring skill
2
-
3
- How an AI agent should author content, schema, **forms**, and **components** in
4
- BetterCMS through this MCP. The MCP is bound to ONE project (the API key's project);
5
- everything you create lands there.
6
-
7
- The guided prompts ship with the server — prefer them: `studio` (router),
8
- `propose_schema`, `new_page`, `new_form`, `new_component`. This file is the reference
9
- behind them.
10
-
11
- ## Golden rules
12
- - **Confirm before creating.** Propose the shape (fields / block tree) and get the
13
- user's approval (AskUserQuestion) before any create/update. Never silently guess.
14
- - **Read before you edit.** Use the `list_*` / `get_*` tools first so you keep existing
15
- keys/blocks and don't duplicate.
16
- - **Additive where possible.** `add_field` appends (to a model or a page); passing `fields`
17
- to `update_form` or `blockJson` to `update_component` REPLACES — include everything
18
- you want to keep.
19
- - **Drafts are invisible.** A component you create is a DRAFT, and a draft component
20
- renders as an empty string on the live site — no error, no placeholder. Call
21
- `publish_component` on every component you place, then publish the page.
22
-
23
- ## Pages & schema
24
- Decide the architecture before the first call. A marketing site is **components-first**:
25
- a library of `create_component` sections, each with a `sectionType` and a library
26
- category, placed on pages via `create_page`'s `blockJson` as `component` blocks. A blog or
27
- catalogue is **schema-first**: a `create_content_model` collection whose body field is
28
- `document` (the article canvas — pages or collections, one per schema, top level; cannot coexist with a Section zone).
29
-
30
- Within a page's own schema, destructure into a tree: plain fields, **group** (one nested
31
- object), **repeater** (a repeatable array). Never a 1-item repeater, never a repeater of a
32
- single text (that is `array`).
33
-
34
- ### A page is a list of SECTIONS. Never a list of loose blocks.
35
-
36
- The top level of a page's `blockJson` holds only `component` blocks or `section` blocks —
37
- never a bare `heading`/`text`/`image`/`button`/`spacer`.
38
-
39
- **Why it matters, concretely:** the visual editor derives one section per TOP-LEVEL block. A
40
- hero authored as four loose blocks (headline, lede, two CTAs) becomes four separate sections,
41
- each with its own move / duplicate / delete controls and its own "Add section here" gap — and
42
- because each top-level block gets its own block box, the two call-to-action buttons stop being
43
- inline siblings and **stack onto separate lines**. That is not a styling bug to chase in CSS;
44
- it is the page telling you it was authored flat.
45
-
46
- ```jsonc
47
- // ✅ one section, four children — one set of controls, CTAs side by side
48
- { "type": "section", "id": "hero",
49
- "style": { "bg": "surface", "paddingTop": 96, "paddingBottom": 96, "contentWidth": "default" },
50
- "props": { "children": [
51
- { "type": "heading", "id": "hero-title", "props": { "text": "Strategic Planning.", "level": 1 } },
52
- { "type": "text", "id": "hero-lede", "props": { "html": "<p>One place to plan, track and ship.</p>" } },
53
- { "type": "button", "id": "hero-cta", "props": { "text": "Start a project", "href": "/contact" } },
54
- { "type": "button", "id": "hero-cta2", "props": { "text": "See our work", "href": "/work", "variant": "secondary" } }
55
- ] } }
56
- ```
57
-
58
- **Two CTAs are two sibling `button` blocks.** Not a `columns` block — `columns` renders
59
- `grid-template-columns: repeat(N, 1fr)`, so each CTA would stretch to half the container.
60
- Buttons are inline-level and flow side by side on their own. This is what the shipped
61
- `builtin:hero-centered` section does.
62
-
63
- ### `section` block or `component`? Decide by REUSE.
64
-
65
- | | use it when | the catch |
66
- |---|---|---|
67
- | **`component`** (+ `sectionType`) | the band appears on more than one page, or needs layout variants — a Hero that is "Centered" on Home and "Two-column" on About | its children render **without field bindings**, so their text is **not click-to-edit on the canvas**. It is edited through the component's declared `props` in the section dock, where an unset prop shows empty and inherits the definition's default. Authors who want the current copy visible in the dock should set the prop explicitly rather than relying on the inherited default. |
68
- | **`section` block** | a genuinely one-off band on a single page | no family name and no variant switcher, and the editor's "Add a section" picker cannot insert one — only components |
69
-
70
- `component` is the shape the product's reuse machinery is built around: variants, the section
71
- picker and the family name all key off `componentId`. So reach for it whenever a band actually
72
- recurs — and when you do, **declare a prop for every string, link and image a marketer will
73
- ever touch**. A component with un-propped editable copy is the defect, not the component.
74
-
75
- ### Don't hand-write a hero. Start from a built-in section.
76
-
77
- There are built-in section components — `hero-centered`, `hero-split`, `feature-grid-three`,
78
- `feature-grid-two`, `faq-list`, `stats-row`, `logo-cloud-row`, `testimonial-quote`,
79
- `case-study-story`, `cta-banner`, `cta-split`, `pricing-three`, `contact-simple` — available to
80
- every project (`list_components` returns them; they have no `projectId`). Every one is already
81
- rooted in a `section` block with its editable leaves declared as props, and `Hero` ships two
82
- variants, so the shapes above are worked examples you can read.
83
-
84
- Two sanctioned recipes, and which one you want follows the reuse rule above:
85
-
86
- 1. **Inline it** — copy the builtin's `section` blockJson onto the page as a `section` block and
87
- substitute your values. Standard path for a one-off band; children stay click-to-edit.
88
- 2. **Reference it** — place `{ type: "component", props: { componentId, overrides } }`. Use this
89
- when the band recurs across pages or needs the variant switcher.
90
-
91
- Authoring a hero's JSON from scratch is the slow way to reinvent recipe 1 with more mistakes.
92
-
93
- The full decision tree, section anatomy, variant rules and build order live in the
94
- `propose_schema` prompt and in the `bettercms://playbook/schema` resource.
95
-
96
- ## Forms
97
- Tools: `list_forms`, `get_form`, `create_form`, `update_form`.
98
-
99
- 1. Collect the fields with the user. Field: `{ key, label, type, required?, placeholder?,
100
- options?, defaultValue?, showIf? }`. Types: `text, email, textarea, select` (needs
101
- `options`), `checkbox, number, phone, date, url, consent, hidden`. `showIf:
102
- { field, equals }` shows a field conditionally.
103
- 2. Settings: `name` (the handle for `getForm('Name')`), `submitLabel?`, `successMessage?`,
104
- `redirectUrl?`.
105
- 3. Confirm → `create_form { name, fields, ... }` (returns the id) or
106
- `update_form { formId, ... }`.
107
- 4. **Wire it into the site**: in a `@bettercms-ai/next` site,
108
- `import { getForm } from "@bettercms-ai/next"` then render
109
- `<BcmsForm form={await getForm("Name")} />` where the user wants it.
110
-
111
- ## Components (reusable Webflow-style symbols)
112
- Tools: `list_components`, `get_component`, `create_component`, `update_component`.
113
-
114
- 1. Design the `blockJson` tree — an array of blocks `{ type, id, props }`. The full set:
115
- `heading {text, level}`, `text {html}`, `richtext {html}`, `image {src, alt}`,
116
- `button {text, href, variant?, size?, fullWidth?}`, `spacer {height}`, `video {url}`,
117
- `columns {columns: block[][], gap}`, `section {children: block[]}`,
118
- `slider {slides}`, `tabs {tabs}`, `navbar {links, logo?, cta?}`, `footer {columns, copyright?}`,
119
- `form {formId}`, `component {componentId, overrides?}`, `collection {…}`.
120
- `section`, `columns`, `slider` and `tabs` nest child blocks. Every block needs a stable
121
- unique `id`.
122
-
123
- ⚠️ `text` and `richtext` carry **`html`**, never `text`. `props.html` is required with no
124
- default, so `{ text: "…" }` is a **400 on every save** — not a silent strip. This file said
125
- `text {text}` until 2026-08-20; if you learned it from an older copy, unlearn it.
126
- 2. Optional `props` — overridable fields `{ key, label, target: { blockId, path }, type,
127
- defaultValue? }` (type: `text|richtext|image|url|boolean`) so each instance can be
128
- customized.
129
- 3. Confirm the structure → `create_component { name, slug, category?, blockJson, props? }`
130
- (returns the id) or `update_component { componentId, ... }`. **Updating a component
131
- re-bakes every page that embeds it.**
132
- 4. **Render it**: `<BcmsBlocks blocks={component.blockJson} components={...} />` from
133
- `@bettercms-ai/next`.
134
-
135
- Authoring `blockJson` blind is error-prone — keep trees small, confirm with the user,
136
- and read back with `get_component` to verify.
137
-
138
- ## AI content & SEO
139
- Tools: `write_content`, `generate_seo_meta`. Guided prompts: `build_site`,
140
- `generate_landing_pages`, `seo_optimize`. These use the workspace's own Anthropic key
141
- (BYOK, unmetered) or platform AI credits, and return a *suggestion* — you apply it.
142
-
143
- 1. **Write copy** → `write_content { action, text, instructions?, targetLang?, context? }`.
144
- `action`: `write` (draft from a brief), `rewrite` (improve existing copy), `translate`
145
- (needs `targetLang`). Returns the text — apply it with `set_page_content`, `create_entry`,
146
- or `update_entry`.
147
- 2. **SEO meta** → `generate_seo_meta { text, context? }` returns
148
- `{ metaTitle, metaDescription, keywords, jsonLd? }`. Read the page/entry, pass its content
149
- as `text`, then apply the suggestion via the page/entry update tools.
150
- 3. **Programmatic pages** → for a template + a dataset, loop rows: `write_content` per slot →
151
- `create_entry` → `generate_seo_meta`. Confirm the first row or two first. For hundreds of
152
- rows, the dashboard AI Page Builder bulk-imports a CSV.
153
-
154
- Always show suggestions to the user before applying; never invent brand facts.
155
-
156
- ## Errors
157
- - `401/403` → the MCP key needs (re)authorizing (re-run "Connect your AI").
158
- - `409 slug_taken` → offer a different slug.
159
- - `create_form`/`create_component` missing → the host has a stale cached MCP; tell the
160
- user to `rm -rf ~/.npm/_npx` and restart, or that their connector predates this skill.
1
+ # BetterCMS MCP — authoring skill
2
+
3
+ How an AI agent should author content, schema, **forms**, and **components** in
4
+ BetterCMS through this MCP. The MCP is bound to ONE project (the API key's project);
5
+ everything you create lands there.
6
+
7
+ The guided prompts ship with the server — prefer them: `studio` (router),
8
+ `propose_schema`, `new_page`, `new_form`, `new_component`. This file is the reference
9
+ behind them.
10
+
11
+ ## Golden rules
12
+ - **Confirm before creating.** Propose the shape (fields / block tree) and get the
13
+ user's approval (AskUserQuestion) before any create/update. Never silently guess.
14
+ - **Read before you edit.** Use the `list_*` / `get_*` tools first so you keep existing
15
+ keys/blocks and don't duplicate.
16
+ - **Additive where possible.** `add_field` appends (to a model or a page); passing `fields`
17
+ to `update_form` or `blockJson` to `update_component` REPLACES — include everything
18
+ you want to keep.
19
+ - **Drafts are invisible.** A component you create is a DRAFT, and a draft component
20
+ renders as an empty string on the live site — no error, no placeholder. Call
21
+ `publish_component` on every component you place, then publish the page.
22
+
23
+ ## Pages & schema
24
+ Decide the architecture before the first call. A marketing site is **components-first**:
25
+ a library of `create_component` sections, each with a `sectionType` and a library
26
+ category, placed on pages via `create_page`'s `blockJson` as `component` blocks. A blog or
27
+ catalogue is **schema-first**: a `create_content_model` collection whose body field is
28
+ `document` (the article canvas — pages or collections, one per schema, top level; cannot coexist with a Section zone).
29
+
30
+ Within a page's own schema, destructure into a tree: plain fields, **group** (one nested
31
+ object), **repeater** (a repeatable array). Never a 1-item repeater, never a repeater of a
32
+ single text (that is `array`).
33
+
34
+ ### A page is a list of SECTIONS. Never a list of loose blocks.
35
+
36
+ The top level of a page's `blockJson` holds only `component` blocks or `section` blocks —
37
+ never a bare `heading`/`text`/`image`/`button`/`spacer`.
38
+
39
+ **Why it matters, concretely:** the visual editor derives one section per TOP-LEVEL block. A
40
+ hero authored as four loose blocks (headline, lede, two CTAs) becomes four separate sections,
41
+ each with its own move / duplicate / delete controls and its own "Add section here" gap — and
42
+ because each top-level block gets its own block box, the two call-to-action buttons stop being
43
+ inline siblings and **stack onto separate lines**. That is not a styling bug to chase in CSS;
44
+ it is the page telling you it was authored flat.
45
+
46
+ ```jsonc
47
+ // ✅ one section, four children — one set of controls, CTAs side by side
48
+ { "type": "section", "id": "hero",
49
+ "style": { "bg": "surface", "paddingTop": 96, "paddingBottom": 96, "contentWidth": "default" },
50
+ "props": { "children": [
51
+ { "type": "heading", "id": "hero-title", "props": { "text": "Strategic Planning.", "level": 1 } },
52
+ { "type": "text", "id": "hero-lede", "props": { "html": "<p>One place to plan, track and ship.</p>" } },
53
+ { "type": "button", "id": "hero-cta", "props": { "text": "Start a project", "href": "/contact" } },
54
+ { "type": "button", "id": "hero-cta2", "props": { "text": "See our work", "href": "/work", "variant": "secondary" } }
55
+ ] } }
56
+ ```
57
+
58
+ **Two CTAs are two sibling `button` blocks.** Not a `columns` block — `columns` renders
59
+ `grid-template-columns: repeat(N, 1fr)`, so each CTA would stretch to half the container.
60
+ Buttons are inline-level and flow side by side on their own. This is what the shipped
61
+ `builtin:hero-centered` section does.
62
+
63
+ ### `section` block or `component`? Decide by REUSE.
64
+
65
+ | | use it when | the catch |
66
+ |---|---|---|
67
+ | **`component`** (+ `sectionType`) | the band appears on more than one page, or needs layout variants — a Hero that is "Centered" on Home and "Two-column" on About | its children render **without field bindings**, so their text is **not click-to-edit on the canvas**. It is edited through the component's declared `props` in the section dock, where an unset prop shows empty and inherits the definition's default. Authors who want the current copy visible in the dock should set the prop explicitly rather than relying on the inherited default. |
68
+ | **`section` block** | a genuinely one-off band on a single page | no family name and no variant switcher, and the editor's "Add a section" picker cannot insert one — only components |
69
+
70
+ `component` is the shape the product's reuse machinery is built around: variants, the section
71
+ picker and the family name all key off `componentId`. So reach for it whenever a band actually
72
+ recurs — and when you do, **declare a prop for every string, link and image a marketer will
73
+ ever touch**. A component with un-propped editable copy is the defect, not the component.
74
+
75
+ ### Don't hand-write a hero. Start from a built-in section.
76
+
77
+ There are built-in section components — `hero-centered`, `hero-split`, `feature-grid-three`,
78
+ `feature-grid-two`, `faq-list`, `stats-row`, `logo-cloud-row`, `testimonial-quote`,
79
+ `case-study-story`, `cta-banner`, `cta-split`, `pricing-three`, `contact-simple` — available to
80
+ every project (`list_components` returns them; they have no `projectId`). Every one is already
81
+ rooted in a `section` block with its editable leaves declared as props, and `Hero` ships two
82
+ variants, so the shapes above are worked examples you can read.
83
+
84
+ Two sanctioned recipes, and which one you want follows the reuse rule above:
85
+
86
+ 1. **Inline it** — copy the builtin's `section` blockJson onto the page as a `section` block and
87
+ substitute your values. Standard path for a one-off band; children stay click-to-edit.
88
+ 2. **Reference it** — place `{ type: "component", props: { componentId, overrides } }`. Use this
89
+ when the band recurs across pages or needs the variant switcher.
90
+
91
+ Authoring a hero's JSON from scratch is the slow way to reinvent recipe 1 with more mistakes.
92
+
93
+ The full decision tree, section anatomy, variant rules and build order live in the
94
+ `propose_schema` prompt and in the `bettercms://playbook/schema` resource.
95
+
96
+ ## Forms
97
+ Tools: `list_forms`, `get_form`, `create_form`, `update_form`.
98
+
99
+ 1. Collect the fields with the user. Field: `{ key, label, type, required?, placeholder?,
100
+ options?, defaultValue?, showIf? }`. Types: `text, email, textarea, select` (needs
101
+ `options`), `checkbox, number, phone, date, url, consent, hidden`. `showIf:
102
+ { field, equals }` shows a field conditionally.
103
+ 2. Settings: `name` (the handle for `getForm('Name')`), `submitLabel?`, `successMessage?`,
104
+ `redirectUrl?`.
105
+ 3. Confirm → `create_form { name, fields, ... }` (returns the id) or
106
+ `update_form { formId, ... }`.
107
+ 4. **Wire it into the site**: in a `@bettercms-ai/next` site,
108
+ `import { getForm } from "@bettercms-ai/next"` then render
109
+ `<BcmsForm form={await getForm("Name")} />` where the user wants it.
110
+
111
+ ## Components (reusable Webflow-style symbols)
112
+ Tools: `list_components`, `get_component`, `create_component`, `update_component`.
113
+
114
+ 1. Design the `blockJson` tree — an array of blocks `{ type, id, props }`. The full set:
115
+ `heading {text, level}`, `text {html}`, `richtext {html}`, `image {src, alt}`,
116
+ `button {text, href, variant?, size?, fullWidth?}`, `spacer {height}`, `video {url}`,
117
+ `columns {columns: block[][], gap}`, `section {children: block[]}`,
118
+ `slider {slides}`, `tabs {tabs}`, `navbar {links, logo?, cta?}`, `footer {columns, copyright?}`,
119
+ `form {formId}`, `component {componentId, overrides?}`, `collection {…}`.
120
+ `section`, `columns`, `slider` and `tabs` nest child blocks. Every block needs a stable
121
+ unique `id`.
122
+
123
+ ⚠️ `text` and `richtext` carry **`html`**, never `text`. `props.html` is required with no
124
+ default, so `{ text: "…" }` is a **400 on every save** — not a silent strip. This file said
125
+ `text {text}` until 2026-08-20; if you learned it from an older copy, unlearn it.
126
+ 2. Optional `props` — overridable fields `{ key, label, target: { blockId, path }, type,
127
+ defaultValue? }` (type: `text|richtext|image|url|boolean`) so each instance can be
128
+ customized.
129
+ 3. Confirm the structure → `create_component { name, slug, category?, blockJson, props? }`
130
+ (returns the id) or `update_component { componentId, ... }`. **Updating a component
131
+ re-bakes every page that embeds it.**
132
+ 4. **Render it**: `<BcmsBlocks blocks={component.blockJson} components={...} />` from
133
+ `@bettercms-ai/next`.
134
+
135
+ Authoring `blockJson` blind is error-prone — keep trees small, confirm with the user,
136
+ and read back with `get_component` to verify.
137
+
138
+ ## AI content & SEO
139
+ Tools: `write_content`, `generate_seo_meta`. Guided prompts: `build_site`,
140
+ `generate_landing_pages`, `seo_optimize`. These use the workspace's own Anthropic key
141
+ (BYOK, unmetered) or platform AI credits, and return a *suggestion* — you apply it.
142
+
143
+ 1. **Write copy** → `write_content { action, text, instructions?, targetLang?, context? }`.
144
+ `action`: `write` (draft from a brief), `rewrite` (improve existing copy), `translate`
145
+ (needs `targetLang`). Returns the text — apply it with `set_page_content`, `create_entry`,
146
+ or `update_entry`.
147
+ 2. **SEO meta** → `generate_seo_meta { text, context? }` returns
148
+ `{ metaTitle, metaDescription, keywords, jsonLd? }`. Read the page/entry, pass its content
149
+ as `text`, then apply the suggestion via the page/entry update tools.
150
+ 3. **Programmatic pages** → for a template + a dataset, loop rows: `write_content` per slot →
151
+ `create_entry` → `generate_seo_meta`. Confirm the first row or two first. For hundreds of
152
+ rows, the dashboard AI Page Builder bulk-imports a CSV.
153
+
154
+ Always show suggestions to the user before applying; never invent brand facts.
155
+
156
+ ## Errors
157
+ - `401/403` → the MCP key needs (re)authorizing (re-run "Connect your AI").
158
+ - `409 slug_taken` → offer a different slug.
159
+ - `create_form`/`create_component` missing → the host has a stale cached MCP; tell the
160
+ user to `rm -rf ~/.npm/_npx` and restart, or that their connector predates this skill.