@wootsup/yt-builder-mcp 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,15 +12,15 @@ description: Drive the YOOtheme Pro Page Builder on WordPress or Joomla 5/6. Dis
12
12
  > WordPress and Joomla 5/6.
13
13
 
14
14
  This skill helps AI assistants drive the YOOtheme Pro Page Builder through the
15
- `@wootsup/yt-builder-mcp` server. The server catalogues 47 typed, scoped,
16
- idempotent tools (49 callable end-to-end with the two gateways). 41 of them
15
+ `@wootsup/yt-builder-mcp` server. The server catalogues 48 typed, scoped,
16
+ idempotent tools (50 callable end-to-end with the two gateways). 41 of them
17
17
  advertised as first-class entries in `tools/list`
18
- (37 essential L1 forwards + 2 direct L3 entries + 2 gateways), and 8 advanced
18
+ (37 essential L1 forwards + 2 direct L3 entries + 2 gateways), and 9 advanced
19
19
  tools reachable through the gateways. The write-capable
20
- `yootheme_builder_advanced` gateway routes all 8; the read-only
20
+ `yootheme_builder_advanced` gateway routes all 9; the read-only
21
21
  `yootheme_builder_advanced_read` gateway routes only the read-only subset
22
22
  (page_get_schema, inspect_sublayout, layout_query, local_content_types,
23
- local_content_read) and is itself
23
+ local_content_read, pages_audit) and is itself
24
24
  read-only-annotated, so MCP hosts can auto-allow pure inspection without a
25
25
  per-call write-approval prompt. This 3-lane split keeps `tools/list` well below
26
26
  the ~40-tool Cursor cap while every catalogued tool stays fully reachable.
@@ -55,7 +55,7 @@ entries when the user types "YT Builder MCP" into the picker:
55
55
 
56
56
  - **`YT Builder MCP for YOOtheme Pro (unofficial)`**: the MCP **server**.
57
57
  Provides the 41 first-class tools (37 essential, 2 direct, 2 gateways).
58
- The `yootheme_builder_advanced` gateway routes all 8 advanced tools; the
58
+ The `yootheme_builder_advanced` gateway routes all 9 advanced tools; the
59
59
  read-only `yootheme_builder_advanced_read` gateway routes the read-only
60
60
  subset without a write-approval prompt.
61
61
  - **`Von YT Builder MCP for YOOtheme Pro`**: the bundled **skill** (this
@@ -125,10 +125,11 @@ The server exposes:
125
125
  step that turns a placeholder-filled skeleton into a finished-looking
126
126
  page (see "Fill image slots with real on-topic photos" under Layout
127
127
  Library).
128
- - **8 advanced captured tools**: everything else (page_get_schema,
128
+ - **9 advanced captured tools**: everything else (page_get_schema,
129
129
  element_unbind_source, clean_implode_directives,
130
- `inspect_sublayout`, `layout_query`, `library_save_as`, and the two Local
131
- Content Read tools `local_content_types` / `local_content_read`).
130
+ `inspect_sublayout`, `layout_query`, `library_save_as`, the two Local
131
+ Content Read tools `local_content_types` / `local_content_read`, and
132
+ `pages_audit`).
132
133
  Reachable through one gateway tool:
133
134
  `yootheme_builder_advanced({ tool: "<name>", arguments: { ... } })`. For
134
135
  example, to inspect a panel/grid_item `< SUBLAYOUT`:
@@ -143,6 +144,12 @@ The server exposes:
143
144
  It flags `invalid_source_possible: true` when the saved subtree carries
144
145
  bound dynamic sources that may not exist wherever it is later reloaded —
145
146
  re-bind after inserting it elsewhere.
147
+ And to run a one-call site-wide SEO/structure/content/links audit
148
+ (read-only, severity-sorted findings + machine-executable fix_hints),
149
+ prefer the read gateway:
150
+ `yootheme_builder_advanced_read({ tool: "yootheme_builder_pages_audit", arguments: { checks: ["seo", "structure"] } })`
151
+ (the write gateway routes it too, if you prefer one entry point:
152
+ `yootheme_builder_advanced({ tool: "yootheme_builder_pages_audit" })`).
146
153
 
147
154
  The core write/refactor + render-verify tools the Glöckle HANDOFF-4/5 ask for
148
155
  are now **L1 — call them directly by name**, not through a gateway:
@@ -184,7 +191,8 @@ The server exposes:
184
191
  - **2 gateway tools**: `yootheme_builder_advanced` (write-capable; can route
185
192
  into delete/unbind, so hosts gate it) and `yootheme_builder_advanced_read`
186
193
  (read-only; routes ONLY the read-only advanced subset — page_get_schema,
187
- inspect_sublayout, layout_query, local_content_types, local_content_read
194
+ inspect_sublayout, layout_query, local_content_types, local_content_read,
195
+ pages_audit —
188
196
  and is read-only-annotated so hosts auto-allow it). Prefer the read gateway
189
197
  for pure inspection:
190
198
  `yootheme_builder_advanced_read({ tool: "yootheme_builder_page_get_schema", arguments: { template_id } })`.
@@ -196,8 +204,8 @@ The server exposes:
196
204
  `yootheme_builder_advanced({ tool: "yootheme_builder_local_content_read", arguments: { kind: "entities", type: "post" } })`.)
