@bettercms-ai/mcp 0.85.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.
@@ -148,7 +148,8 @@
148
148
  "lead",
149
149
  "band",
150
150
  "family",
151
- "shape"
151
+ "shape",
152
+ "staleRule"
152
153
  ]
153
154
  },
154
155
  "categories": [
@@ -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. **A composition of ui keys.** `ui.control` (`segmented` for a short single choice), `ui.preview`
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
- 3. **A custom input.** Only when rungs 1 and 2 cannot show the choice, and only with the user's yes.
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 and 2 for each row. Drop rows they cover.
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
- | `list_dock_ui` | `{ scope?, status? }` | `{ sources, usage: { sources, bytes, limits } }` |
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.85.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.37.0",
35
+ "@bettercms-ai/sdk": "^1.38.0",
36
36
  "@modelcontextprotocol/sdk": "^1.30.0",
37
37
  "zod": "^4.3.6"
38
38
  },