@caelo-cms/mcp-server 0.10.71 → 0.10.72
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 +25 -16
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.72",
|
|
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.72",
|
|
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.**
|
|
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.** `list_modules` 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 `list_modules` catalog. 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); the tool result flags unscoped selectors. **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). **Accordion, tabs, carousel need NO module JS** - mark the markup with `data-caelo-ui=\"accordion|tabs|carousel\"` and Caelo ships vetted, accessible behaviour (markup contract in 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 (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. 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,
|
|
@@ -323,7 +323,7 @@
|
|
|
323
323
|
},
|
|
324
324
|
{
|
|
325
325
|
"name": "set_page_module_content",
|
|
326
|
-
"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 —
|
|
326
|
+
"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 — `list_modules` shows each module's `type` and each nested field's `allowedModuleTypes`). 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 (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. 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.",
|
|
327
327
|
"inputSchema": {
|
|
328
328
|
"type": "object",
|
|
329
329
|
"additionalProperties": false,
|
|
@@ -426,7 +426,7 @@
|
|
|
426
426
|
},
|
|
427
427
|
{
|
|
428
428
|
"name": "build_page",
|
|
429
|
-
"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 (on an existing page entries append; an entry's optional `position` — 'top' or an index, as in add_module — inserts it instead). 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: when creating, pass `page.metaDescription` (and `status:'published'` for a page that belongs on the site) — a Stage refuses published pages without a description, and builds only published ones; the result says when something still blocks a Stage. `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
|
|
429
|
+
"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 (on an existing page entries append; an entry's optional `position` — 'top' or an index, as in add_module — inserts it instead). 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: when creating, pass `page.metaDescription` (and `status:'published'` for a page that belongs on the site) — a Stage refuses published pages without a description, and builds only published ones; the result says when something still blocks a Stage. `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 `list_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', status:'published', metaDescription:'…'}, 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}, in any array order. 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 (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. 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.",
|
|
430
430
|
"inputSchema": {
|
|
431
431
|
"type": "object",
|
|
432
432
|
"additionalProperties": false,
|
|
@@ -527,7 +527,7 @@
|
|
|
527
527
|
"description": "Local handle: another entry's module/module-list field value (or moduleId) {\"$ref\": \"<ref>\"} resolves to this entry's {moduleId, contentInstanceId}. Array order does not matter — referenced entries are created first automatically."
|
|
528
528
|
},
|
|
529
529
|
"moduleId": {
|
|
530
|
-
"description": "EXISTING module UUID from
|
|
530
|
+
"description": "EXISTING module UUID from list_modules — or {\"$ref\": \"<handle>\"} to RE-PLACE a module minted by another entry of THIS call (e.g. one card module, three placements; each entry still gets its own content).",
|
|
531
531
|
"oneOf": [
|
|
532
532
|
{
|
|
533
533
|
"type": "string",
|
|
@@ -1000,7 +1000,7 @@
|
|
|
1000
1000
|
},
|
|
1001
1001
|
{
|
|
1002
1002
|
"name": "set_content_instance_values",
|
|
1003
|
-
"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
|
|
1003
|
+
"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` and each nested field's `allowedModuleTypes` in `list_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 (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. 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.",
|
|
1004
1004
|
"inputSchema": {
|
|
1005
1005
|
"type": "object",
|
|
1006
1006
|
"properties": {
|
|
@@ -1442,7 +1442,7 @@
|
|
|
1442
1442
|
},
|
|
1443
1443
|
{
|
|
1444
1444
|
"name": "list_modules",
|
|
1445
|
-
"description": "List the FULL module catalog with decision-support metadata: id, slug, type, kind, description,
|
|
1445
|
+
"description": "List the FULL module catalog with decision-support metadata: id, slug, type, kind, description, fields, and placement usage. Call it before placing or nesting an existing module: to find a reuse candidate, to find modules by `kind`/`filter` (plus `limit`/`offset`/`full` as in every list tool), and before filling a `module`/`module-list` field — the fields column shows each nested field's accepted types (`blocks:module-list allowed=faq/cta`; `allowed=any` = no restriction), and a child's `type` must be one of them. Returns metadata only (no HTML/CSS/JS bodies). Prefer placing an existing module (`add_module` with `moduleId`) over minting a near-duplicate.",
|
|
1446
1446
|
"inputSchema": {
|
|
1447
1447
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
1448
1448
|
"type": "object",
|
|
@@ -2520,7 +2520,7 @@
|
|
|
2520
2520
|
},
|
|
2521
2521
|
{
|
|
2522
2522
|
"name": "add_module",
|
|
2523
|
-
"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
|
|
2523
|
+
"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 `list_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 (`build_page({page:{slug, title}, modules: []})` makes an empty page shell in one call), or add the link after. The site root `/` (also `/#section`) is always valid, even before the homepage is built. 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).",
|
|
2524
2524
|
"inputSchema": {
|
|
2525
2525
|
"type": "object",
|
|
2526
2526
|
"additionalProperties": false,
|
|
@@ -4094,7 +4094,7 @@
|
|
|
4094
4094
|
},
|
|
4095
4095
|
{
|
|
4096
4096
|
"name": "autofill_page_seo",
|
|
4097
|
-
"description": "Write a page's meta description (and optional og:image) for the FIRST time. Every new page needs one before it is Staged — a Stage refuses pages without one. Write it from the page's actual content (or carry over the source page's description when migrating a site). Prefer `autofill_page_seo_many` when more than one page needs it. For a page you create with build_page, pass `page.metaDescription` there instead — same fill-once write, no extra call. Fill-once: sending the SAME text again is a harmless no-op; a DIFFERENT text for an already-filled page returns AlreadyAutofilled with the stored description (the page is not missing one) — change it on purpose only via `optimize_page_seo`, and only when the user asks. Aim for 50–60 char title (set via `update_pages_many`) and 150–160 char meta description.",
|
|
4097
|
+
"description": "Write a page's meta description (and optional og:image) for the FIRST time. Every new page needs one before it is Staged — a Stage refuses pages without one. Write it from the page's actual content — `get_page_seo` returns the page's core text (or carry over the source page's description when migrating a site). Prefer `autofill_page_seo_many` when more than one page needs it. For a page you create with build_page, pass `page.metaDescription` there instead — same fill-once write, no extra call. Fill-once: sending the SAME text again is a harmless no-op; a DIFFERENT text for an already-filled page returns AlreadyAutofilled with the stored description (the page is not missing one) — change it on purpose only via `optimize_page_seo`, and only when the user asks. Aim for 50–60 char title (set via `update_pages_many`) and 150–160 char meta description.",
|
|
4098
4098
|
"inputSchema": {
|
|
4099
4099
|
"type": "object",
|
|
4100
4100
|
"additionalProperties": false,
|
|
@@ -4124,7 +4124,7 @@
|
|
|
4124
4124
|
},
|
|
4125
4125
|
{
|
|
4126
4126
|
"name": "autofill_page_seo_many",
|
|
4127
|
-
"description": "Write the FIRST meta description for SEVERAL pages in ONE transaction — prefer this over multiple `autofill_page_seo` calls (§11 bulk-first). Use it after building pages and before Staging them: a Stage refuses pages without a meta description and names them. Write each description from that page's actual content (150–160 characters, for a human reading a search result), or carry over the source page's description when migrating a site. All-or-nothing: a page already autofilled with a DIFFERENT text (AlreadyAutofilled) aborts the whole batch — leave such pages out (the same text again is a no-op); their descriptions change through `optimize_page_seo` / `bulk_optimize_seo` (re-optimization) or `set_page_seo_many` (an exact value the user dictated). Each item is the `autofill_page_seo` shape: `{pageId, metaDescription, ogImageAssetId?}`.",
|
|
4127
|
+
"description": "Write the FIRST meta description for SEVERAL pages in ONE transaction — prefer this over multiple `autofill_page_seo` calls (§11 bulk-first). Use it after building pages and before Staging them: a Stage refuses pages without a meta description and names them. Write each description from that page's actual content (150–160 characters, for a human reading a search result) — read the core text of all of them with ONE `get_page_seo({pageIds})` call, never by rendering pages — or carry over the source page's description when migrating a site. All-or-nothing: a page already autofilled with a DIFFERENT text (AlreadyAutofilled) aborts the whole batch — leave such pages out (the same text again is a no-op); their descriptions change through `optimize_page_seo` / `bulk_optimize_seo` (re-optimization) or `set_page_seo_many` (an exact value the user dictated). Each item is the `autofill_page_seo` shape: `{pageId, metaDescription, ogImageAssetId?}`.",
|
|
4128
4128
|
"inputSchema": {
|
|
4129
4129
|
"type": "object",
|
|
4130
4130
|
"additionalProperties": false,
|
|
@@ -4166,7 +4166,7 @@
|
|
|
4166
4166
|
},
|
|
4167
4167
|
{
|
|
4168
4168
|
"name": "optimize_page_seo",
|
|
4169
|
-
"description": "Explicit re-optimization of per-page meta description (and optional og:image) for ONE page. Use ONLY when the user explicitly asks to optimize / re-write SEO and provides context (keyword research, intent shift, branding update). Always allowed regardless of fill state. For 2+ pages prefer `bulk_optimize_seo` — one tool call carrying all updates instead of N round-trips. Don't use for first-publish auto-fill — that's `autofill_page_seo`.",
|
|
4169
|
+
"description": "Explicit re-optimization of per-page meta description (and optional og:image) for ONE page. Use ONLY when the user explicitly asks to optimize / re-write SEO and provides context (keyword research, intent shift, branding update). Always allowed regardless of fill state. Read the page's current description and core text with `get_page_seo` first. For 2+ pages prefer `bulk_optimize_seo` — one tool call carrying all updates instead of N round-trips. Don't use for first-publish auto-fill — that's `autofill_page_seo`.",
|
|
4170
4170
|
"inputSchema": {
|
|
4171
4171
|
"type": "object",
|
|
4172
4172
|
"additionalProperties": false,
|
|
@@ -4838,7 +4838,7 @@
|
|
|
4838
4838
|
},
|
|
4839
4839
|
{
|
|
4840
4840
|
"name": "bulk_optimize_seo",
|
|
4841
|
-
"description": "Re-optimize SEO across up to 200 pages in one transaction with shared context (keyword research, intent shift, branding update). Use this — NOT multiple `optimize_page_seo` calls — when the user asks to optimize 2+ pages. Each update is { pageId, metaDescription, ogImageAssetId? }. All-or-nothing: a single failed update rolls back the whole batch. For a single page, use `optimize_page_seo`.",
|
|
4841
|
+
"description": "Re-optimize SEO across up to 200 pages in one transaction with shared context (keyword research, intent shift, branding update). Use this — NOT multiple `optimize_page_seo` calls — when the user asks to optimize 2+ pages. Each update is { pageId, metaDescription, ogImageAssetId? }. Read the pages' current descriptions and core text with ONE `get_page_seo({pageIds})` call first. All-or-nothing: a single failed update rolls back the whole batch. For a single page, use `optimize_page_seo`.",
|
|
4842
4842
|
"inputSchema": {
|
|
4843
4843
|
"type": "object",
|
|
4844
4844
|
"additionalProperties": false,
|
|
@@ -8044,20 +8044,29 @@
|
|
|
8044
8044
|
},
|
|
8045
8045
|
{
|
|
8046
8046
|
"name": "get_page_seo",
|
|
8047
|
-
"description": "Read
|
|
8047
|
+
"description": "Read pages' stored SEO fields (meta description, OG image, canonical URL, noindex, sitemap changefreq/priority, when autofilled / optimized) AND each page's core text: the plain text of the page's own content blocks (headings, paragraphs, list items, link labels; nested cards included; header/footer chrome left out), from THIS chat's draft, capped at ~1500 characters. Use it to read what a page already has AND its core text before writing or optimizing its description — one call for every page (`pageIds`, up to 50), then ONE autofill_page_seo_many (first description) or bulk_optimize_seo / set_page_seo_many. Don't render the page for this (inspect_page_render / screenshot_page are for layout and visual questions). A page without an SEO row shows that autofill_page_seo_many fills it.",
|
|
8048
8048
|
"inputSchema": {
|
|
8049
8049
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
8050
8050
|
"type": "object",
|
|
8051
8051
|
"properties": {
|
|
8052
8052
|
"pageId": {
|
|
8053
|
+
"description": "One page. Pass this OR pageIds.",
|
|
8053
8054
|
"type": "string",
|
|
8054
8055
|
"format": "uuid",
|
|
8055
8056
|
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
8057
|
+
},
|
|
8058
|
+
"pageIds": {
|
|
8059
|
+
"description": "Several pages (up to 50) in one call. Pass this OR pageId.",
|
|
8060
|
+
"minItems": 1,
|
|
8061
|
+
"maxItems": 50,
|
|
8062
|
+
"type": "array",
|
|
8063
|
+
"items": {
|
|
8064
|
+
"type": "string",
|
|
8065
|
+
"format": "uuid",
|
|
8066
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
8067
|
+
}
|
|
8056
8068
|
}
|
|
8057
8069
|
},
|
|
8058
|
-
"required": [
|
|
8059
|
-
"pageId"
|
|
8060
|
-
],
|
|
8061
8070
|
"additionalProperties": false
|
|
8062
8071
|
}
|
|
8063
8072
|
},
|