197
205
 
198
206
  `tools/list` therefore advertises 41 names (37 + 2 + 2). That's 37 L1
199
- essentials + 2 L3 direct + 2 gateways. The total callable surface is 49
200
- (41 advertised + 8 advanced reachable through the gateways). If the AI
207
+ essentials + 2 L3 direct + 2 gateways. The total callable surface is 50
208
+ (41 advertised + 9 advanced reachable through the gateways). If the AI
201
209
  client reports "tool not found", you are almost certainly calling an
202
210
  advanced tool by its raw name. Wrap it in
203
211
  `yootheme_builder_advanced({ tool, arguments })` (or the read gateway for
@@ -736,6 +744,12 @@ must have **exactly one** `<h1>`). Set it explicitly on every headline:
736
744
  for punch); a big stat number is `title_element: "div"` with
737
745
  `title_style: "heading-medium"`.
738
746
 
747
+ **This `h1` default is specific to `headline`.** Container elements
748
+ (`grid`, `panel`, `slideshow`, and the rest of the item-carrier family)
749
+ default to `h3`, not `h1`, when `title_element` is left unset. See
750
+ "Workflow 6 (SEO audit)" below for the full per-type default table
751
+ `pages_audit` uses to find missing/multiple h1 issues across a whole site.
752
+
739
753
  ### Headline `content` is plain inline text — never wrapped in `<p>`
740
754
 
741
755
  A `headline` renders its `content` **inside** the heading tag, so wrapping it
@@ -1238,7 +1252,161 @@ and the props payload you passed.
1238
1252
 
1239
1253
  ---
1240
1254
 
