@caelo-cms/mcp-server 0.11.3 → 0.11.4

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 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.11.3",
4
+ "version": "0.11.4",
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.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.11.3",
2
+ "version": "0.11.4",
3
3
  "tools": [
4
4
  {
5
5
  "name": "read_content",
@@ -229,7 +229,7 @@
229
229
  },
230
230
  {
231
231
  "name": "build_page",
232
- "description": "Build a WHOLE page in ONE transaction: create (`page:{slug, title}`) or target existing (`page:{pageId}`) + the full ordered module list (each entry places one module into a template block, top to bottom; on an existing page entries append). Any invalid entry aborts everything (error names `modules[i]` + failing field); nothing is written.\nUSE FOR: every page creation (THE page-creation tool); any page needing >1 module — never `add_module` / `create_content_instances` chains. `modules: []` = intentionally empty page shell.\nDO NOT USE FOR: one incremental module on an existing page -> `add_module` (target `page`); site chrome header/footer -> `add_module` (target `layout`; chrome never rides in a page body).\nCORE RULES:\n- Creating: typed page -> `page.pageType` (+ `properties`/`terms`; an ordinary page omits pageType); always `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 what still blocks a Stage.\n- Per module, two modes: `moduleId` places an EXISTING module (`list_modules`; reuse first — shared modules keep pages consistent), or mint with `displayName` + `html` + `description` + `kind` + explicit `fields[]` (all required).\n- Pair `{{field_name}}` placeholders in `html` with `fields[]` in semantic snake_case (`hero_title`, `primary_cta_href`); never heuristic names (`cta2label2`) or numbered scalars (`label1`..`label10` -> ONE `list` field or a `module-list`).\n- Nested modules in the SAME call: inner entry gets `ref` and NO `blockName` (detached: minted + content-bound, not placed); the outer module's `module`/`module-list` field value references it as `{\"$ref\":\"<ref>\"}`, any array order. Never an `add_module` -> `remove_module_from` -> `create_content_instances` chain. E.g. `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'}}}}]`\n- Zero templates on the site -> bootstrap first (`create_layout` -> `create_template`).\n- INTERNAL LINKS (`html` hrefs, `url`/`link` fields, list-entry links): write the page path (e.g. `/about`). Target MUST be an existing page (`list_pages`) or it is refused — create an empty shell first (`build_page({page:{slug, title}, modules:[]})`) or link after. `/` and `/#section` are always valid. Exception: links to pages of this chat's site-import run are kept and resolved by `resolve_import_links` at the end. Media/files: media URL (`find_media`). External: full `https://` URL.\nTypical 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'}}}, …]}`",
232
+ "description": "Build a WHOLE page in ONE transaction: create (`page:{slug, title}`) or target existing (`page:{pageId}`) + the full ordered module list (each entry places one module into a template block, top to bottom; on an existing page entries append). Any invalid entry aborts everything (error names `modules[i]` + failing field); nothing is written.\nUSE FOR: every page creation (THE page-creation tool); any page needing >1 module — never `add_module` / `create_content_instances` chains. `modules: []` = intentionally empty page shell.\nDO NOT USE FOR: one incremental module on an existing page -> `add_module` (target `page`); site chrome header/footer -> `add_module` (target `layout`; chrome never rides in a page body).\nCORE RULES:\n- Creating: typed page -> `page.pageType` (+ `properties`/`terms`; an ordinary page omits pageType); always `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 what still blocks a Stage.\n- Per module, two modes: `moduleId` places an EXISTING module (`list_modules`; reuse first — shared modules keep pages consistent), or mint with `displayName` + `html` + `description` + `kind` + explicit `fields[]` (all required).\n- Pair `{{field_name}}` placeholders in `html` with `fields[]` in semantic snake_case (`hero_title`, `primary_cta_href`); never heuristic names (`cta2label2`) or numbered scalars (`label1`..`label10` -> ONE `list` field or a `module-list`).\n- An overview of pages (guides hub, category page) is ONE `list` field bound to a page query (`bind:\"source\"`, `source:{kind:\"query\", defaults:{page_type:…}}`; `term_pages` / `children`), never a hand-kept card per page.\n- Nested modules in the SAME call: inner entry gets `ref` and NO `blockName` (detached: minted + content-bound, not placed); the outer module's `module`/`module-list` field value references it as `{\"$ref\":\"<ref>\"}`, any array order. Never an `add_module` -> `remove_module_from` -> `create_content_instances` chain. E.g. `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'}}}}]`\n- Zero templates on the site -> bootstrap first (`create_layout` -> `create_template`).\n- INTERNAL LINKS (`html` hrefs, `url`/`link` fields, list-entry links): write the page path (e.g. `/about`). Target MUST be an existing page (`list_pages`) or it is refused — create an empty shell first (`build_page({page:{slug, title}, modules:[]})`) or link after. `/` and `/#section` are always valid. Exception: links to pages of this chat's site-import run are kept and resolved by `resolve_import_links` at the end. Media/files: media URL (`find_media`). External: full `https://` URL.\nTypical 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'}}}, …]}`",
233
233
  "inputSchema": {
234
234
  "type": "object",
235
235
  "additionalProperties": false,
@@ -528,7 +528,28 @@
528
528
  },
529
529
  "source": {
530
530
  "type": "object",
531
- "description": "bind \"source\": {kind:\"query\", editable, defaults} | {kind:\"system\", name: breadcrumbs|children|siblings|page_terms|term_pages} | {kind:\"plugin\", name} (a plugin list iterated by name needs no field)."
531
+ "description": "bind \"source\": {kind:\"query\", editable, defaults} | {kind:\"system\", name: breadcrumbs|children|siblings|page_terms|term_pages|pagination} | {kind:\"plugin\", name} (a plugin list iterated by name needs no field)."
532
+ },
533
+ "paginate": {
534
+ "type": "object",
535
+ "additionalProperties": false,
536
+ "description": "Overview of ALL matching pages (query without `limit`, or term_pages): page 1 here, then <path>/page/2/…; one per module. Needs a pager field (source system \"pagination\": {{number}}, {{link}}, is_current/is_prev/is_next). A placement may override with value `paginate`.",
537
+ "properties": {
538
+ "per_page": {
539
+ "type": "integer",
540
+ "minimum": 1,
541
+ "maximum": 50,
542
+ "description": "Default 50."
543
+ },
544
+ "follow_ups": {
545
+ "type": "string",
546
+ "enum": [
547
+ "index",
548
+ "noindex"
549
+ ],
550
+ "description": "Default \"index\" (self-canonical, in sitemap)."
551
+ }
552
+ }
532
553
  },
533
554
  "allowedModuleTypes": {
534
555
  "type": "array",
@@ -715,7 +736,7 @@
715
736
  },
716
737
  {
717
738
  "name": "list_content_instances",
718
- "description": "List content_instances — values filling module placeholders (TOON rows: id, module, kind, slug, displayName, purpose, placements, parents), most-placed first; counts as this chat's draft sees them.\n- `purpose` = why a shared row exists: read it BEFORE minting (`create_content_instances`); reuse a row whose purpose + module fit.\n- `placements` = blast radius: >1 = SHARED, edits propagate to every bound page -> check BEFORE `set_content_instance_values_many`.\n- `parents` = instances showing this one in a `module`/`module-list` field. ORPHAN (safe to delete) = placements 0 AND parents 0.\nNarrow by `moduleId` / `slug` / `pageId` (instances used on that page).",
739
+ "description": "List content_instances — values filling module placeholders (TOON rows: id, module, kind, slug, displayName, purpose, placements, parents, pages), widest reach first; counts as this chat's draft sees them.\n- `purpose` = why a shared row exists: read it BEFORE minting (`create_content_instances`); reuse a row whose purpose + module fit.\n- `placements` = its own placements; `parents` = instances, module defaults (layout chrome) and shared lists that show it inside them.\n- `pages` = blast radius: every page it renders on, its own placements plus everything its parents render on (`+site` = also layout chrome or a shared list) -> check BEFORE `set_content_instance_values_many`; `get_content_instance` names them.\n- ORPHAN (safe to delete) = placements 0 AND parents 0.\nNarrow by `moduleId` / `slug` / `pageId` (instances used on that page).",
719
740
  "inputSchema": {
720
741
  "type": "object",
721
742
  "properties": {
@@ -757,7 +778,7 @@
757
778
  },
758
779
  {
759
780
  "name": "get_content_instance",
760
- "description": "Fetch one content_instance, as this chat's draft sees it: values, placements (page path, block, position, sync_mode) and parent instances showing it in a `module`/`module-list` field. Check blast radius before `set_content_instance_values_many` and before deleting (orphan = no placements AND no parents).",
781
+ "description": "Fetch one content_instance, as this chat's draft sees it: values, placements (page path, block, position, sync_mode), parents showing it (instances' `module`/`module-list` fields, module defaults, shared lists) and every page it renders on. Check that blast radius before `set_content_instance_values_many` and before deleting (orphan = no placements AND no parents).",
761
782
  "inputSchema": {
762
783
  "type": "object",
763
784
  "properties": {
@@ -774,7 +795,7 @@
774
795
  },
775
796
  {
776
797
  "name": "set_content_instance_values_many",
777
- "description": "Edit values of 1..N shared content_instances in one transaction. Each item propagates to EVERY placement bound to that instance with sync_mode `synced` (blast radius = its placement count).\nUSE FOR: deliberate shared-content changes — check `purpose`, sample pages, placement count first (`list_content_instances` / `get_content_instance`).\nDO NOT USE FOR: an edit meant for ONE page -> `set_page_module_content_many` (unsynced placement) or `fork_placement_content` first (synced); module structure -> `update_modules_many`.\nCORE RULES:\n- All-or-nothing: an invalid item aborts the batch (error names `items[i]`); nothing is written.\n- Optional metadata in the same write: `slug`, `displayName` (`null` clears), `purpose`.\n- The result names per item where the edit now shows — relay it when the reach is wider than the operator asked for.\n- INTERNAL LINKS (`html` hrefs, `url`/`link` fields, list-entry links): write the page path (e.g. `/about`). Target MUST be an existing page (`list_pages`) or it is refused — create an empty shell first (`build_page({page:{slug, title}, modules:[]})`) or link after. `/` and `/#section` are always valid. Exception: links to pages of this chat's site-import run are kept and resolved by `resolve_import_links` at the end. Media/files: media URL (`find_media`). External: full `https://` URL.",
798
+ "description": "Edit values of 1..N shared content_instances in one transaction. Each item propagates to EVERY page it renders on: its `synced` placements and every page of the parents that show it (blast radius = `pages` in `list_content_instances`).\nUSE FOR: deliberate shared-content changes — check `purpose`, pages and parents first (`list_content_instances` / `get_content_instance`).\nDO NOT USE FOR: an edit meant for ONE page -> `set_page_module_content_many` (unsynced placement) or `fork_placement_content` first (synced); module structure -> `update_modules_many`.\nCORE RULES:\n- All-or-nothing: an invalid item aborts the batch (error names `items[i]`); nothing is written.\n- Optional metadata in the same write: `slug`, `displayName` (`null` clears), `purpose`.\n- The result names per item where the edit now shows — relay it when the reach is wider than the operator asked for.\n- INTERNAL LINKS (`html` hrefs, `url`/`link` fields, list-entry links): write the page path (e.g. `/about`). Target MUST be an existing page (`list_pages`) or it is refused — create an empty shell first (`build_page({page:{slug, title}, modules:[]})`) or link after. `/` and `/#section` are always valid. Exception: links to pages of this chat's site-import run are kept and resolved by `resolve_import_links` at the end. Media/files: media URL (`find_media`). External: full `https://` URL.",
778
799
  "inputSchema": {
779
800
  "type": "object",
780
801
  "additionalProperties": false,
@@ -836,7 +857,7 @@
836
857
  },
837
858
  {
838
859
  "name": "delete_content_instances",
839
- "description": "Soft-delete 1..N ORPHAN content_instances in one transaction. Orphan = no placements AND no parent instance shows it in a `module`/`module-list` field (`list_content_instances`: placements=0, parents=0).\nCORE RULES:\n- ANY refused item rolls back the WHOLE batch; the refusal names the blockers:\n - placements (page path, pageId, blockName, position) -> detach each via `fork_placement_content` with those values (page keeps a private copy), then retry.\n - parents -> first remove the ref from each parent's field via `set_content_instance_values_many`.\n- In a chat this lands on the draft; live changes when staged.",
860
+ "description": "Soft-delete 1..N ORPHAN content_instances in one transaction. Orphan = no placements AND no parent shows it — no instance's `module`/`module-list` field, module default or shared list (`list_content_instances`: placements=0, parents=0).\nCORE RULES:\n- ANY refused item rolls back the WHOLE batch; the refusal names the blockers:\n - placements (page path, pageId, blockName, position) -> detach each via `fork_placement_content` with those values (page keeps a private copy), then retry.\n - parents -> detach first: an instance parent via `set_content_instance_values_many` (or delete the parent EARLIER in the same call), a module default via `update_modules_many`, a shared list entry via `patch_list_items_many`.\n- In a chat this lands on the draft; live changes when staged.",
840
861
  "inputSchema": {
841
862
  "type": "object",
842
863
  "additionalProperties": false,
@@ -1116,7 +1137,7 @@
1116
1137
  },
1117
1138
  {
1118
1139
  "name": "list_modules",
1119
- "description": "List the module catalog (metadata, no HTML/CSS/JS): id, slug, type, kind, description, fields, placement usage.\nUSE FOR: a reuse candidate before placing/creating (prefer `add_module` with `moduleId` over a near-duplicate); the accepted child types before filling a `module`/`module-list` field.\nFields column: `blocks:module-list allowed=faq/cta` -> child `type` must be one of them (`allowed=any` = unrestricted). `nav:list(menu) shared=main-nav` = shared list (edit entries via `patch_list_items_many`) | `items:list(texts) local` = entries per placement | `latest:list(links) source=query(…)`.",
1140
+ "description": "List the module catalog (metadata, no HTML/CSS/JS): id, slug, type, kind, description, fields, placement usage.\nUSE FOR: a reuse candidate before placing/creating (prefer `add_module` with `moduleId` over a near-duplicate); the accepted child types before filling a `module`/`module-list` field.\nFields column: `blocks:module-list allowed=faq/cta` -> child `type` must be one of them (`allowed=any` = unrestricted). `nav:list(menu) shared=main-nav` = shared list (edit entries via `patch_list_items_many`) | `items:list(texts) local` = entries per placement | `latest:list(links) source=query(…)` | `… paginate(per_page=50 follow_ups=index)` = overview of every match over follow-up pages.",
1120
1141
  "inputSchema": {
1121
1142
  "type": "object",
1122
1143
  "properties": {
@@ -2387,7 +2408,28 @@
2387
2408
  },
2388
2409
  "source": {
2389
2410
  "type": "object",
2390
- "description": "bind \"source\": {kind:\"query\", editable, defaults} | {kind:\"system\", name: breadcrumbs|children|siblings|page_terms|term_pages} | {kind:\"plugin\", name} (a plugin list iterated by name needs no field)."
2411
+ "description": "bind \"source\": {kind:\"query\", editable, defaults} | {kind:\"system\", name: breadcrumbs|children|siblings|page_terms|term_pages|pagination} | {kind:\"plugin\", name} (a plugin list iterated by name needs no field)."
2412
+ },
2413
+ "paginate": {
2414
+ "type": "object",
2415
+ "additionalProperties": false,
2416
+ "description": "Overview of ALL matching pages (query without `limit`, or term_pages): page 1 here, then <path>/page/2/…; one per module. Needs a pager field (source system \"pagination\": {{number}}, {{link}}, is_current/is_prev/is_next). A placement may override with value `paginate`.",
2417
+ "properties": {
2418
+ "per_page": {
2419
+ "type": "integer",
2420
+ "minimum": 1,
2421
+ "maximum": 50,
2422
+ "description": "Default 50."
2423
+ },
2424
+ "follow_ups": {
2425
+ "type": "string",
2426
+ "enum": [
2427
+ "index",
2428
+ "noindex"
2429
+ ],
2430
+ "description": "Default \"index\" (self-canonical, in sitemap)."
2431
+ }
2432
+ }
2391
2433
  },
2392
2434
  "allowedModuleTypes": {
2393
2435
  "type": "array",
@@ -4061,6 +4103,10 @@
4061
4103
  "minLength": 1,
4062
4104
  "maxLength": 300,
4063
4105
  "description": "CSS selector: capture only the first match (e.g. '[data-caelo-module-id=\"<uuid>\"]', 'footer.caelo-layout-footer') — cheaper when checking one module. Error if nothing matches."
4106
+ },
4107
+ "fullPage": {
4108
+ "type": "boolean",
4109
+ "description": "Server render only: true = the whole page; default = the visible viewport. Prefer `selector` for one section below the fold."
4064
4110
  }
4065
4111
  }
4066
4112
  }
@@ -4577,7 +4623,28 @@
4577
4623
  },
4578
4624
  "source": {
4579
4625
  "type": "object",
4580
- "description": "bind \"source\": {kind:\"query\", editable, defaults} | {kind:\"system\", name: breadcrumbs|children|siblings|page_terms|term_pages} | {kind:\"plugin\", name} (a plugin list iterated by name needs no field)."
4626
+ "description": "bind \"source\": {kind:\"query\", editable, defaults} | {kind:\"system\", name: breadcrumbs|children|siblings|page_terms|term_pages|pagination} | {kind:\"plugin\", name} (a plugin list iterated by name needs no field)."
4627
+ },
4628
+ "paginate": {
4629
+ "type": "object",
4630
+ "additionalProperties": false,
4631
+ "description": "Overview of ALL matching pages (query without `limit`, or term_pages): page 1 here, then <path>/page/2/…; one per module. Needs a pager field (source system \"pagination\": {{number}}, {{link}}, is_current/is_prev/is_next). A placement may override with value `paginate`.",
4632
+ "properties": {
4633
+ "per_page": {
4634
+ "type": "integer",
4635
+ "minimum": 1,
4636
+ "maximum": 50,
4637
+ "description": "Default 50."
4638
+ },
4639
+ "follow_ups": {
4640
+ "type": "string",
4641
+ "enum": [
4642
+ "index",
4643
+ "noindex"
4644
+ ],
4645
+ "description": "Default \"index\" (self-canonical, in sitemap)."
4646
+ }
4647
+ }
4581
4648
  },
4582
4649
  "allowedModuleTypes": {
4583
4650
  "type": "array",