@bettercms-ai/mcp 0.86.0 → 0.87.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.
package/dist/kit-catalog.json
CHANGED
|
@@ -15,10 +15,12 @@ Industry practice: a custom input is the last resort. Climb in this order and st
|
|
|
15
15
|
rung that works.
|
|
16
16
|
|
|
17
17
|
1. **The stock control.** The field type already draws one: text, number, select, boolean, image.
|
|
18
|
-
2. **
|
|
18
|
+
2. **The stock form, restructured.** Order the props, drop the tab strip, fold Design under the
|
|
19
|
+
content, regroup: one `update_component` `dock` write (editor-chrome.md §10).
|
|
20
|
+
3. **A composition of ui keys.** `ui.control` (`segmented` for a short single choice), `ui.preview`
|
|
19
21
|
and `ui.layout` for list items, `ui.group` for props, model `fieldsets` for groups, rows and
|
|
20
22
|
tabs. See editor-chrome.md.
|
|
21
|
-
|
|
23
|
+
4. **A custom input.** Only when rungs 1 to 3 cannot show the choice, and only with the user's yes.
|
|
22
24
|
|
|
23
25
|
A custom input never adds data. It draws fields that already exist in the schema.
|
|
24
26
|
|
|
@@ -28,7 +30,7 @@ A custom input never adds data. It draws fields that already exist in the schema
|
|
|
28
30
|
row has the field `key`, a `reason` and, for a look set by several props, the sibling keys it would
|
|
29
31
|
`writes`. The rule is a candidate list, never a defect.
|
|
30
32
|
|
|
31
|
-
1. Check rungs 1
|
|
33
|
+
1. Check rungs 1 to 3 for each row. Drop rows they cover.
|
|
32
34
|
2. Ask the user, in one message per field: name the field, the input you would write and the
|
|
33
35
|
reason the stock control cannot do it. Wait for an explicit yes.
|
|
34
36
|
3. On yes: write the input file (section 5), then set `ui.input` with `confirmed: true`. These
|
|
@@ -137,7 +139,9 @@ restraint ladder (section 1) and the user's yes (section 2) still come first.
|
|
|
137
139
|
| 3 | `approve_dock_ui { name, version, userApproved: true }` | approves that ONE version; a later write drops it |
|
|
138
140
|
| 4 | `validate_dock_ui { name }` | the import allow-list, the size caps, a name clash; the build receipt |
|
|
139
141
|
| 5 | `publish_dock_ui { name }` | validates again, then the dock serves that version |
|
|
140
|
-
| — | `list_dock_ui { scope?, status? }` | every source, its status and the project's usage |
|
|
142
|
+
| — | `list_dock_ui { scope?, status? }` | every source, its status, `notShown` and the project's usage |
|
|
143
|
+
| — | `publish_dock_ui { name, action: "unpublish" }` | off the dock; the release is rebuilt without it |
|
|
144
|
+
| — | `publish_dock_ui { name, action: "delete" }` | removes an UNPUBLISHED source and every version |
|
|
141
145
|
|
|
142
146
|
- `name` is what `cms:<name>` imports resolve: a lowercase letter first, then letters, digits and
|
|
143
147
|
hyphens, at most 48 (`DOCK_UI_NAME_UNSERVABLE` otherwise: such a name could never build).
|
|
@@ -154,6 +158,15 @@ restraint ladder (section 1) and the user's yes (section 2) still come first.
|
|
|
154
158
|
the project's dock UI release (the repo's inputs plus every published CMS source) and answers its
|
|
155
159
|
`release` key. A project with no site handle yet refuses `build.compile_error` (detail "no_site_handle"):
|
|
156
160
|
publish the site once, then publish again. A failed build leaves the stock control (section 8).
|
|
161
|
+
- The dock mounts an `input` at the `field` scope and a `panel` at `section`, `page` or `layout`. Any
|
|
162
|
+
other pair refuses `protocol.unsupported` at write and validate.
|
|
163
|
+
- **Published is not shown.** approve, validate and publish answer `dockInputs: { enabled, platform }`,
|
|
164
|
+
and `notShown: { reason, fix }` when the dock will not draw it: "flag_off" (the project's Custom inputs
|
|
165
|
+
switch is off), "platform_off" (this server has it off), "switched_off" (an admin turned that input
|
|
166
|
+
off). Publish still succeeds. Tell the user the `fix`: a project admin turns on Custom inputs in
|
|
167
|
+
Project settings › General › Visual editor. Never flip it yourself; no tool can.
|
|
168
|
+
- Clean up test UI: unpublish (refused `DOCK_UI_IN_USE` while a published sibling imports it), then
|
|
169
|
+
delete (refused `DOCK_UI_PUBLISHED` while published). Both are recorded in the activity log.
|
|
157
170
|
|
|
158
171
|
One minimal example per scope. The `target` says what the UI is for.
|
|
159
172
|
|
|
@@ -183,10 +196,12 @@ buildReceipt | null, createdAt, updatedAt }`. A version is `{ version, sourceSha
|
|
|
183
196
|
| Tool | Takes | Answers |
|
|
184
197
|
|---|---|---|
|
|
185
198
|
| `write_dock_ui` | `{ name, scope: field \| section \| page \| layout, kind: input \| panel, target, source }` | `{ source, version, created }` (`created: false` when the same bytes were already the newest) |
|
|
186
|
-
| `approve_dock_ui` | `{ name, version, userApproved? }` | `{ source }`, status `approved` |
|
|
187
|
-
| `validate_dock_ui` | `{ name, version? }` (default: the newest) | `{ name, receipt }`: `{ ok: true, version, bundleKey, bytes, builtAt }` or `{ ok: false, version, refusals, builtAt }` |
|
|
188
|
-
| `publish_dock_ui` | `{ name, version? }` | `{ ok: true, source, receipt, release: { K, built, cached } }` |
|
|
189
|
-
| `
|
|
199
|
+
| `approve_dock_ui` | `{ name, version, userApproved? }` | `{ source, dockInputs, notShown? }`, status `approved` |
|
|
200
|
+
| `validate_dock_ui` | `{ name, version? }` (default: the newest) | `{ name, receipt, dockInputs, notShown? }`: receipt `{ ok: true, version, bundleKey, bytes, builtAt }` or `{ ok: false, version, refusals, builtAt }` |
|
|
201
|
+
| `publish_dock_ui` | `{ name, version? }` | `{ ok: true, source, receipt, release: { K, built, cached }, dockInputs, notShown? }` |
|
|
202
|
+
| `publish_dock_ui` | `{ name, action: "unpublish" }` | `{ source, wasPublished, release: { K, built, cached } \| null }` |
|
|
203
|
+
| `publish_dock_ui` | `{ name, action: "delete" }` | `{ deleted, versions }` |
|
|
204
|
+
| `list_dock_ui` | `{ scope?, status? }` | `{ sources, usage: { sources, bytes, limits }, dockInputs }`; each source carries `notShown?` |
|
|
190
205
|
|
|
191
206
|
**Who may approve.** A person in the dashboard approves any version (the route needs `content:publish`).
|
|
192
207
|
An agent approves only with the user's yes, sent as `userApproved: true` after they saw the source.
|
|
@@ -209,5 +224,8 @@ connection that may not publish. A write after the approval drops it: approve th
|
|
|
209
224
|
| `DOCK_UI_VERSION_NOT_HEAD` (409) | approving an older version, or a newer one was saved meanwhile | `list_dock_ui`, then approve and publish the newest |
|
|
210
225
|
| `DOCK_UI_NOT_FOUND` / `DOCK_UI_VERSION_NOT_FOUND` (404) | no such name, or no such version | `list_dock_ui` lists them; `write_dock_ui` creates one |
|
|
211
226
|
| `DOCK_UI_REBUILD_FAILED` (502) | the version is published but the UI release failed | nothing to change: the next site release builds it; the stock control shows until then |
|
|
227
|
+
| `protocol.unsupported` (400 / 422) | a kind and scope the dock never mounts (an input outside `field`, a panel at `field`) | write it with a scope the dock mounts |
|
|
228
|
+
| `DOCK_UI_IN_USE` (409) | unpublishing a source a published sibling imports | unpublish the sibling first |
|
|
229
|
+
| `DOCK_UI_PUBLISHED` (409) | deleting a published source | unpublish it first |
|
|
212
230
|
| `DOCK_UI_UNAVAILABLE` (503) | this server has no dock UI storage yet | try again after the server update |
|
|
213
231
|
|
|
@@ -86,6 +86,95 @@ A Global field with `overridable: false` and a `lockReason` is not offered on a
|
|
|
86
86
|
of it answers 403 **LAYOUT_FIELD_NOT_OVERRIDABLE** with the reason. A page value written earlier is
|
|
87
87
|
kept but the Global value renders; `get_layout` scope page lists it under `shadowed`.
|
|
88
88
|
|
|
89
|
+
## 9. Names and help on an imported site
|
|
90
|
+
|
|
91
|
+
An import names the dock for you. A setup run or a release runs the naming pass whenever the
|
|
92
|
+
project's `naming` receipt is behind the current rules version (`naming-2`); it does not mark setup
|
|
93
|
+
stale, and a second run writes nothing. On a project whose setup is already `ready`, run it with
|
|
94
|
+
`editor_setup` `force: true`. The pass runs in the background, one at a time per server, so poll
|
|
95
|
+
`editor_setup` `check: true`: `setup.namingRun` is the latest run {runId, state, clean, ms, steps,
|
|
96
|
+
counts, errors}, its state queued → running → done or failed (skipped when the rules were already
|
|
97
|
+
current). Only a clean run stamps `setup.naming` {version, at, runId, counts, refusals}. A check
|
|
98
|
+
never starts the pass.
|
|
99
|
+
|
|
100
|
+
What the pass changes on its own, only where the platform wrote the old value:
|
|
101
|
+
- **Labels.** A label that is copied text, alt text or an artifact name becomes the bare V2 label
|
|
102
|
+
of its role: "Heading", "Eyebrow", "Intro", "Body", "Image", "Primary button", "Number",
|
|
103
|
+
"Rating", "Quote". A repeat inside one section or component gets " 2" ("Image 2"). A whole-button
|
|
104
|
+
prop or field ("Hero Primary Cta") becomes "Primary button" or "Secondary button".
|
|
105
|
+
- **Lists.** A list takes a plural noun ("Questions", "Features", "Links", "Logos"), its row
|
|
106
|
+
sub-fields their roles ("Question" / "Answer", "Title" / "Description" / "Icon", "Label" /
|
|
107
|
+
"Links to"), and, where its `ui` names none, `ui.itemLabel` ("Question") and `ui.preview.title`.
|
|
108
|
+
- **Help.** A field or prop with no real help gets the kit's help text for its role.
|
|
109
|
+
- **Section kind and name.** A machine-made kind (a heading's first word, like "Equip") becomes the
|
|
110
|
+
section family ("Hero", "Stats", "Testimonials"…), and the name a phrase from its heading. A
|
|
111
|
+
placement titled with that first word, or untitled, takes its component's name; a person's title
|
|
112
|
+
stays.
|
|
113
|
+
- **Type.** "7k+" becomes a number with suffix "k+"; "4.8 / 5" a number with the rating control.
|
|
114
|
+
A retyped number (not a rating) also carries `config.unit`, its suffix or prefix ("k+", "$"),
|
|
115
|
+
which the dock shows beside the input.
|
|
116
|
+
- **Stale.** Decorative icons and per-page copies of Layout chrome are made stale, so they leave
|
|
117
|
+
the "couldn't connect" list.
|
|
118
|
+
|
|
119
|
+
It never touches keys, ids, slugs or values (except an allowed retype), nor a label, help or `ui`
|
|
120
|
+
a person or agent wrote, nor a repository-managed component. Don't redo its work by hand: a label
|
|
121
|
+
you write is yours and the pass leaves it alone from then on.
|
|
122
|
+
|
|
123
|
+
Each refusal keeps the field as it is and names why:
|
|
124
|
+
|
|
125
|
+
| Reason | Means | Do |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| **person-named** | A person set this label | Leave it; rename only when the user asks |
|
|
128
|
+
| **person-helped** | A person or agent owns this help | Leave it |
|
|
129
|
+
| **retype-code-reads-value** | The site's code reads this value, so a number would change the live text (helper older than v24, or the build declares its own bindings) | Keep the text type. After the site runs `@bettercms-ai/convert` helper v24 or newer and rebuilds, the next run retypes it |
|
|
130
|
+
| **retype-not-lossless** | The text does not round-trip exactly as a number ("about 7k") | Nothing: text is the right type |
|
|
131
|
+
| **regroup-would-rekey** | Numbered siblings ("Avatar 1–4") would become one list, which changes keys | They stay separate fields. A list needs a re-import, with the user's approval |
|
|
132
|
+
| **kit-low-confidence** | No kit entry matched clearly enough to borrow its labels and controls | Role labels only; refine with `update_field` if the user wants |
|
|
133
|
+
| **cta-modelled-twice** | A call to action exists as loose fields and as a button field, and the template declares neither | It stays in "couldn't connect". Fix by re-converting the source, or drop one copy with the user's approval |
|
|
134
|
+
| **decorative** | A decorative icon (an SVG data URL, hidden or untitled) | Made stale. Never bind or label it |
|
|
135
|
+
| **layout-owned** | The value belongs to the Global Layout | Counted once on the Layout; edit it with `update_layout` |
|
|
136
|
+
| **repository-managed** | The component belongs to the repository, whose next push rewrites it whole | Rename it in the source, not here |
|
|
137
|
+
| **not-machine-named** | A person or agent wrote the component's name and family | Leave them |
|
|
138
|
+
| **shown-again** | A person set `stale: false` on the field | Leave it shown; the pass never hides it again |
|
|
139
|
+
|
|
140
|
+
## 10. Restructure a section
|
|
141
|
+
|
|
142
|
+
The stock form, rearranged, comes before any custom input (dock-input.md §1). The scope is the
|
|
143
|
+
component: every placement shows the same form. Order, tabs and the Design fold apply at once, show
|
|
144
|
+
the "Layout by agent" badge, and the user can revert them in one click. Tabs appear only when
|
|
145
|
+
declared (`ui.tab`, a fieldset of kind `tab`, the kit's groups); there are no automatic
|
|
146
|
+
Content | Media tabs.
|
|
147
|
+
|
|
148
|
+
Decide the shape from the user's words, read with `get_component` (§1), then send ONE
|
|
149
|
+
`update_component` with `dock`:
|
|
150
|
+
|
|
151
|
+
| The user wants | Send |
|
|
152
|
+
|---|---|
|
|
153
|
+
| One form, text first, then media | `dock: { propOrder: ["heading", "intro", "primaryButton", "image"], tabs: "none" }` |
|
|
154
|
+
| Design controls kept apart (or says nothing) | `design: "tab"`: today's Content \| Design |
|
|
155
|
+
| Design under the content, one scroll | `design: "fold"`: a Design group below the content (only on a designable section) |
|
|
156
|
+
| Related props together | `propGroup`, as in §2 |
|
|
157
|
+
| The old form back | `tabs: null`, `design: null`, and the old `propOrder` |
|
|
158
|
+
|
|
159
|
+
- `propOrder` names every top-level prop key exactly once. A missing, unknown or repeated key is
|
|
160
|
+
400 `prop_order_invalid`, which names the key.
|
|
161
|
+
- `tabs` takes "none" or null (else `dock_tabs_invalid`); `design` takes "tab", "fold" or null
|
|
162
|
+
(else `dock_design_invalid`).
|
|
163
|
+
- `tabs: "none"` keeps any stored `ui.tab`, so null brings the tabs back. A prop added later still
|
|
164
|
+
draws.
|
|
165
|
+
- A person's own arrangement answers 409 **ui_human_owned**: send `override: true` only when the
|
|
166
|
+
user asked you to replace it.
|
|
167
|
+
|
|
168
|
+
**Hiding takes the user's yes.** Hiding a field (`ui.hidden`), moving it into `advanced` or a
|
|
169
|
+
collapsed group, or demoting it from required to optional changes what an editor can find:
|
|
170
|
+
1. Ask first, naming each field and why ("Hide 'Badge'? This page never shows it").
|
|
171
|
+
2. Only after a yes, send the write with `confirmed: true`. Without it the answer is 409
|
|
172
|
+
`hide_needs_confirmation`, with the `paths` and each `change`.
|
|
173
|
+
3. A required field is never hidden: 409 **required-field-hidden**, hint "demote it first
|
|
174
|
+
(required:false, confirmed)". Ask the user to make it optional. On a yes, send `required: false`
|
|
175
|
+
with `confirmed: true` (`update_field` for a field, the prop in `props` for a component), then
|
|
176
|
+
hide it in a second confirmed write. Never demote just to get past the refusal.
|
|
177
|
+
|
|
89
178
|
## Acceptance
|
|
90
179
|
|
|
91
180
|
Read back with `get_content_model`, `get_page` or `get_component`. Done means `get_next_steps`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bettercms-ai/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.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.
|
|
35
|
+
"@bettercms-ai/sdk": "^1.38.0",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
37
37
|
"zod": "^4.3.6"
|
|
38
38
|
},
|