@bettercms-ai/mcp 0.82.0 → 0.83.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -86
- package/SKILL.md +160 -160
- package/dist/index.js +49 -16
- package/dist/index.js.map +1 -1
- package/dist/kit-catalog.json +1509 -1509
- package/dist/skills/bettercms/SKILL.md +206 -206
- package/dist/skills/bettercms/references/accessibility.md +171 -171
- package/dist/skills/bettercms/references/animation.md +296 -296
- package/dist/skills/bettercms/references/astro.md +148 -148
- package/dist/skills/bettercms/references/bindings.md +366 -366
- package/dist/skills/bettercms/references/componentize.md +185 -185
- package/dist/skills/bettercms/references/components.md +415 -415
- package/dist/skills/bettercms/references/content-experiments.md +62 -62
- package/dist/skills/bettercms/references/content-modeling.md +382 -382
- package/dist/skills/bettercms/references/detection.md +465 -465
- package/dist/skills/bettercms/references/dock-input.md +121 -121
- package/dist/skills/bettercms/references/editor-chrome.md +92 -92
- package/dist/skills/bettercms/references/external-content.md +78 -78
- package/dist/skills/bettercms/references/finish-site.md +377 -377
- package/dist/skills/bettercms/references/forms-and-interactions.md +245 -245
- package/dist/skills/bettercms/references/frameworks.md +159 -156
- package/dist/skills/bettercms/references/getting-started.md +156 -141
- package/dist/skills/bettercms/references/integrations.md +364 -364
- package/dist/skills/bettercms/references/kit-catalog.md +3259 -3259
- package/dist/skills/bettercms/references/kit-scenarios.md +712 -712
- package/dist/skills/bettercms/references/localization.md +85 -85
- package/dist/skills/bettercms/references/media-and-brand.md +181 -181
- package/dist/skills/bettercms/references/next.md +189 -186
- package/dist/skills/bettercms/references/other-frameworks.md +85 -68
- package/dist/skills/bettercms/references/portable-text.md +213 -213
- package/dist/skills/bettercms/references/programmatic-seo.md +98 -98
- package/dist/skills/bettercms/references/publishing.md +289 -289
- package/dist/skills/bettercms/references/seo.md +357 -357
- package/dist/skills/bettercms/references/shopify-headless.md +52 -52
- package/dist/skills/bettercms/references/site-blueprints.md +895 -895
- package/dist/skills/bettercms/references/site-review.md +74 -74
- package/dist/skills/bettercms/references/worked-examples-advanced.md +509 -509
- package/dist/skills/bettercms/references/worked-examples.md +885 -885
- package/dist/skills/bettercms/references/write-limits.md +217 -217
- package/package.json +2 -2
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.
|