1241
- ## When something doesn't fit one of these 5 workflows
1255
+ ## Workflow 6: SEO audit (pages_audit)
1256
+
1257
+ **Goal:** Run one deterministic, site-wide sweep for structure, content,
1258
+ SEO, and link issues, drill into the pages a finding names, and fix them
1259
+ one page at a time through the existing write tools this server already
1260
+ exposes. Never analyze findings by hand and never bulk-apply a fix.
1261
+
1262
+ **Canonical tool-call sequence (real parameter names, snake_case):**
1263
+
1264
+ 1. `yootheme_builder_advanced_read({ tool: "yootheme_builder_pages_audit", arguments: { checks: ["seo", "structure"] } })`
1265
+ sweeps the whole site in one call. `pages_audit` is a read-only
1266
+ advanced tool, so reach it through the read gateway (the write
1267
+ gateway routes it too, but the read gateway never triggers a
1268
+ write-approval prompt). Omit `checks`/`types` to run every category
1269
+ against every scanned template.
1270
+ 2. Read the response's `findings` array. Each finding carries a `code`,
1271
+ `category`, `severity`, a `count`, up to 30 affected `pages`, and
1272
+ (where a safe automatic fix exists) a `fix_hint` naming the exact
1273
+ write tool and args template to run. `duplicate-layout` and
1274
+ `title-collision` never carry a `fix_hint` — they need a human
1275
+ decision (rename, merge, or intentionally keep as-is), not an
1276
+ automatic patch.
1277
+ 3. `yootheme_builder_template_summary({ template_id })` drills into one
1278
+ page a finding names. It carries the same `heading_levels`,
1279
+ `images_missing_alt`, and `disabled_elements` counters the audit
1280
+ used, so you can see exactly which nodes are affected before
1281
+ writing.
1282
+ 4. Fix that ONE page via the write tool named in `fix_hint.tool`, with
1283
+ `fix_hint.args_template` as a starting point (`element_update_settings`,
1284
+ `element_add`, etc), ETag-locked like every other write in this
1285
+ server.
1286
+ 5. `yootheme_builder_page_publish({ template_id, etag })` republishes.
1287
+ The response's `quick_check` block (default on) re-checks THIS page
1288
+ with the same per-page codes, so you can confirm the fix landed
1289
+ without a second full sweep.
1290
+
1291
+ **Common pitfalls:**
1292
+
1293
+ - **Promising a full SEO audit.** `pages_audit` covers Builder structure
1294
+ and CMS basics only: heading hierarchy, empty/thin pages, missing
1295
+ image alt text, duplicate layouts, title collisions, non-pretty URL
1296
+ fallbacks, stale content, and dead in-page anchors. Every response
1297
+ carries a fixed disclaimer field:
1298
+ `"Meta title/description/OG live in your CMS SEO plugin — not covered here."`
1299
+ It does not read or evaluate Yoast/RankMath/any SEO-plugin meta
1300
+ title, meta description, or Open Graph tags. Say this scope boundary
1301
+ out loud and point the user at their SEO plugin for that layer.
1302
+ - **Confusing the CONTENT count with `pages_list.elements_count`.**
1303
+ `pages_list.elements_count` counts every node, including
1304
+ `section`/`row`/`column`/`fragment` layout wrappers. `pages_audit`
1305
+ instead uses a narrower CONTENT count (`elements_content` in the
1306
+ evidence) that excludes `section`/`row`/`column`/`fragment`/`layout`
1307
+ wrapper nodes: an empty section nested in three columns is correctly
1308
+ `0` content elements, not "not empty" just because the wrappers
1309
+ exist. Both numbers ride in the evidence (`elements_total` and
1310
+ `elements_content`). `thin-content` fires when `elements_content` is
1311
+ below `thin_content_threshold` (default 8); `empty-page` fires at
1312
+ `elements_content === 0`.
1313
+ - **The h1-default trap.** Only `headline` defaults to `h1` when
1314
+ `title_element` is unset. A container element (`grid`, `panel`,
1315
+ `slideshow`, and the rest) left unset does NOT become an `<h1>` — it
1316
+ becomes an `<h3>` (or stays a non-heading `div` for
1317
+ `description_list`). See the per-type default table below and
1318
+ "`title_style` vs `title_element`" above (that section's "defaults
1319
+ to `h1`" rule is specific to `headline` only). `meta_element` is a
1320
+ SECOND, independent heading channel on the same container types,
1321
+ defaulting to `div` and counting only when explicitly set to
1322
+ `h1`-`h6` with `show_meta` not `false`.
1323
+ - **Treating a bound-but-empty slot as missing.** Bound title/meta
1324
+ slots count as filled even when the static prop text is empty — a
1325
+ title bound to a dynamic source field is not "missing" just because
1326
+ the builder-side prop is blank. The same exemption applies to
1327
+ `image_alt`/`background_image_alt`: `images-missing-alt` never flags
1328
+ a bound image-alt slot, only an unbound, empty, or whitespace-only
1329
+ alt on a static image.
1330
+ - **Flagging every `#`-prefixed value as a dead anchor.** A
1331
+ `dead-anchor` finding fires only for a genuinely dangling `#name`
1332
+ link whose target element `id` does not exist anywhere on the same
1333
+ page. It explicitly excludes a bare `#` (a placeholder, never a real
1334
+ target), `mailto:`/`tel:` links, `http(s)://` external links, and
1335
+ cross-page links like `?page_id=…#…` (that anchor lives on a
1336
+ different page's render and cannot be verified from this page's own
1337
+ `props.id` set).
1338
+ - **Treating `missing-h1` as critical, or double-flagging it.**
1339
+ Severity is calibrated from live empirical review, not per-rule
1340
+ guessing: `no-headings` (zero headings anywhere on the page) is
1341
+ critical, but `missing-h1` (headings exist, but none is an effective
1342
+ h1) is only a warning, and it is suppressed entirely when
1343
+ `no-headings` already fired for the same page. Real sites
1344
+ legitimately have h2/h3-only pages with no h1.
1345
+ - **Treating `quick_check` findings as a publish failure.** `quick_check`
1346
+ is advisory only: publish and save always succeed regardless of what
1347
+ it reports. This is not the blocking render-precheck (which still
1348
+ gates on genuinely broken output) — SEO/structure findings are
1349
+ hints, not corruption. `quick_check` only ever includes checks whose
1350
+ `scope` is `"per-page"`; `duplicate-layout` and `title-collision`
1351
+ need the whole site swept to mean anything, so they never appear
1352
+ here — instead the block carries a fixed note:
1353
+ `"run pages_audit for site-wide checks"`. `page_publish` attaches
1354
+ `quick_check` by default (set `quick_check: false` to opt out, e.g.
1355
+ scripted bulk publishes); `page_save` carries it only when you opt
1356
+ in with `quick_check: true`.
1357
+
1358
+ **Per-type `title_element` default table (the correctness crux of every
1359
+ heading finding):**
1360
+
1361
+ | Element type(s) | `title_element` default | Counts as heading? |
1362
+ |---|---|---|
1363
+ | `headline` | `h1` | Yes, unless explicitly overridden. |
1364
+ | `comments` (WP source) | `h2` | Yes, always active. |
1365
+ | `alert`, `gallery`, `grid`, `map`, `overlay`, `overlay-slider`, `panel`, `panel-slider`, `popover`, `slideshow`, `switcher` | `h3` | Yes, when `show_title` is not `false` and the title is non-empty or bound. |
1366
+ | `description_list` | `div` | No. |
1367
+ | `section` (Section Title) | (no field; hardcoded `<div class="tm-section-title">`) | No, ever (counted only as an informational `section_titles` tally). |
1368
+
1369
+ `div` never counts as a heading whether it came from an explicit value
1370
+ or a type default.
1371
+
1372
+ **Worked example (tool-call snippet):**
1373
+
1374
+ ```jsonc
1375
+ // Step 1. Sweep the site for SEO + structure issues.
1376
+ yootheme_builder_advanced_read({
1377
+ tool: "yootheme_builder_pages_audit",
1378
+ arguments: { checks: ["seo", "structure"] }
1379
+ })
1380
+ // A finding: { code: "missing-h1", category: "structure", severity: "warning",
1381
+ // count: 3, pages: [{ id: "page:42", label: "About" }, ...],
1382
+ // fix_hint: { tool: "element_update_settings",
1383
+ // args_template: { props: { title_element: "h1" } }, note: "..." } }
1384
+
1385
+ // Step 3. Drill into the flagged page.
1386
+ yootheme_builder_template_summary({ template_id: "page:42" })
1387
+ // heading_levels: { h1: 0, h2: 2, h3: 5, div: 1 }, images_missing_alt: 2
1388
+
1389
+ // Step 4. Fix that ONE page via the write tool the fix_hint named.
1390
+ yootheme_builder_element_update_settings({
1391
+ template_id: "page:42",
1392
+ element_path: "/children/0/children/0",
1393
+ props: { title_element: "h1" },
1394
+ etag: "abc123"
1395
+ })
1396
+
1397
+ // Step 5. Republish; quick_check re-confirms this page is clean.
1398
+ yootheme_builder_page_publish({ template_id: "page:42", etag: "def456" })
1399
+ // Response: { ..., quick_check: { clean: true } }
1400
+ ```
1401
+
1402
+ **Success criterion:** A re-run of `pages_audit` (or the `quick_check`
1403
+ block on the next publish) no longer lists the fixed finding for that
1404
+ page, and the response's `summary.critical`/`warning`/`info` totals go
1405
+ down.
1406
+
1407
+ ---
1408
+
1409
+ ## When something doesn't fit one of these 6 workflows
1242
1410
 
1243
1411
  - **Move an element** (intra-template reorder/reparent): use
1244
1412
  `yootheme_builder_element_move({ template_id, element_path,
@@ -2016,7 +2184,7 @@ perform.
2016
2184
 
2017
2185
  <!-- TOOL-CATALOG:BEGIN -->
2018
2186
 
2019
- **47 catalogued tools** plus the `yootheme_builder_advanced` (write) + `yootheme_builder_advanced_read` (read-only) gateways = **49 reachable via `tools/list`** (37 L1 + 2 L3 + 2 gateways = 41 advertised; the gateways route to 8 additional advanced tools, bringing the total to 49 callable). Generated by `scripts/extract-tools.mjs` from the compiled `buildAllTools()` registry. Do not hand-edit this section; re-run `npm run build && node scripts/extract-tools.mjs` after changing tool definitions.
2187
+ **48 catalogued tools** plus the `yootheme_builder_advanced` (write) + `yootheme_builder_advanced_read` (read-only) gateways = **50 reachable via `tools/list`** (37 L1 + 2 L3 + 2 gateways = 41 advertised; the gateways route to 9 additional advanced tools, bringing the total to 50 callable). Generated by `scripts/extract-tools.mjs` from the compiled `buildAllTools()` registry. Do not hand-edit this section; re-run `npm run build && node scripts/extract-tools.mjs` after changing tool definitions.
2020
2188
 
2021
2189
  | Tool | Kind | Input keys | Description |
2022
2190
  | --- | --- | --- | --- |
@@ -2051,8 +2219,9 @@ perform.
2051
2219
  | `yootheme_builder_page_dynamize` | mutating | `conditions`, `element_path`, `etag`, `leaf_map`, `list_source_name`, `page`, `remove_leaves`, `section_name`, `site_id`, `source_map`, `template_id` | One-call way to dynamize a designed section: copies the layout byte-identically and swaps only what you map. Use INSTEAD of hand-editing JSON. Give a REFERENCE (section_name from template_summary, or element_path) + list_source_name + a leaf_map (original leaf field-name → flat flow field; #parent + tuned props kept). A section with several source blocks (Played/Next/Later) dynamizes in ONE call via source_map ({match, source_name, leaf_map?}[]). Sub-query panels: key by "<subquery>.<leaf>". Returns page + `summary`. Requires page.title. Operates on the default site unless site_id is provided. |
2052
2220
  | `yootheme_builder_page_get_layout` | read+idempotent | `cursor`, `fields`, `flat`, `force`, `max_chars`, `mode`, `post_id`, `site_id`, `template_id`, `url` | Get the layout tree for one template — address it by `template_id` (from pages_list), `post_id` (numeric post/article id), or `url`. For dynamize/bind prefer `mode:"bindings_only"` (binding skeleton, no style props). Default `mode:"full"` returns nested `{layout, etag}`; `flat:true` gives a depth-first `{elements:[...]}` array (with `fields[]`). A repeated unchanged read auto-skips (`force:true` re-fetches). `max_chars` bounds the text; a clipped read returns `next_cursor` (pass back as `cursor`) to page the rest. Operates on the default site unless site_id is provided. |
2053
2221
  | `yootheme_builder_page_get_schema` | read+idempotent | `fields`, `max_chars`, `post_id`, `site_id`, `template_id`, `url` | Get the flat schema for a template — nodes with their JSON-Pointer paths and element types. Lighter than page_get_layout, enough to locate elements before editing. Address by `template_id` (pages_list), `post_id`, or `url`. `fields[]` slims each node; `max_chars` bounds output. Operates on the default site unless site_id is provided. |
2054
- | `yootheme_builder_page_publish` | idempotent | `etag`, `site_id`, `template_id` | Publish a template — persist state, flush YT + WP caches, snapshot the published-state ETag. ETag optional — when provided, 412 on conflict; when omitted, last-write-wins. Recommended for collaborative edits. Operates on the default site unless site_id is provided. |
2055
- | `yootheme_builder_page_save` | idempotent | `dry_run`, `etag`, `layout`, `site_id`, `template_id` | Re-run save-transforms + flush caches. Optionally REPLACE the template tree by passing the INNER {type:"layout",children:[...]} tree as `layout` — NOT the full template node page_get_layout returns (rejected 422, would blank the page). Validated + ETag-locked; on reject nothing persists. An unrenderable layout is refused PRE-WRITE (422). `dry_run:true` preflights; the success response carries `previous_layout` for 1-call rollback. Omit `layout` to re-run transforms only. ETag optional (412 on conflict; else last-write-wins). Operates on the default site unless site_id is provided. |
2222
+ | `yootheme_builder_page_publish` | idempotent | `etag`, `quick_check`, `site_id`, `template_id` | Publish a template — persist state, flush YT + WP caches, snapshot the published-state ETag. ETag optional — when provided, 412 on conflict; when omitted, last-write-wins. Recommended for collaborative edits. Operates on the default site unless site_id is provided. |
2223
+ | `yootheme_builder_page_save` | idempotent | `dry_run`, `etag`, `layout`, `quick_check`, `site_id`, `template_id` | Re-run save-transforms + flush caches. Optionally REPLACE the template tree by passing the INNER {type:"layout",children:[...]} tree as `layout` — NOT the full template node page_get_layout returns (rejected 422, would blank the page). Validated + ETag-locked; on reject nothing persists. An unrenderable layout is refused PRE-WRITE (422). `dry_run:true` preflights; the success response carries `previous_layout` for 1-call rollback. Omit `layout` to re-run transforms only. ETag optional (412 on conflict; else last-write-wins). Operates on the default site unless site_id is provided. |
2224
+ | `yootheme_builder_pages_audit` | read+idempotent | `checks`, `max_chars`, `site_id`, `stale_after_months`, `thin_content_threshold`, `types` | One-call site-wide SEO/structure/content/links audit — severity-sorted findings with machine-executable fix_hints (missing/multiple h1, empty/thin pages, missing image alt, duplicate layouts, title collisions, non-pretty URLs, stale content, dead anchors). Filter with `types`/`checks`. Disclaimer: SEO-plugin meta (title/description/OG) NOT covered. Drill in via template_summary; fix one page at a time via each finding's fix_hint. Operates on the default site unless site_id is provided. |
2056
2225
  | `yootheme_builder_pages_create` | idempotent | `dry_run`, `layout`, `menu_item`, `published`, `site_id`, `slug`, `status`, `title` | Create a page (WordPress) or article (Joomla 5/6) with a YOOtheme per-page layout. Required: title. Optional: slug, status (default draft), published (default false → live on true: WP publish / Joomla state 1), layout (JSON), menu_item (Joomla SEF route), dry_run (preflight, no persist). Returns page_id (WP) / article_id (Joomla), slug, template_id (page:{id} or article:{id}), etag, warnings. Joomla: a published article has NO SEF route until a menu item points at it — pass menu_item, else frontend_url may 404. Operates on the default site unless site_id is provided. |
2057
2226
  | `yootheme_builder_pages_delete` | destructive | `confirm`, `force`, `post_id`, `site_id` | Delete a page (WordPress) or article (Joomla 5/6) with its YOOtheme per-page layout. Requires confirm:true (gate against accidental deletion). Default: trash (recoverable). force:true: permanent delete. Refuses non-page/article types with 403 (Layer 9 defense). Returns deleted:true, page_id (WP) / article_id (Joomla), force_deleted, bumped etag. Operates on the default site unless site_id is provided. |
2058
2227
  | `yootheme_builder_pages_list` | read+idempotent | `cursor`, `fields`, `label_contains`, `limit`, `max_chars`, `site_id` | List pages, templates, and layouts in the YOOtheme Pro builder (id, label, type, elements_count, frontend_url, builder_url, serves_context_kind, serves_context_ids per row). CALL THIS FIRST to discover template IDs before page_get_layout / element_list / page_get_schema. Paginates: up to `limit` rows (default 50) plus `total` and a `next_cursor` (pass it back as `cursor`) when more remain. Filter with `label_contains`. Slim rows with `fields:["id","label"]`; bound output with `max_chars`. Operates on the default site unless site_id is provided. |