@caelo-cms/mcp-server 0.10.57 → 0.10.58
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/package.json +1 -1
- package/power-catalogue.json +104 -18
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caelo-cms/mcp-server",
|
|
3
3
|
"mcpName": "io.github.caelo-cms/caelo",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.58",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MPL-2.0",
|
|
7
7
|
"description": "MCP servers for Caelo CMS: `caelo_chat` (talk to Caelo's own AI) and the admin-scoped Power-MCP (the full chat-runner tool catalogue, driven by your own agent). Lets Claude Code (or any MCP-aware client) drive a Caelo install from outside the browser.",
|
package/power-catalogue.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.10.
|
|
2
|
+
"version": "0.10.58",
|
|
3
3
|
"tools": [
|
|
4
4
|
{
|
|
5
5
|
"name": "edit_module",
|
|
6
|
-
"description": "Edit ONE module's structure: HTML template, fields, CSS, JS, displayName, description, or kind. **AUTHOR EXPLICITLY (v0.12.0+):** pass `html` with `{{fieldName}}` placeholders + an explicit `fields[]` array with semantic snake_case names (`hero_title`, `primary_cta_href`, `nav_items`), NOT a literal-content fallback. The operator says 'fix the homepage hero copy'; YOU translate that to the right field names. Field names like `cta2label2` or `spanText` are wrong — they leak the extractor heuristic. **Field kinds (v0.12.0):** `text`, `richtext`, `url`, `image`, `number`, `boolean`, `link` (primitives); `text-list` (array of strings, slot `{{#field}}…{{.}}…{{/field}}`); `link-list` (array of `{label, href}`, slot `{{#field}}…{{label}}…{{href}}…{{/field}}` — use for menus, footer columns); `module` (single nested module, slot `{{>field}}`); `module-list` (array of nested modules, slot `{{#field}}…{{/field}}`). **Nested fields may declare `allowedModuleTypes`** — a whitelist of stable module `type`s (e.g. `[\"button\"]`), NOT slugs. It matches the referenced module's `type`; set it to constrain what can nest in a `module`/`module-list` slot, and widen it here if a valid module is being wrongly rejected. **Lists are lists, not numbered scalars.** A menu with 10 items is ONE `link-list` field with 10 elements — never `label1`, `label2`, …, `label10`. A tag cloud is ONE `text-list`. Cards with rich per-item structure use `module-list` pointing at a sub-module. **Update description + kind when the module's purpose drifts.** The `## Modules` block exposes them to your future self; stale descriptions hurt your own decision-making. **Legacy fallback only:** if you pass HTML with literal content and NO `fields[]`, a server-side extractor mints heuristic field names — useful for one-shot drafts but the result hurts the `## Modules` block. Treat it as a fallback, not the default path. **Scope module CSS under the module's own root class** - bare global selectors (`body`, `h1`, `.card`) bleed into every other module on the page (issue #158). **Module JS shares ONE page-level script** - no implicit `root`/`el` binding exists; wrap in an IIFE and query via document.querySelectorAll scoped to your module's root class (see the `js` input description). Edits are CHAT-BRANCHED until publish. **For a TARGETED change to existing html/css/js, PREFER `edit_content`** (surgical string-replace: far cheaper than re-emitting the whole body here, and the diff is reviewable). Use `edit_module` for a WHOLESALE rewrite, a brand-new module body, or `fields`/`displayName`/`kind`/`type`/`description` changes. Use this for structure, styling, fields list, or `description`/`kind` updates. DO NOT use this to change what one page shows — use `set_page_module_content` (per-page content) or `set_content_instance_values` (shared content) for that. Prefer `update_modules_many` when targeting > 1 module. DO NOT use for page metadata (`update_pages_many`) or template-level edits (`propose_update_template`).",
|
|
6
|
+
"description": "Edit ONE module's structure: HTML template, fields, CSS, JS, displayName, description, or kind. **AUTHOR EXPLICITLY (v0.12.0+):** pass `html` with `{{fieldName}}` placeholders + an explicit `fields[]` array with semantic snake_case names (`hero_title`, `primary_cta_href`, `nav_items`), NOT a literal-content fallback. The operator says 'fix the homepage hero copy'; YOU translate that to the right field names. Field names like `cta2label2` or `spanText` are wrong — they leak the extractor heuristic. **Field kinds (v0.12.0):** `text`, `richtext`, `url`, `image`, `number`, `boolean`, `link` (primitives); `text-list` (array of strings, slot `{{#field}}…{{.}}…{{/field}}`); `link-list` (array of `{label, href}`, slot `{{#field}}…{{label}}…{{href}}…{{/field}}` — use for menus, footer columns); `module` (single nested module, slot `{{>field}}`); `module-list` (array of nested modules, slot `{{#field}}…{{/field}}`). **Nested fields may declare `allowedModuleTypes`** — a whitelist of stable module `type`s (e.g. `[\"button\"]`), NOT slugs. It matches the referenced module's `type`; set it to constrain what can nest in a `module`/`module-list` slot, and widen it here if a valid module is being wrongly rejected. **Lists are lists, not numbered scalars.** A menu with 10 items is ONE `link-list` field with 10 elements — never `label1`, `label2`, …, `label10`. A tag cloud is ONE `text-list`. Cards with rich per-item structure use `module-list` pointing at a sub-module. **Update description + kind when the module's purpose drifts.** The `## Modules` block exposes them to your future self; stale descriptions hurt your own decision-making. **Legacy fallback only:** if you pass HTML with literal content and NO `fields[]`, a server-side extractor mints heuristic field names — useful for one-shot drafts but the result hurts the `## Modules` block. Treat it as a fallback, not the default path. **Scope module CSS under the module's own root class** - bare global selectors (`body`, `h1`, `.card`) bleed into every other module on the page (issue #158). **Module JS shares ONE page-level script** - no implicit `root`/`el` binding exists; wrap in an IIFE and query via document.querySelectorAll scoped to your module's root class (see the `js` input description). Edits are CHAT-BRANCHED until publish. **For a TARGETED change to existing html/css/js, PREFER `edit_content`** (surgical string-replace: far cheaper than re-emitting the whole body here, and the diff is reviewable). Use `edit_module` for a WHOLESALE rewrite, a brand-new module body, or `fields`/`displayName`/`kind`/`type`/`description` changes. Use this for structure, styling, fields list, or `description`/`kind` updates. DO NOT use this to change what one page shows — use `set_page_module_content` (per-page content) or `set_content_instance_values` (shared content) for that. Prefer `update_modules_many` when targeting > 1 module. DO NOT use for page metadata (`update_pages_many`) or template-level edits (`propose_update_template`). **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first, or add the link after. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
7
7
|
"inputSchema": {
|
|
8
8
|
"type": "object",
|
|
9
9
|
"additionalProperties": false,
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
},
|
|
214
214
|
{
|
|
215
215
|
"name": "edit_content",
|
|
216
|
-
"description": "Surgically edit a module's or template's body (html/css/js) by string replacement — the cheap, minimal-diff way to change existing code. Pass `entityKind` + `entityId` + `field` + an `edits` array of {oldString, newString, replaceAll?}. Each `oldString` must be UNIQUE in the body (add surrounding context if not) unless `replaceAll:true`; edits apply in order, atomically (all-or-nothing). Read the body first with read_content and copy exact text (incl. whitespace) into `oldString`; optionally pass its `sha=` as `expectedSha` to guard against a concurrent change. On success it returns the NEW `sha=` plus a line-numbered snippet of each changed region — chain another edit_content in the same turn using that sha and the fresh line numbers, WITHOUT re-reading. PREFER this over edit_module for targeted changes to existing html/css/js — it is far cheaper and the diff is reviewable. Use edit_module instead for a wholesale rewrite, a new module, or field-schema / displayName / kind changes. TEMPLATES ARE GATED: an edit_content on a template is a TWO-STEP §11.A flow — you propose, the Owner clicks Approve on the proposal card. Do not claim a template edit was applied.",
|
|
216
|
+
"description": "Surgically edit a module's or template's body (html/css/js) by string replacement — the cheap, minimal-diff way to change existing code. Pass `entityKind` + `entityId` + `field` + an `edits` array of {oldString, newString, replaceAll?}. Each `oldString` must be UNIQUE in the body (add surrounding context if not) unless `replaceAll:true`; edits apply in order, atomically (all-or-nothing). Read the body first with read_content and copy exact text (incl. whitespace) into `oldString`; optionally pass its `sha=` as `expectedSha` to guard against a concurrent change. On success it returns the NEW `sha=` plus a line-numbered snippet of each changed region — chain another edit_content in the same turn using that sha and the fresh line numbers, WITHOUT re-reading. PREFER this over edit_module for targeted changes to existing html/css/js — it is far cheaper and the diff is reviewable. Use edit_module instead for a wholesale rewrite, a new module, or field-schema / displayName / kind changes. TEMPLATES ARE GATED: an edit_content on a template is a TWO-STEP §11.A flow — you propose, the Owner clicks Approve on the proposal card. Do not claim a template edit was applied. **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first, or add the link after. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
217
217
|
"inputSchema": {
|
|
218
218
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
219
219
|
"type": "object",
|
|
@@ -319,7 +319,7 @@
|
|
|
319
319
|
},
|
|
320
320
|
{
|
|
321
321
|
"name": "set_page_module_content",
|
|
322
|
-
"description": "Set the content values for ONE module placement on a specific page. **Prefer `set_page_module_content_many` when a content pass touches more than one placement** (a whole page's text in one transaction — §11 bulk-first), and `build_page` when the modules don't exist yet (it places modules WITH content in one call). v0.12.0 — page CONTENT lives in a content_instance bound to the placement. For UNSYNCED placements (the default) this tool routes through content_instances.set_values and edits stay local to this page. For SYNCED placements (sharing content with other pages) this tool refuses and points you at fork_placement_content (to detach first) or set_content_instance_values (to commit to the propagate-everywhere blast radius). Use this when the operator says 'change the hero heading on /home to X' or 'set the about page subtitle to Y'. Use `edit_module` (NOT this tool) when the change should affect every page using the module (structural / styling / field-schema change). `contentValues` is keyed by the module's declared field names (`{{fieldName}}` placeholders). For nested fields (kind `module` / `module-list`) the value is `{ moduleId, contentInstanceId }` (or an array of them); the referenced module's stable `type` must be in the field's `allowedModuleTypes` when set (reuse a module of an allowed type — see `## Modules`). Missing fields fall back to the module field's `default`. Pass an empty object to reset a placement to all defaults.",
|
|
322
|
+
"description": "Set the content values for ONE module placement on a specific page. **Prefer `set_page_module_content_many` when a content pass touches more than one placement** (a whole page's text in one transaction — §11 bulk-first), and `build_page` when the modules don't exist yet (it places modules WITH content in one call). v0.12.0 — page CONTENT lives in a content_instance bound to the placement. For UNSYNCED placements (the default) this tool routes through content_instances.set_values and edits stay local to this page. For SYNCED placements (sharing content with other pages) this tool refuses and points you at fork_placement_content (to detach first) or set_content_instance_values (to commit to the propagate-everywhere blast radius). Use this when the operator says 'change the hero heading on /home to X' or 'set the about page subtitle to Y'. Use `edit_module` (NOT this tool) when the change should affect every page using the module (structural / styling / field-schema change). `contentValues` is keyed by the module's declared field names (`{{fieldName}}` placeholders). For nested fields (kind `module` / `module-list`) the value is `{ moduleId, contentInstanceId }` (or an array of them); the referenced module's stable `type` must be in the field's `allowedModuleTypes` when set (reuse a module of an allowed type — see `## Modules`). Missing fields fall back to the module field's `default`. Pass an empty object to reset a placement to all defaults. **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first, or add the link after. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
323
323
|
"inputSchema": {
|
|
324
324
|
"type": "object",
|
|
325
325
|
"additionalProperties": false,
|
|
@@ -352,7 +352,7 @@
|
|
|
352
352
|
},
|
|
353
353
|
{
|
|
354
354
|
"name": "build_page",
|
|
355
|
-
"description": "Build a WHOLE page in ONE call: the page (create new via `page: {slug, title}` or target existing via `page: {pageId}`) plus the FULL ordered module list — each entry places one module into a template block, top to bottom. ONE transaction: any invalid entry aborts the whole call (the error names `modules[i]` and the failing field) and nothing is written. This is THE page-creation tool: `modules: []` (empty) creates an intentionally empty page shell; a populated `modules` array builds the whole page in one transaction. **Prefer it over add_module / create_content_instance chains whenever a page needs more than one module** — one build_page call replaces that whole round-trip chain (§11 bulk-first). Use add_module (target='page') only for a single incremental module on an existing page. **Per module, two modes** (same contract as add_module): pass `moduleId` to place an EXISTING module from `## Modules` (reuse first — shared modules keep pages consistent), or `displayName` + `html` (+ required `description`, `kind`, explicit `fields[]`) to mint a new one. Field names are semantic snake_case (`hero_title`, `primary_cta_href`); repeats are LIST fields (`text-list`, `link-list`, `module-list`), never numbered scalars. **Per module, `content` fills the placement**: `{source:'inline', values:{hero_title:'…'}}` for page-local content (the routine case); `{source:'shared', purpose:'…', values:{…}}` to mint a REUSABLE instance other pages can bind (synced by default); `{source:'existing', contentInstanceId}` to bind a row from `## Content Library`. Omit `content` for an empty private instance you fill later. Typical call: `{page:{slug:'pricing', title:'Pricing'}, modules:[{blockName:'content', displayName:'Pricing Hero', kind:'hero', description:'…', html:'<section><h1>{{hero_title}}</h1></section>', fields:[{name:'hero_title', kind:'text', label:'Hero title'}], content:{source:'inline', values:{hero_title:'Fair pricing'}}}, …]}`. **Nested modules in the SAME call**: give the inner module `ref` and NO blockName (detached — minted + content-bound but not placed), then reference it from the outer module's `module`/`module-list` field value as `{\"$ref\": \"<ref>\"}` — the server resolves it to the real {moduleId, contentInstanceId}. Referenced entries come FIRST. Example: `modules:[{ref:'btn', displayName:'Button', html:'<button>{{label}}</button>', fields:[…], content:{source:'inline', values:{label:'Go'}}}, {blockName:'content', displayName:'CTA', html:'<section>{{>cta}}</section>', fields:[{name:'cta', kind:'module', label:'CTA', allowedModuleTypes:['button']}], content:{source:'inline', values:{cta:{'$ref':'btn'}}}}]` — the whole nested composition is ONE call, never an add_module → remove_module_from → create_content_instance chain. For site-wide chrome (header/footer) use add_module (target='layout') — chrome is layout-owned and never rides in a page body. Each entry's `blockName` must be a block on the page's template — see `# Current page` (exhaustive list for the focused page); a mismatch error names the valid set. A block name is a template slot, NOT a module `kind`. With zero templates on the site, bootstrap first (create_layout → create_template).",
|
|
355
|
+
"description": "Build a WHOLE page in ONE call: the page (create new via `page: {slug, title}` or target existing via `page: {pageId}`) plus the FULL ordered module list — each entry places one module into a template block, top to bottom. ONE transaction: any invalid entry aborts the whole call (the error names `modules[i]` and the failing field) and nothing is written. This is THE page-creation tool: `modules: []` (empty) creates an intentionally empty page shell; a populated `modules` array builds the whole page in one transaction. **Prefer it over add_module / create_content_instance chains whenever a page needs more than one module** — one build_page call replaces that whole round-trip chain (§11 bulk-first). Use add_module (target='page') only for a single incremental module on an existing page. **Per module, two modes** (same contract as add_module): pass `moduleId` to place an EXISTING module from `## Modules` (reuse first — shared modules keep pages consistent), or `displayName` + `html` (+ required `description`, `kind`, explicit `fields[]`) to mint a new one. Field names are semantic snake_case (`hero_title`, `primary_cta_href`); repeats are LIST fields (`text-list`, `link-list`, `module-list`), never numbered scalars. **Per module, `content` fills the placement**: `{source:'inline', values:{hero_title:'…'}}` for page-local content (the routine case); `{source:'shared', purpose:'…', values:{…}}` to mint a REUSABLE instance other pages can bind (synced by default); `{source:'existing', contentInstanceId}` to bind a row from `## Content Library`. Omit `content` for an empty private instance you fill later. Typical call: `{page:{slug:'pricing', title:'Pricing'}, modules:[{blockName:'content', displayName:'Pricing Hero', kind:'hero', description:'…', html:'<section><h1>{{hero_title}}</h1></section>', fields:[{name:'hero_title', kind:'text', label:'Hero title'}], content:{source:'inline', values:{hero_title:'Fair pricing'}}}, …]}`. **Nested modules in the SAME call**: give the inner module `ref` and NO blockName (detached — minted + content-bound but not placed), then reference it from the outer module's `module`/`module-list` field value as `{\"$ref\": \"<ref>\"}` — the server resolves it to the real {moduleId, contentInstanceId}. Referenced entries come FIRST. Example: `modules:[{ref:'btn', displayName:'Button', html:'<button>{{label}}</button>', fields:[…], content:{source:'inline', values:{label:'Go'}}}, {blockName:'content', displayName:'CTA', html:'<section>{{>cta}}</section>', fields:[{name:'cta', kind:'module', label:'CTA', allowedModuleTypes:['button']}], content:{source:'inline', values:{cta:{'$ref':'btn'}}}}]` — the whole nested composition is ONE call, never an add_module → remove_module_from → create_content_instance chain. For site-wide chrome (header/footer) use add_module (target='layout') — chrome is layout-owned and never rides in a page body. Each entry's `blockName` must be a block on the page's template — see `# Current page` (exhaustive list for the focused page); a mismatch error names the valid set. A block name is a template slot, NOT a module `kind`. With zero templates on the site, bootstrap first (create_layout → create_template). **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first, or add the link after. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
356
356
|
"inputSchema": {
|
|
357
357
|
"type": "object",
|
|
358
358
|
"additionalProperties": false,
|
|
@@ -704,7 +704,7 @@
|
|
|
704
704
|
},
|
|
705
705
|
{
|
|
706
706
|
"name": "set_page_module_content_many",
|
|
707
|
-
"description": "Set content values for SEVERAL placements in ONE transaction — prefer this over multiple `set_page_module_content` calls whenever a content pass touches more than one placement (a whole page's text, the same fix across pages; §11 bulk-first). All-or-nothing: any invalid item aborts the whole batch (the error names `items[i]`, the placement, and for value problems the failing field) and nothing is written. Each item is the exact `set_page_module_content` shape: `{pageId, blockName, position, contentValues}` with `contentValues` keyed by the module's declared field names; each fully replaces that placement's values. Items may span multiple pages. Same UNSYNCED-only contract as the singular tool: a SYNCED placement in the batch aborts it with the fork/commit guidance — fork_placement_content that placement (or set_content_instance_values deliberately) and resend. **When NOT to use:** when the page and its modules don't exist yet, `build_page` places modules WITH their content in one call — never build a page as add_module + set_page_module_content_many when one build_page call does both. For one placement the singular tool is fine. Typical call: `{items: [{pageId: \"…\", blockName: \"content\", position: 0, contentValues: {hero_title: \"…\"}}, {pageId: \"…\", blockName: \"content\", position: 1, contentValues: {body: \"…\"}}]}`.",
|
|
707
|
+
"description": "Set content values for SEVERAL placements in ONE transaction — prefer this over multiple `set_page_module_content` calls whenever a content pass touches more than one placement (a whole page's text, the same fix across pages; §11 bulk-first). All-or-nothing: any invalid item aborts the whole batch (the error names `items[i]`, the placement, and for value problems the failing field) and nothing is written. Each item is the exact `set_page_module_content` shape: `{pageId, blockName, position, contentValues}` with `contentValues` keyed by the module's declared field names; each fully replaces that placement's values. Items may span multiple pages. Same UNSYNCED-only contract as the singular tool: a SYNCED placement in the batch aborts it with the fork/commit guidance — fork_placement_content that placement (or set_content_instance_values deliberately) and resend. **When NOT to use:** when the page and its modules don't exist yet, `build_page` places modules WITH their content in one call — never build a page as add_module + set_page_module_content_many when one build_page call does both. For one placement the singular tool is fine. Typical call: `{items: [{pageId: \"…\", blockName: \"content\", position: 0, contentValues: {hero_title: \"…\"}}, {pageId: \"…\", blockName: \"content\", position: 1, contentValues: {body: \"…\"}}]}`. **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first, or add the link after. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
708
708
|
"inputSchema": {
|
|
709
709
|
"type": "object",
|
|
710
710
|
"additionalProperties": false,
|
|
@@ -751,7 +751,7 @@
|
|
|
751
751
|
},
|
|
752
752
|
{
|
|
753
753
|
"name": "create_content_instances",
|
|
754
|
-
"description": "Mint SEVERAL content_instances in ONE transaction — prefer this over multiple `create_content_instance` calls whenever you need more than one instance (§11 bulk-first: one round-trip instead of N). All-or-nothing: any invalid item aborts the whole batch (the error names `instances[i]` and, for value problems, the failing field) and nothing is written. Each item is the exact `create_content_instance` shape: `{moduleId, values, purpose?, slug?, displayName?}` — `purpose` required for SHARED rows (why this row exists as a reusable instance; surfaced in `## Content Library`), `values` keyed by the module's declared field names. **When NOT to use:** if you're also creating the page and its modules, `build_page` does the whole assembly (modules + instances + placements) in one call — use that instead. For a single instance the singular tool is fine. After minting, bind rows to placements via `set_placement_content` (or let `build_page` content.source='existing' bind them). Typical call: `{instances: [{moduleId: \"…\", purpose: \"Footer CTA shared across product pages\", values: {cta_label: \"Start free\", cta_href: \"/signup\"}}, {moduleId: \"…\", values: {…}}]}`.",
|
|
754
|
+
"description": "Mint SEVERAL content_instances in ONE transaction — prefer this over multiple `create_content_instance` calls whenever you need more than one instance (§11 bulk-first: one round-trip instead of N). All-or-nothing: any invalid item aborts the whole batch (the error names `instances[i]` and, for value problems, the failing field) and nothing is written. Each item is the exact `create_content_instance` shape: `{moduleId, values, purpose?, slug?, displayName?}` — `purpose` required for SHARED rows (why this row exists as a reusable instance; surfaced in `## Content Library`), `values` keyed by the module's declared field names. **When NOT to use:** if you're also creating the page and its modules, `build_page` does the whole assembly (modules + instances + placements) in one call — use that instead. For a single instance the singular tool is fine. After minting, bind rows to placements via `set_placement_content` (or let `build_page` content.source='existing' bind them). Typical call: `{instances: [{moduleId: \"…\", purpose: \"Footer CTA shared across product pages\", values: {cta_label: \"Start free\", cta_href: \"/signup\"}}, {moduleId: \"…\", values: {…}}]}`. **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first, or add the link after. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
755
755
|
"inputSchema": {
|
|
756
756
|
"type": "object",
|
|
757
757
|
"additionalProperties": false,
|
|
@@ -865,7 +865,7 @@
|
|
|
865
865
|
},
|
|
866
866
|
{
|
|
867
867
|
"name": "create_content_instance",
|
|
868
|
-
"description": "Mint ONE shared, reusable content_instance for a module — used when the same content should appear identically on N pages (the site footer's copyright, a brand banner across product pages, a repeated CTA in the blog). **Prefer `create_content_instances` (plural) when minting more than one instance** — one transaction instead of N round-trips (§11 bulk-first) — and `build_page` when you're also creating the page/modules (its per-module `content` payload mints and binds in the same call). The new row is then bindable to N placements via `set_placement_content({syncMode:'synced'})` so editing it propagates everywhere bound. **Required v0.12.0 input:** `purpose` — a one-line rationale for why this is a shared row (\"The brand footer used across the whole marketing site\", \"Pricing CTA — appears at the bottom of every pricing-adjacent page\"). The `## Content Library` block surfaces this so your future self can decide reuse vs fork without asking the operator. **When NOT to use:** for one-off per-page content, prefer `set_page_module_content` — that path auto-mints an unsynced (private) instance per placement, no shared semantics, no decision needed. Only mint a shared instance when reuse is the actual intent. **Decision rule before calling:** check `## Content Library` first. If a row with the matching purpose + module + appropriate placement pattern already exists, BIND to it via `set_placement_content` instead of minting a new one. Duplicate shared rows defeat the point. `values` keys are the module's declared field names (`{{fieldName}}` placeholders); `slug` is an optional kebab-case handle (must be unique per module) for easy AI-side reference.",
|
|
868
|
+
"description": "Mint ONE shared, reusable content_instance for a module — used when the same content should appear identically on N pages (the site footer's copyright, a brand banner across product pages, a repeated CTA in the blog). **Prefer `create_content_instances` (plural) when minting more than one instance** — one transaction instead of N round-trips (§11 bulk-first) — and `build_page` when you're also creating the page/modules (its per-module `content` payload mints and binds in the same call). The new row is then bindable to N placements via `set_placement_content({syncMode:'synced'})` so editing it propagates everywhere bound. **Required v0.12.0 input:** `purpose` — a one-line rationale for why this is a shared row (\"The brand footer used across the whole marketing site\", \"Pricing CTA — appears at the bottom of every pricing-adjacent page\"). The `## Content Library` block surfaces this so your future self can decide reuse vs fork without asking the operator. **When NOT to use:** for one-off per-page content, prefer `set_page_module_content` — that path auto-mints an unsynced (private) instance per placement, no shared semantics, no decision needed. Only mint a shared instance when reuse is the actual intent. **Decision rule before calling:** check `## Content Library` first. If a row with the matching purpose + module + appropriate placement pattern already exists, BIND to it via `set_placement_content` instead of minting a new one. Duplicate shared rows defeat the point. `values` keys are the module's declared field names (`{{fieldName}}` placeholders); `slug` is an optional kebab-case handle (must be unique per module) for easy AI-side reference. **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first, or add the link after. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
869
869
|
"inputSchema": {
|
|
870
870
|
"type": "object",
|
|
871
871
|
"additionalProperties": false,
|
|
@@ -899,7 +899,7 @@
|
|
|
899
899
|
},
|
|
900
900
|
{
|
|
901
901
|
"name": "set_content_instance_values",
|
|
902
|
-
"description": "Edit a content_instance's values. **BLAST RADIUS = placementCount in `## Content Library`** — this propagates to EVERY placement bound with sync_mode='synced'. **Read `## Content Library` first** to see the row's `purpose` + sample pages + placement count before editing. If the operator described an edit that should ONLY affect one page, this is the WRONG tool — use `set_page_module_content` (unsynced placements) or `fork_placement_content` first (synced placements that need to be detached). `values` fully replaces existing values (zero-merge); read first via `get_content_instance` if you need to preserve other fields. **Nested fields (kind `module` / `module-list`):** the value is `{ moduleId, contentInstanceId }` (single) or an array of them (list). The referenced module's stable `type` MUST be in the field's `allowedModuleTypes` when that whitelist is set — see each module's `type` in `## Modules`. Reuse an existing module of an allowed type rather than minting a duplicate; if none fits, create one with `type` set to an allowed value. Optional metadata edits in the same write: `slug` + `displayName` (pass `null` to clear), and v0.12.0 `purpose` (rewrite the rationale when the operator's intent for this shared row has shifted — keeps `## Content Library` accurate for your future self). Prefer `set_content_instance_values_many` over multiple `set_content_instance_values` calls when changing more than one.",
|
|
902
|
+
"description": "Edit a content_instance's values. **BLAST RADIUS = placementCount in `## Content Library`** — this propagates to EVERY placement bound with sync_mode='synced'. **Read `## Content Library` first** to see the row's `purpose` + sample pages + placement count before editing. If the operator described an edit that should ONLY affect one page, this is the WRONG tool — use `set_page_module_content` (unsynced placements) or `fork_placement_content` first (synced placements that need to be detached). `values` fully replaces existing values (zero-merge); read first via `get_content_instance` if you need to preserve other fields. **Nested fields (kind `module` / `module-list`):** the value is `{ moduleId, contentInstanceId }` (single) or an array of them (list). The referenced module's stable `type` MUST be in the field's `allowedModuleTypes` when that whitelist is set — see each module's `type` in `## Modules`. Reuse an existing module of an allowed type rather than minting a duplicate; if none fits, create one with `type` set to an allowed value. Optional metadata edits in the same write: `slug` + `displayName` (pass `null` to clear), and v0.12.0 `purpose` (rewrite the rationale when the operator's intent for this shared row has shifted — keeps `## Content Library` accurate for your future self). Prefer `set_content_instance_values_many` over multiple `set_content_instance_values` calls when changing more than one. **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first, or add the link after. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
903
903
|
"inputSchema": {
|
|
904
904
|
"type": "object",
|
|
905
905
|
"properties": {
|
|
@@ -981,7 +981,7 @@
|
|
|
981
981
|
},
|
|
982
982
|
{
|
|
983
983
|
"name": "set_content_instance_values_many",
|
|
984
|
-
"description": "Edit SEVERAL content_instances in ONE transaction — prefer this over multiple `set_content_instance_values` calls whenever you change more than one (§11 bulk-first: one round-trip + one snapshot instead of N). All-or-nothing: any invalid item aborts the whole batch (the error names `items[i]`) and nothing is written. **Same per-item BLAST RADIUS caveat as the singular tool:** each item propagates to EVERY placement bound to that instance with sync_mode='synced' — read `## Content Library` first. `values` fully replaces existing values (zero-merge) per item. Each item is the exact `set_content_instance_values` shape: `{id, values, expectedVersion?, slug?, displayName?, purpose?}`.",
|
|
984
|
+
"description": "Edit SEVERAL content_instances in ONE transaction — prefer this over multiple `set_content_instance_values` calls whenever you change more than one (§11 bulk-first: one round-trip + one snapshot instead of N). All-or-nothing: any invalid item aborts the whole batch (the error names `items[i]`) and nothing is written. **Same per-item BLAST RADIUS caveat as the singular tool:** each item propagates to EVERY placement bound to that instance with sync_mode='synced' — read `## Content Library` first. `values` fully replaces existing values (zero-merge) per item. Each item is the exact `set_content_instance_values` shape: `{id, values, expectedVersion?, slug?, displayName?, purpose?}`. **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first, or add the link after. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
985
985
|
"inputSchema": {
|
|
986
986
|
"type": "object",
|
|
987
987
|
"additionalProperties": false,
|
|
@@ -1865,6 +1865,34 @@
|
|
|
1865
1865
|
}
|
|
1866
1866
|
}
|
|
1867
1867
|
},
|
|
1868
|
+
{
|
|
1869
|
+
"name": "get_link_migration_report",
|
|
1870
|
+
"description": "Internal links are stored as page references (they follow a page when it is renamed or moved). Content written before that was converted once by the link migration; this tool reports it: links converted per place, and every link it could NOT convert (it matched no page or media asset — never guessed), with the module / content / menu it sits in. Use when the operator asks about broken or old links after an update, or before fixing links site-wide. Fix each listed link with the normal edit tools (edit_module, set_content_instance_values, set_structured_set) by pointing it at an existing page's path (list_pages) or removing it; the edit stores the reference. A link in a layout's own HTML (the page frame) is the Owner's to fix under Settings → Layouts: name the layout and the path to use. Filter with `search` (matches the link, the label and the field). Running or reverting the migration is the Owner's (Settings → Internal links); you only read.",
|
|
1871
|
+
"inputSchema": {
|
|
1872
|
+
"type": "object",
|
|
1873
|
+
"additionalProperties": false,
|
|
1874
|
+
"properties": {
|
|
1875
|
+
"runId": {
|
|
1876
|
+
"type": "string",
|
|
1877
|
+
"format": "uuid",
|
|
1878
|
+
"description": "Omit for the latest site-wide run."
|
|
1879
|
+
},
|
|
1880
|
+
"search": {
|
|
1881
|
+
"type": "string",
|
|
1882
|
+
"maxLength": 200
|
|
1883
|
+
},
|
|
1884
|
+
"limit": {
|
|
1885
|
+
"type": "integer",
|
|
1886
|
+
"minimum": 1,
|
|
1887
|
+
"maximum": 500
|
|
1888
|
+
},
|
|
1889
|
+
"offset": {
|
|
1890
|
+
"type": "integer",
|
|
1891
|
+
"minimum": 0
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
},
|
|
1868
1896
|
{
|
|
1869
1897
|
"name": "set_migration_budget",
|
|
1870
1898
|
"description": "Propose the money ceiling for a migration run, in the currency the operator named. Approving an import proposal already arms a ceiling from the estimate's high end, so call this only when the operator names a DIFFERENT amount — or to RAISE the budget after the gate paused the run ('Cost ceiling reached'), which re-arms it so the run can continue. `ceiling` is the whole-run budget in major units (10 for €10), not per-page. This is a TWO-STEP flow: you propose the amount, the operator clicks Approve on the card in the chat, and only then is the ceiling recorded. Do not claim the budget was applied, and do not carry on as if the higher ceiling were already in force. Never propose an amount the operator has not named — the click confirms their number, it does not authorise yours. Use `check_run_budget` to track spend against the ceiling.\n\nOver MCP: when this call needs the operator's approval, it queues an approval card in their Caelo admin (Live Edit chat and the pending-approvals page) and returns the outcome once they decide — tell them to approve the card; never ask them to paste a prompt into the Caelo chat.",
|
|
@@ -2062,7 +2090,7 @@
|
|
|
2062
2090
|
},
|
|
2063
2091
|
{
|
|
2064
2092
|
"name": "bug_report",
|
|
2065
|
-
"description": "File a bug report against Caelo ITSELF when a tool or surface behaves contrary to its documented contract — e.g. a tool result contradicts persisted state, a screenshot/render ignores a parameter, an error fires on input the description says is valid. This is for defects in the SYSTEM, not for your own mistakes (wrong arguments, misread state) and not for content/design issues on the site being built. Report ONCE per distinct defect (do not re-file the same bug in one session), then CONTINUE the task via a workaround when one exists. Only when the bug genuinely blocks the task: file with blockedTask=true, tell the operator in one sentence what is blocked, and stop that line of work instead of retrying into the same wall. `whatHappened` = what you observed (include the exact tool + arguments); `expected` = what the tool's contract promised; `evidence` = the result excerpt that proves it.",
|
|
2093
|
+
"description": "File a bug report against Caelo ITSELF when a tool or surface behaves contrary to its documented contract — e.g. a tool result contradicts persisted state, a screenshot/render ignores a parameter, an error fires on input the description says is valid. This is for defects in the SYSTEM, not for your own mistakes (wrong arguments, misread state) and not for content/design issues on the site being built. Report ONCE per distinct defect (do not re-file the same bug in one session), then CONTINUE the task via a workaround when one exists. Only when the bug genuinely blocks the task: file with blockedTask=true, tell the operator in one sentence what is blocked, and stop that line of work instead of retrying into the same wall. `whatHappened` = what you observed (include the exact tool + arguments); `expected` = what the tool's contract promised; `evidence` = the result excerpt that proves it. The result says where the report went (stored on this site only, or sent to the Caelo team) — tell the operator exactly that and never claim the Caelo team was notified unless the result says it was sent.",
|
|
2066
2094
|
"inputSchema": {
|
|
2067
2095
|
"type": "object",
|
|
2068
2096
|
"additionalProperties": false,
|
|
@@ -2390,7 +2418,7 @@
|
|
|
2390
2418
|
},
|
|
2391
2419
|
{
|
|
2392
2420
|
"name": "add_module",
|
|
2393
|
-
"description": "Add ONE module to a page, a layout (site-wide chrome on every page), or a template (fans out to every page of that page-type). **Prefer `build_page` when a PAGE needs more than one module** — it places the whole ordered list WITH content in one transaction. `target` = 'page' | 'layout' | 'template'. `targetRef` is that page's id or URL path ('/docs/setup'), or the layout's / template's slug or id. A bare page slug works only while it names one page — nested pages can share a slug. Two modes, on ALL three targets. **Reuse (check the catalog first):** pass `moduleId` of an existing module from `## Modules` to place/fan-out a SHARED module without minting a duplicate — a later edit_module then updates it everywhere at once. **Mint:** pass `html` (+ `displayName`, `kind`, and semantic snake_case `fields`) to author a new module and place it; raw HTML is fine, it is parametrised into {{fields}} automatically. `moduleId` is placement-only: if you also pass authoring fields (a different label, new html, …) the call still succeeds but those fields are NOT applied and the result says so — to actually change a shared module, place it by `moduleId` and then call edit_module. **The initial content comes IN THIS CALL, always via `values`** ({fieldName: value, …}): on 'page' it fills the placement's content_instance, on 'template' every fanned-out placement gets it, on 'layout' it is stored as the minted module's field defaults (chrome has no content_instance — so layout `values` require explicit `fields`, and are rejected with `moduleId` reuse: a shared module renders its stored defaults; edit_module changes them). A mint with fields but neither `values` nor field defaults is REJECTED — it would render empty placeholders until a second call. `blockName` must be a real block on the target; the handler returns the available block set if it isn't. NOTE on `position`: pass the literal string \"top\" or \"bottom\", OR a bare integer (0, 1, 2…). Prefer a bare integer (`0`, not `\"0\"`).\n\nOver this connection the install's AI is not used: the AI part comes back to you — a work item to answer with your own model and send back through the tool it names, or an `AiDelegated:` answer that says what to do instead (see caelo_get_context).",
|
|
2421
|
+
"description": "Add ONE module to a page, a layout (site-wide chrome on every page), or a template (fans out to every page of that page-type). **Prefer `build_page` when a PAGE needs more than one module** — it places the whole ordered list WITH content in one transaction. `target` = 'page' | 'layout' | 'template'. `targetRef` is that page's id or URL path ('/docs/setup'), or the layout's / template's slug or id. A bare page slug works only while it names one page — nested pages can share a slug. Two modes, on ALL three targets. **Reuse (check the catalog first):** pass `moduleId` of an existing module from `## Modules` to place/fan-out a SHARED module without minting a duplicate — a later edit_module then updates it everywhere at once. **Mint:** pass `html` (+ `displayName`, `kind`, and semantic snake_case `fields`) to author a new module and place it; raw HTML is fine, it is parametrised into {{fields}} automatically. `moduleId` is placement-only: if you also pass authoring fields (a different label, new html, …) the call still succeeds but those fields are NOT applied and the result says so — to actually change a shared module, place it by `moduleId` and then call edit_module. **The initial content comes IN THIS CALL, always via `values`** ({fieldName: value, …}): on 'page' it fills the placement's content_instance, on 'template' every fanned-out placement gets it, on 'layout' it is stored as the minted module's field defaults (chrome has no content_instance — so layout `values` require explicit `fields`, and are rejected with `moduleId` reuse: a shared module renders its stored defaults; edit_module changes them). A mint with fields but neither `values` nor field defaults is REJECTED — it would render empty placeholders until a second call. `blockName` must be a real block on the target; the handler returns the available block set if it isn't. NOTE on `position`: pass the literal string \"top\" or \"bottom\", OR a bare integer (0, 1, 2…). Prefer a bare integer (`0`, not `\"0\"`). **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first, or add the link after. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.\n\nOver this connection the install's AI is not used: the AI part comes back to you — a work item to answer with your own model and send back through the tool it names, or an `AiDelegated:` answer that says what to do instead (see caelo_get_context).",
|
|
2394
2422
|
"inputSchema": {
|
|
2395
2423
|
"type": "object",
|
|
2396
2424
|
"additionalProperties": false,
|
|
@@ -2821,7 +2849,7 @@
|
|
|
2821
2849
|
},
|
|
2822
2850
|
{
|
|
2823
2851
|
"name": "set_structured_set",
|
|
2824
|
-
"description": "Upsert a structured-data set. Creates the set if `(kind, slug)` doesn't exist yet; REPLACES the items array if it does (NOT append — pass the full desired list). Kinds: nav-menu, tags, taxonomy, link-list. The per-item shape is enforced by Zod at the tool boundary; a mismatch is rejected with a structured error naming the offending field. Read before you write: sets are NOT inlined in the system prompt — call `get_structured_set({kind, slug})` first and modify its items. Visibility: writing a set does NOT by itself change the site. The only built-in binding renders a set through a module whose slug is exactly `nav-menu-<set-slug>` placed directly in a layout/page block — that module's own HTML is replaced by the built-in renderer (`children` render as nested submenus). No AI tool can mint such a slug today (admin UI only — issue #414); if no bound module exists, render nav in the chrome module via a `link-list` field instead. Other kinds render only where module HTML/JS consumes them. Common slugs: nav-menu/header-main, nav-menu/footer-main, tags/blog. Theme tokens are NOT structured sets anymore (v0.11.0) — use `set_theme_tokens` for theme edits and `propose_create_theme` to mint a new theme.",
|
|
2852
|
+
"description": "Upsert a structured-data set. Creates the set if `(kind, slug)` doesn't exist yet; REPLACES the items array if it does (NOT append — pass the full desired list). Kinds: nav-menu, tags, taxonomy, link-list. The per-item shape is enforced by Zod at the tool boundary; a mismatch is rejected with a structured error naming the offending field. Read before you write: sets are NOT inlined in the system prompt — call `get_structured_set({kind, slug})` first and modify its items. Visibility: writing a set does NOT by itself change the site. The only built-in binding renders a set through a module whose slug is exactly `nav-menu-<set-slug>` placed directly in a layout/page block — that module's own HTML is replaced by the built-in renderer (`children` render as nested submenus). No AI tool can mint such a slug today (admin UI only — issue #414); if no bound module exists, render nav in the chrome module via a `link-list` field instead. Other kinds render only where module HTML/JS consumes them. Common slugs: nav-menu/header-main, nav-menu/footer-main, tags/blog. Theme tokens are NOT structured sets anymore (v0.11.0) — use `set_theme_tokens` for theme edits and `propose_create_theme` to mint a new theme. **Internal links** (`href`s in HTML, `url`/`link` fields, link-list and nav items): write the page's path, e.g. `/about` — it must be an EXISTING page (list_pages); a link to a page that does not exist yet is refused, so create the target first, or add the link after. Exception, a site import you are rebuilding in this chat: a link to a page of that import run is kept and resolved by resolve_import_links at the end. Images/files: their media URL (find_media). Other sites: the full https:// URL.",
|
|
2825
2853
|
"inputSchema": {
|
|
2826
2854
|
"type": "object",
|
|
2827
2855
|
"additionalProperties": false,
|
|
@@ -4120,6 +4148,40 @@
|
|
|
4120
4148
|
"additionalProperties": false
|
|
4121
4149
|
}
|
|
4122
4150
|
},
|
|
4151
|
+
{
|
|
4152
|
+
"name": "check_links",
|
|
4153
|
+
"description": "Check the site's internal links (links between its own pages and media) in the draft you see — every place a link is stored: page content, module HTML, field defaults, layouts, templates, navigation menus and plugin data. Reports links to deleted pages or media and links without a page in the linking page's language (both BLOCK Stage and Publish live), links to unpublished pages (BLOCK Publish live, a warning at Stage), and old plain-path links (warnings). Each problem names the page, where the link is stored and the tool that fixes it. Use it before stage_changes when you deleted, unpublished or translated pages, when a Stage was refused for links, or when the operator asks about broken links. Not for external links or what the built site serves (get_quality_audit has the staged-build link check).",
|
|
4154
|
+
"inputSchema": {
|
|
4155
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4156
|
+
"type": "object",
|
|
4157
|
+
"properties": {
|
|
4158
|
+
"for": {
|
|
4159
|
+
"description": "Which gate to check for. 'publish' (default): Publish live / promote — a link to an unpublished page blocks. 'stage': such a link is only a warning.",
|
|
4160
|
+
"type": "string",
|
|
4161
|
+
"enum": [
|
|
4162
|
+
"stage",
|
|
4163
|
+
"publish"
|
|
4164
|
+
]
|
|
4165
|
+
},
|
|
4166
|
+
"pagePath": {
|
|
4167
|
+
"description": "Only problems on this page (its path, e.g. '/about'); site-wide places (menus, plugin data) are always included.",
|
|
4168
|
+
"type": "string",
|
|
4169
|
+
"minLength": 1,
|
|
4170
|
+
"maxLength": 2000
|
|
4171
|
+
},
|
|
4172
|
+
"blockingOnly": {
|
|
4173
|
+
"description": "True = only problems that block.",
|
|
4174
|
+
"type": "boolean"
|
|
4175
|
+
},
|
|
4176
|
+
"limit": {
|
|
4177
|
+
"type": "integer",
|
|
4178
|
+
"minimum": 1,
|
|
4179
|
+
"maximum": 500
|
|
4180
|
+
}
|
|
4181
|
+
},
|
|
4182
|
+
"additionalProperties": false
|
|
4183
|
+
}
|
|
4184
|
+
},
|
|
4123
4185
|
{
|
|
4124
4186
|
"name": "get_quality_audit",
|
|
4125
4187
|
"description": "Read the quality check of the staged site: the page checks plugins add (such as SEO checks; when an active plugin adds any) that read EVERY page on every Stage, plus a Lighthouse audit (Performance, Accessibility, Best Practices, SEO; mobile) when the Stage changed how pages render. With no arguments: the newest audit of THIS chat's Stages. Shows per page the scores (the target is 100 in every category), the Performance metrics behind the Performance score (FCP, LCP, TBT, CLS, Speed Index: the best run's value with Lighthouse's rating, plus the LCP element, the LCP breakdown and the elements that shifted with their causes), every problem with its Lighthouse audit id (e.g. `image-alt`, `color-contrast`, `meta-description`, `render-blocking-insight`) and the elements it flagged (CSS selector, HTML snippet, why — e.g. the measured contrast and colours), every page-check problem with its rule id (`<check>/<rule>`, e.g. `seo-check/title-missing` — rule ids contain a slash) and a fix hint naming the tool to use, site-wide findings, non-blocking warnings and hints, and infrastructure errors. THE TARGET IS 100 IN EVERY CATEGORY AND ZERO FINDINGS: every category below 100, every scored Lighthouse audit below a perfect score, and every page-check error is a PROBLEM that blocks Publish live. A score like 90 is NOT acceptable on its own — keep fixing until 100 wherever achievable. Problems an editor accepted are marked ACCEPTED and do not block (a worse value than accepted blocks again). Warnings and hints never block. Use this after a Stage to see what to fix; use list_quality_acceptances to see what editors already accepted. To make a page faster, read its metrics here and fix the worst one in the module that renders the named element, then Stage again and re-read — repeat until Performance is 100. Use this instead of running Lighthouse yourself: the audit runs against the real staged build, which no other URL serves. Only what truly cannot be fixed goes to the operator, as one decision per group (accept_quality_findings with groups), with the reason why. A finding marked CAELO PLATFORM (back/forward cache, caching of Caelo's own files, server response, compression, redirects) is decided by Caelo's hosting/build setup, not by the site: report it with bug_report and never edit the site for it; it blocks until Caelo fixes it or the operator decides to accept it. Without an open operator chat (Power-MCP) a call on a queued or running audit waits for its result (up to ~50 s; call again if it is still running). Typical input: {} or { auditRunId }.",
|
|
@@ -4535,7 +4597,7 @@
|
|
|
4535
4597
|
},
|
|
4536
4598
|
{
|
|
4537
4599
|
"name": "screenshot_page",
|
|
4538
|
-
"description": "Capture a screenshot of the rendered page (the image comes back WITHIN this same turn — keep working and analyse it on your next step, do NOT end your turn to wait for it). Use for the GROSS visual impression — 'does the hero feel crowded?', 'is the color/imagery right?', 'what's the overall layout impression?'. Works on EVERY surface: in an interactive chat the operator's browser captures (html2canvas on their exact viewport); without an attached browser (MCP, background sends, subagent turns) the server renders the branch preview in real headless Chromium instead — the result text names which backend ran. FIDELITY LIMIT (browser-captured results only) — html2canvas is an APPROXIMATION, not a real browser render. It reliably mis-renders TWO things: (1) fine text spacing — words can show large irregular gaps that look like broken text-align:justify, and (2) flex-wrap — wrapping rows can stack one-item-per-line. These are html2canvas artifacts, NOT real CSS bugs. If a browser-captured screenshot shows justified-looking word gaps or single-column flex stacking, DO NOT edit CSS or file a bug from the screenshot alone — confirm against `inspect_page_render` first. Server-rendered results are real Chromium output and carry no such artifacts. ALWAYS call this after composing a page or making structural/styling changes — desktop AND mobile viewports — and fix real issues the screenshot reveals BEFORE telling the operator you're done (max two review rounds; skip for content-only edits). For ANY spacing/layout/CSS question (word gaps, flex stacking, white halo around the header, wrong colors, broken layout) prefer `inspect_page_render` — its summary then `target`/`search` give the composed HTML and each CSS layer separately, the accurate source of truth for spacing + layout. Pass `selector` (CSS selector) to capture a SINGLE element instead of the whole page — right choice when checking one module (a footer, a hero) instead of the page. By default the capture shows THIS chat's branch preview (your pending edits included); pass `chatBranchId` only to capture a different branch. The image is available to you in this turn only, not persisted across the chat.",
|
|
4600
|
+
"description": "Capture a screenshot of the rendered page (the image comes back WITHIN this same turn — keep working and analyse it on your next step, do NOT end your turn to wait for it). Use for the GROSS visual impression — 'does the hero feel crowded?', 'is the color/imagery right?', 'what's the overall layout impression?'. Works on EVERY surface: in an interactive chat the operator's browser captures (html2canvas on their exact viewport); without an attached browser (MCP, background sends, subagent turns) the server renders the branch preview in real headless Chromium instead — the result text names which backend ran. FIDELITY LIMIT (browser-captured results only) — html2canvas is an APPROXIMATION, not a real browser render. It reliably mis-renders TWO things: (1) fine text spacing — words can show large irregular gaps that look like broken text-align:justify, and (2) flex-wrap — wrapping rows can stack one-item-per-line. These are html2canvas artifacts, NOT real CSS bugs. If a browser-captured screenshot shows justified-looking word gaps or single-column flex stacking, DO NOT edit CSS or file a bug from the screenshot alone — confirm against `inspect_page_render` first. Server-rendered results are real Chromium output and carry no such artifacts. DESKTOP IS NOT ONE WIDTH — type sized with vw/clamp() wraps differently per width, so a headline that sits on 3 lines at 1280px can take 4 at 1740px. In an interactive chat, desktop captures the operator's live preview size (the result states the size and whose view it is); omit `width` for that capture. For typography/layout reviews ALSO capture a second desktop width via `width` (1280 when the operator's view is wider, 1920 when it is narrower) and make the design hold at both. When the operator reports a wrap or break you don't see, re-capture without `width` before concluding it is fine. ALWAYS call this after composing a page or making structural/styling changes — desktop AND mobile viewports — and fix real issues the screenshot reveals BEFORE telling the operator you're done (max two review rounds; skip for content-only edits). For ANY spacing/layout/CSS question (word gaps, flex stacking, white halo around the header, wrong colors, broken layout) prefer `inspect_page_render` — its summary then `target`/`search` give the composed HTML and each CSS layer separately, the accurate source of truth for spacing + layout. Pass `selector` (CSS selector) to capture a SINGLE element instead of the whole page — right choice when checking one module (a footer, a hero) instead of the page. By default the capture shows THIS chat's branch preview (your pending edits included); pass `chatBranchId` only to capture a different branch. The image is available to you in this turn only, not persisted across the chat.",
|
|
4539
4601
|
"inputSchema": {
|
|
4540
4602
|
"type": "object",
|
|
4541
4603
|
"additionalProperties": false,
|
|
@@ -4559,7 +4621,13 @@
|
|
|
4559
4621
|
"tablet",
|
|
4560
4622
|
"mobile"
|
|
4561
4623
|
],
|
|
4562
|
-
"description": "Optional viewport
|
|
4624
|
+
"description": "Optional viewport. desktop (default) captures at the operator's live preview size in an interactive chat (falls back to the 1280x800 preset when no preview is open or on the server-side renderer); tablet = 768x1024, mobile = 375x812."
|
|
4625
|
+
},
|
|
4626
|
+
"width": {
|
|
4627
|
+
"type": "integer",
|
|
4628
|
+
"minimum": 320,
|
|
4629
|
+
"maximum": 3840,
|
|
4630
|
+
"description": "Optional layout width in CSS px — overrides the viewport's width (height stays the viewport's). Use it to check a SECOND desktop width on purpose (e.g. 1280 and 1920), not for the first capture: omitting it shows you the operator's own view."
|
|
4563
4631
|
},
|
|
4564
4632
|
"selector": {
|
|
4565
4633
|
"type": "string",
|
|
@@ -5527,7 +5595,8 @@
|
|
|
5527
5595
|
"private_files",
|
|
5528
5596
|
"image_generation",
|
|
5529
5597
|
"site_media_read",
|
|
5530
|
-
"content_variants"
|
|
5598
|
+
"content_variants",
|
|
5599
|
+
"link_resolve"
|
|
5531
5600
|
]
|
|
5532
5601
|
},
|
|
5533
5602
|
"target": {
|
|
@@ -5613,7 +5682,7 @@
|
|
|
5613
5682
|
},
|
|
5614
5683
|
{
|
|
5615
5684
|
"name": "propose_create_user",
|
|
5616
|
-
"description": "Propose inviting a new user to the CMS. Provide email + displayName + roleNames. DO NOT include a password — the Owner approves and a one-time temporary password is generated server-side. On Google Cloud installs the approval also lets this email through Google IAP (browser and MCP), so use the person's Google account address (Gmail or Workspace); the result's `operatorAccess` says whether that worked — relay any `warning` to the operator. APPROVAL-GATED: calling this PAUSES for the operator's Approve/Reject right in the chat before anything changes. Do not claim the change is live until it is approved.\n\nOver MCP: call this tool yourself — it works here. The call queues an approval card in the operator's Caelo admin (their Live Edit chat and the pending-approvals page), waits for their Approve/Reject and returns the outcome (if the wait times out, continue with caelo_await_approval). Tell the operator you requested it and that they approve the card in Caelo; never ask them to paste a prompt into the Caelo chat to run it.",
|
|
5685
|
+
"description": "Propose inviting a new user to the CMS. Provide email + displayName + roleNames. DO NOT include a password — the Owner approves and a one-time temporary password is generated server-side. On Google Cloud installs the approval also lets this email through Google IAP (browser and MCP), so use the person's Google account address (Gmail or Workspace); the result's `operatorAccess` says whether that worked — relay any `warning` to the operator. An address outside the project's Google organisation is refused until the Owner has set up Google sign-in (Settings → Google sign-in, a console step only a human can do): relay that error's next step to the operator instead of retrying or proposing another address. APPROVAL-GATED: calling this PAUSES for the operator's Approve/Reject right in the chat before anything changes. Do not claim the change is live until it is approved.\n\nOver MCP: call this tool yourself — it works here. The call queues an approval card in the operator's Caelo admin (their Live Edit chat and the pending-approvals page), waits for their Approve/Reject and returns the outcome (if the wait times out, continue with caelo_await_approval). Tell the operator you requested it and that they approve the card in Caelo; never ask them to paste a prompt into the Caelo chat to run it.",
|
|
5617
5686
|
"inputSchema": {
|
|
5618
5687
|
"type": "object",
|
|
5619
5688
|
"additionalProperties": false,
|
|
@@ -7418,6 +7487,23 @@
|
|
|
7418
7487
|
"additionalProperties": false
|
|
7419
7488
|
}
|
|
7420
7489
|
},
|
|
7490
|
+
{
|
|
7491
|
+
"name": "resolve_import_links",
|
|
7492
|
+
"description": "While you rebuild an import run, link to the migrated site's pages by their real paths even before you have built them: a link to a page of THIS run (a crawled page, a page in the run's URL list, or a page the crawled pages link to) is kept as written and recorded against the run instead of refused. A link to any other unknown path is still refused. Call this ONCE when every page of the run is built (before get_import_run_report): it rewrites the recorded content so each link whose page now exists is stored as that page, and lists the links that still match no page, with where they are — fix each (build the page, or point the link at an existing page's path) and call it again. Safe to repeat; the run's cleanup does it too. Do not use it for links outside an import (those are checked when you write them).",
|
|
7493
|
+
"inputSchema": {
|
|
7494
|
+
"type": "object",
|
|
7495
|
+
"additionalProperties": false,
|
|
7496
|
+
"required": [
|
|
7497
|
+
"runId"
|
|
7498
|
+
],
|
|
7499
|
+
"properties": {
|
|
7500
|
+
"runId": {
|
|
7501
|
+
"type": "string",
|
|
7502
|
+
"format": "uuid"
|
|
7503
|
+
}
|
|
7504
|
+
}
|
|
7505
|
+
}
|
|
7506
|
+
},
|
|
7421
7507
|
{
|
|
7422
7508
|
"name": "send_test_email",
|
|
7423
7509
|
"description": "Send the fixed 'Caelo email transport test' message through the site's configured email transport and report whether the provider accepted it. Use after an email config change was approved (`propose_set_email_config`), or when password-reset / form emails seem not to arrive. The recipient must be on the sender's own domain (e.g. sender noreply@example.com → you may test to team@example.com); for any other address, tell the operator to send the test from Settings → Email. Subject and body are fixed — this cannot send custom mail. A failure names the provider's reason (bad API key, unverified sender domain, transport 'none'); relay it rather than retrying unchanged.",
|
|
@@ -7582,7 +7668,7 @@
|
|
|
7582
7668
|
},
|
|
7583
7669
|
{
|
|
7584
7670
|
"name": "list_bug_reports",
|
|
7585
|
-
"description": "List the defect reports already filed against Caelo (by you via bug_report, or auto-captured by the chat), newest first. Call BEFORE bug_report to see whether the problem is already reported; filter by status (new / triaged / fixed / invalid).",
|
|
7671
|
+
"description": "List the defect reports already filed against Caelo (by you via bug_report, or auto-captured by the chat), newest first. Call BEFORE bug_report to see whether the problem is already reported; filter by status (new / triaged / fixed / invalid). The forwarding column says where each report went: local_only = stored on this site only, the Caelo team has NOT seen it; queued = will be sent to the Caelo team; sent = the Caelo team received it; failed = sending failed, stored here only. Never tell the operator the Caelo team knows about a report unless its forwarding is sent.",
|
|
7586
7672
|
"inputSchema": {
|
|
7587
7673
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
7588
7674
|
"type": "object",
|