@wootsup/yt-builder-mcp 1.0.0 → 1.1.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.
Files changed (187) hide show
  1. package/README.md +18 -6
  2. package/bin/yt-builder-mcp.js +6 -0
  3. package/dist/auth.d.ts +86 -6
  4. package/dist/auth.d.ts.map +1 -1
  5. package/dist/auth.js +141 -8
  6. package/dist/auth.js.map +1 -1
  7. package/dist/errors/sanitize.d.ts.map +1 -1
  8. package/dist/errors/sanitize.js +10 -0
  9. package/dist/errors/sanitize.js.map +1 -1
  10. package/dist/gateway/essentials.d.ts +1 -1
  11. package/dist/gateway/essentials.d.ts.map +1 -1
  12. package/dist/gateway/essentials.js +15 -2
  13. package/dist/gateway/essentials.js.map +1 -1
  14. package/dist/index.d.ts +5 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +52 -16
  17. package/dist/index.js.map +1 -1
  18. package/dist/install-skill.d.ts +10 -2
  19. package/dist/install-skill.d.ts.map +1 -1
  20. package/dist/install-skill.js +18 -7
  21. package/dist/install-skill.js.map +1 -1
  22. package/dist/platform/detect.d.ts +64 -0
  23. package/dist/platform/detect.d.ts.map +1 -0
  24. package/dist/platform/detect.js +98 -0
  25. package/dist/platform/detect.js.map +1 -0
  26. package/dist/platform/index.d.ts +72 -0
  27. package/dist/platform/index.d.ts.map +1 -1
  28. package/dist/platform/index.js +138 -0
  29. package/dist/platform/index.js.map +1 -1
  30. package/dist/platform/joomla.d.ts +44 -0
  31. package/dist/platform/joomla.d.ts.map +1 -0
  32. package/dist/platform/joomla.js +46 -0
  33. package/dist/platform/joomla.js.map +1 -0
  34. package/dist/server.d.ts +61 -2
  35. package/dist/server.d.ts.map +1 -1
  36. package/dist/server.js +86 -2
  37. package/dist/server.js.map +1 -1
  38. package/dist/setup-cli-confirm.d.ts +36 -0
  39. package/dist/setup-cli-confirm.d.ts.map +1 -0
  40. package/dist/setup-cli-confirm.js +59 -0
  41. package/dist/setup-cli-confirm.js.map +1 -0
  42. package/dist/setup-cli.d.ts +53 -1
  43. package/dist/setup-cli.d.ts.map +1 -1
  44. package/dist/setup-cli.js +525 -10
  45. package/dist/setup-cli.js.map +1 -1
  46. package/dist/setup-prompts.d.ts.map +1 -1
  47. package/dist/setup-prompts.js +12 -8
  48. package/dist/setup-prompts.js.map +1 -1
  49. package/dist/setup-wizard-types.d.ts +19 -3
  50. package/dist/setup-wizard-types.d.ts.map +1 -1
  51. package/dist/setup-wizard.d.ts.map +1 -1
  52. package/dist/setup-wizard.js +34 -1
  53. package/dist/setup-wizard.js.map +1 -1
  54. package/dist/sites/cli/add-site.d.ts +106 -0
  55. package/dist/sites/cli/add-site.d.ts.map +1 -0
  56. package/dist/sites/cli/add-site.js +185 -0
  57. package/dist/sites/cli/add-site.js.map +1 -0
  58. package/dist/sites/cli/list-sites.d.ts +40 -0
  59. package/dist/sites/cli/list-sites.d.ts.map +1 -0
  60. package/dist/sites/cli/list-sites.js +117 -0
  61. package/dist/sites/cli/list-sites.js.map +1 -0
  62. package/dist/sites/cli/remove-site.d.ts +60 -0
  63. package/dist/sites/cli/remove-site.d.ts.map +1 -0
  64. package/dist/sites/cli/remove-site.js +120 -0
  65. package/dist/sites/cli/remove-site.js.map +1 -0
  66. package/dist/sites/cli/set-default.d.ts +47 -0
  67. package/dist/sites/cli/set-default.d.ts.map +1 -0
  68. package/dist/sites/cli/set-default.js +69 -0
  69. package/dist/sites/cli/set-default.js.map +1 -0
  70. package/dist/sites/cli/test-site.d.ts +77 -0
  71. package/dist/sites/cli/test-site.d.ts.map +1 -0
  72. package/dist/sites/cli/test-site.js +146 -0
  73. package/dist/sites/cli/test-site.js.map +1 -0
  74. package/dist/sites/client-pool.d.ts +124 -0
  75. package/dist/sites/client-pool.d.ts.map +1 -0
  76. package/dist/sites/client-pool.js +172 -0
  77. package/dist/sites/client-pool.js.map +1 -0
  78. package/dist/sites/env-bridge.d.ts +55 -0
  79. package/dist/sites/env-bridge.d.ts.map +1 -0
  80. package/dist/sites/env-bridge.js +94 -0
  81. package/dist/sites/env-bridge.js.map +1 -0
  82. package/dist/sites/paths.d.ts +21 -0
  83. package/dist/sites/paths.d.ts.map +1 -0
  84. package/dist/sites/paths.js +37 -0
  85. package/dist/sites/paths.js.map +1 -0
  86. package/dist/sites/probe.d.ts +60 -0
  87. package/dist/sites/probe.d.ts.map +1 -0
  88. package/dist/sites/probe.js +112 -0
  89. package/dist/sites/probe.js.map +1 -0
  90. package/dist/sites/registry.d.ts +142 -0
  91. package/dist/sites/registry.d.ts.map +1 -0
  92. package/dist/sites/registry.js +163 -0
  93. package/dist/sites/registry.js.map +1 -0
  94. package/dist/sites/schema.d.ts +90 -0
  95. package/dist/sites/schema.d.ts.map +1 -0
  96. package/dist/sites/schema.js +71 -0
  97. package/dist/sites/schema.js.map +1 -0
  98. package/dist/sites/secret-resolver.d.ts +109 -0
  99. package/dist/sites/secret-resolver.d.ts.map +1 -0
  100. package/dist/sites/secret-resolver.js +189 -0
  101. package/dist/sites/secret-resolver.js.map +1 -0
  102. package/dist/sites/store.d.ts +82 -0
  103. package/dist/sites/store.d.ts.map +1 -0
  104. package/dist/sites/store.js +198 -0
  105. package/dist/sites/store.js.map +1 -0
  106. package/dist/sites/tools/index.d.ts +28 -0
  107. package/dist/sites/tools/index.d.ts.map +1 -0
  108. package/dist/sites/tools/index.js +30 -0
  109. package/dist/sites/tools/index.js.map +1 -0
  110. package/dist/sites/tools/sites-list.d.ts +48 -0
  111. package/dist/sites/tools/sites-list.d.ts.map +1 -0
  112. package/dist/sites/tools/sites-list.js +135 -0
  113. package/dist/sites/tools/sites-list.js.map +1 -0
  114. package/dist/sites/tools/sites-test.d.ts +47 -0
  115. package/dist/sites/tools/sites-test.d.ts.map +1 -0
  116. package/dist/sites/tools/sites-test.js +148 -0
  117. package/dist/sites/tools/sites-test.js.map +1 -0
  118. package/dist/skill-loader.d.ts +63 -3
  119. package/dist/skill-loader.d.ts.map +1 -1
  120. package/dist/skill-loader.js +123 -10
  121. package/dist/skill-loader.js.map +1 -1
  122. package/dist/tools/elements/builders.d.ts +2 -2
  123. package/dist/tools/elements/builders.d.ts.map +1 -1
  124. package/dist/tools/elements/builders.js +45 -23
  125. package/dist/tools/elements/builders.js.map +1 -1
  126. package/dist/tools/elements/handlers.d.ts +8 -1
  127. package/dist/tools/elements/handlers.d.ts.map +1 -1
  128. package/dist/tools/elements/handlers.js +37 -1
  129. package/dist/tools/elements/handlers.js.map +1 -1
  130. package/dist/tools/format/health-format.d.ts +12 -6
  131. package/dist/tools/format/health-format.d.ts.map +1 -1
  132. package/dist/tools/format/health-format.js +14 -11
  133. package/dist/tools/format/health-format.js.map +1 -1
  134. package/dist/tools/format/pages-format.d.ts +7 -1
  135. package/dist/tools/format/pages-format.d.ts.map +1 -1
  136. package/dist/tools/format/pages-format.js +25 -1
  137. package/dist/tools/format/pages-format.js.map +1 -1
  138. package/dist/tools/health.d.ts +2 -2
  139. package/dist/tools/health.d.ts.map +1 -1
  140. package/dist/tools/health.js +120 -47
  141. package/dist/tools/health.js.map +1 -1
  142. package/dist/tools/index.d.ts +9 -2
  143. package/dist/tools/index.d.ts.map +1 -1
  144. package/dist/tools/index.js +21 -7
  145. package/dist/tools/index.js.map +1 -1
  146. package/dist/tools/inspection.d.ts +2 -2
  147. package/dist/tools/inspection.d.ts.map +1 -1
  148. package/dist/tools/inspection.js +67 -22
  149. package/dist/tools/inspection.js.map +1 -1
  150. package/dist/tools/multi-items/builders.d.ts +2 -2
  151. package/dist/tools/multi-items/builders.d.ts.map +1 -1
  152. package/dist/tools/multi-items/builders.js +21 -8
  153. package/dist/tools/multi-items/builders.js.map +1 -1
  154. package/dist/tools/pages/builders.d.ts +2 -2
  155. package/dist/tools/pages/builders.d.ts.map +1 -1
  156. package/dist/tools/pages/builders.js +36 -24
  157. package/dist/tools/pages/builders.js.map +1 -1
  158. package/dist/tools/pages/handlers-read.d.ts.map +1 -1
  159. package/dist/tools/pages/handlers-read.js +10 -0
  160. package/dist/tools/pages/handlers-read.js.map +1 -1
  161. package/dist/tools/pages/schemas.d.ts.map +1 -1
  162. package/dist/tools/pages/schemas.js +18 -0
  163. package/dist/tools/pages/schemas.js.map +1 -1
  164. package/dist/tools/pool-resolve-helper.d.ts +83 -0
  165. package/dist/tools/pool-resolve-helper.d.ts.map +1 -0
  166. package/dist/tools/pool-resolve-helper.js +148 -0
  167. package/dist/tools/pool-resolve-helper.js.map +1 -0
  168. package/dist/tools/shared-schemas.d.ts +10 -0
  169. package/dist/tools/shared-schemas.d.ts.map +1 -1
  170. package/dist/tools/shared-schemas.js +20 -2
  171. package/dist/tools/shared-schemas.js.map +1 -1
  172. package/dist/tools/sources/builders.d.ts +2 -2
  173. package/dist/tools/sources/builders.d.ts.map +1 -1
  174. package/dist/tools/sources/builders.js +27 -15
  175. package/dist/tools/sources/builders.js.map +1 -1
  176. package/dist/tools/tool-builder/index.d.ts +1 -1
  177. package/dist/tools/tool-builder/index.d.ts.map +1 -1
  178. package/dist/tools/tool-builder/index.js +1 -1
  179. package/dist/tools/tool-builder/index.js.map +1 -1
  180. package/dist/tools/tool-builder/results.d.ts +34 -0
  181. package/dist/tools/tool-builder/results.d.ts.map +1 -1
  182. package/dist/tools/tool-builder/results.js +86 -0
  183. package/dist/tools/tool-builder/results.js.map +1 -1
  184. package/manifest.json +27 -10
  185. package/package.json +3 -1
  186. package/skills/yt-builder-mcp/SKILL.md +846 -0
  187. package/skills/yootheme-builder/SKILL.md +0 -587
@@ -1,587 +0,0 @@
1
- ---
2
- name: yootheme-builder
3
- description: Drive the YOOtheme Pro page builder programmatically — discover pages, inspect layouts, add/move/clone/delete elements, bind dynamic sources, diagnose 401/403 auth failures. Use when the user wants to build, modify, audit, or troubleshoot a YOOtheme-powered WordPress site through the YT Builder MCP server.
4
- ---
5
-
6
- # YT Builder MCP for YOOtheme Pro (unofficial) — Skill
7
-
8
- > Independent third-party project. YOOtheme® is a registered trademark of YOOtheme GmbH
9
- > ([yootheme.com](https://yootheme.com)). YT Builder MCP is built by WootsUp (getimo
10
- > productions) and is not affiliated with, endorsed by, or sponsored by YOOtheme.
11
- > The integration uses YOOtheme Pro's public extension points.
12
-
13
- This skill helps AI assistants drive the YOOtheme Pro page builder through the
14
- `@wootsup/yt-builder-mcp` server. The server exposes 24 typed,
15
- scoped, idempotent tools behind a 11-entry Gateway-Hub (so it stays
16
- inside the 80-tool Cursor cap even when the catalogue grows).
17
-
18
- ## How to use this MCP server
19
-
20
- The user invokes you through Claude Desktop, Cursor, Zed, Continue, or
21
- any other MCP-aware AI client. Setup looks like this:
22
-
23
- 1. The user installs the WordPress plugin
24
- (`https://wootsup.com/products/yt-builder-mcp`) and generates
25
- a Bearer key in **wp-admin → Tools → "YT Builder MCP" → Bearer Keys**.
26
- 2. The user runs `npx -y @wootsup/yt-builder-mcp setup` once;
27
- the wizard probes the plugin, validates the key, and writes the
28
- MCP server entry into every selected AI client's config file.
29
- 3. The user restarts their AI client. The server is now visible.
30
- 4. The user asks for a YOOtheme task (build, audit, change, diagnose).
31
-
32
- When the user asks a YOOtheme-related question, **always start with
33
- `yootheme_builder_health`** — it confirms the plugin is reachable and
34
- returns the plugin/YOOtheme/WordPress/PHP versions you need to know
35
- about before reading or writing layout state.
36
-
37
- If a tool returns `401 Unauthorized` or `403 Forbidden`, jump straight
38
- to **Workflow 4: Diagnose 401/auth failure**. Do not retry blindly.
39
-
40
- ## Gateway routing (so you know what you can call)
41
-
42
- The server exposes:
43
-
44
- - **2 direct top-level tools** — always callable, always in `tools/list`:
45
- `yootheme_builder_health` and `yootheme_builder_diagnose`. These are
46
- the "the gateway itself might be broken" escape hatch.
47
- - **8 essential forwarded tools** — common reads + the most-used writes
48
- (page list, get_etag, element list / add / update, sources_list,
49
- element_types_list, inspect_multi_items_binding). Always in
50
- `tools/list` so AI clients see them first-class.
51
- - **13 advanced captured tools** — everything else (move, clone, delete,
52
- schema introspection, source binding, clean_implode_directives).
53
- Reachable through one gateway tool:
54
- `yootheme_builder_advanced({ tool: "<name>", input: { ... } })`.
55
- - **1 gateway tool** — `yootheme_builder_advanced`.
56
-
57
- If the AI client reports "tool not found", you are almost certainly
58
- calling an advanced tool by its raw name. Wrap it in
59
- `yootheme_builder_advanced({ tool, input })` instead.
60
-
61
- ## Scopes (Bearer key permissions)
62
-
63
- Every Bearer key has a scope, set at key creation time:
64
-
65
- | Scope | Reads | Writes | Destructive |
66
- |----------|-------|--------|-------------|
67
- | `read` | ✓ | ✗ | ✗ |
68
- | `write` | ✓ | ✓ | ✗ |
69
- | `admin` | ✓ | ✓ | ✓ |
70
-
71
- When a tool returns `{ error: 'insufficient_scope', context: { required: 'write', actual: 'read' } }`,
72
- ask the user to regenerate the key with a higher scope **before**
73
- retrying. Do not loop on auth errors.
74
-
75
- ---
76
-
77
- ## Workflow 1: Build a hero section
78
-
79
- **Goal:** Add a fresh hero section (heading + sub-heading + CTA button)
80
- to an existing page.
81
-
82
- **Canonical tool-call sequence (real parameter names — snake_case):**
83
-
84
- 1. `yootheme_builder_health` — confirm plugin reachable; note plugin
85
- version (some element types are version-gated).
86
- 2. `yootheme_builder_pages_list({ fields: ["id", "label"] })` — find
87
- the target template. Returns `[{ id, label, ... }]`. If the user
88
- named a specific page, match on `label` (exact then fuzzy).
89
- 3. `yootheme_builder_get_etag()` — fetch the current top-level
90
- optimistic-lock ETag. Every write tool requires it via `etag`.
91
- 4. `yootheme_builder_element_add({ template_id: "<id>", parent_path: "", element_type: "section", props: { background: "primary" }, etag: "<etag>" })`
92
- — append a new section at the template root (`parent_path: ""`).
93
- Returns `{ path: "/0/children/N", etag: "<fresh>" }`.
94
- 5. `yootheme_builder_element_add({ template_id, parent_path: "<section-path>", element_type: "row", etag: "<fresh-etag>" })`
95
- — add a row inside the section. Use the etag returned by the
96
- previous write — etags rotate every mutation.
97
- 6. `yootheme_builder_element_add({ template_id, parent_path: "<row-path>", element_type: "headline", props: { content: "<h1 text>" }, etag })`
98
- — add a headline.
99
- 7. `yootheme_builder_element_add({ template_id, parent_path: "<row-path>", element_type: "text", props: { content: "<sub text>" }, etag })`
100
- — add a text element.
101
- 8. `yootheme_builder_element_add({ template_id, parent_path: "<row-path>", element_type: "button", props: { content: "<cta>", link: "<url>" }, etag })`
102
- — add the CTA button.
103
- 9. `yootheme_builder_page_save({ template_id, etag })` — persist the
104
- working copy (visible in YOOtheme Customizer preview).
105
- 10. `yootheme_builder_page_publish({ template_id, etag })` — make the
106
- changes live on the front-end.
107
-
108
- **Common pitfalls:**
109
-
110
- - **Wrong parameter names.** Every tool uses snake_case. Use
111
- `template_id` (not `pageId`), `parent_path` (not `parentPath`),
112
- `element_type` (not `type`), `props` (not `settings`), `etag`
113
- (not `ifMatch`). The MCP server rejects unknown keys with a
114
- Zod-validation error.
115
- - **Forgetting `etag`.** Every write tool needs the latest etag. The
116
- shared schema marks it required (min length 1). On `412 Precondition
117
- Failed` re-fetch via `yootheme_builder_get_etag` and retry.
118
- - **Adding non-row elements directly to a section.** Sections expect a
119
- row in between. The server returns a structured error with a
120
- human-readable hint when you skip the row.
121
- - **Saving without publishing.** `page_save` is the equivalent of the
122
- YOOtheme Customizer "Save" button — content lives in the staging
123
- copy. Visitors see nothing until `page_publish`.
124
- - **Reusing a stale etag across many writes.** Every write returns a
125
- fresh etag in the response. Pass THAT etag into the next write —
126
- don't hold the one from the original `get_etag` call.
127
-
128
- **Worked example (tool-call snippet):**
129
-
130
- ```jsonc
131
- // Step 4 — add the section. parent_path: "" means template root.
132
- yootheme_builder_element_add({
133
- template_id: "home",
134
- parent_path: "",
135
- element_type: "section",
136
- props: { background: "primary" },
137
- etag: "abc123" // from yootheme_builder_get_etag
138
- })
139
- // Response: { path: "/0/children/3", etag: "def456" }
140
- // → next call uses etag "def456"
141
- ```
142
-
143
- **Edge case:** YOOtheme allows nested sections (rare) — if the user
144
- asks for a "card grid inside a hero", you still need the
145
- `section → row → column → grid` hierarchy, even when the parent
146
- section sits inside another section.
147
-
148
- **Success criterion:** After `page_publish`, navigating to the page
149
- URL on the front-end shows the new hero section above the previous
150
- content. Re-reading the layout via
151
- `yootheme_builder_page_get_layout({ template_id })` shows the new
152
- section as the last child of the template root.
153
-
154
- ---
155
-
156
- ## Workflow 2: Bind a dynamic source to a grid
157
-
158
- **Goal:** Wire an existing Grid (or other multi-item element) to a
159
- Source from API Mapper or the built-in YOOtheme Sources system so it
160
- renders dynamic items.
161
-
162
- **Canonical tool-call sequence (real parameter names — snake_case):**
163
-
164
- 1. `yootheme_builder_health` — confirm plugin reachable.
165
- 2. `yootheme_builder_pages_list({ fields: ["id", "label"] })` and
166
- `yootheme_builder_page_get_layout({ template_id: "<id>", flat: false })`
167
- — locate the target Grid. Note its JSON-Pointer `path` (e.g.
168
- `/0/children/2/children/0`).
169
- 3. `yootheme_builder_element_get({ template_id, element_path })` —
170
- fetch the Grid's current props so you can preserve them; binding
171
- sets `props.source` and leaves the rest alone.
172
- 4. `yootheme_builder_sources_list()` — enumerate available Sources.
173
- Each returns `{ name, label, origin, kind }`. Pick the one the
174
- user asked for.
175
- 5. `yootheme_builder_element_get_binding({ template_id, element_path })`
176
- — check whether the Grid is already bound (idempotency: skip step
177
- 7 if `source_name` already matches the user's intent).
178
- 6. `yootheme_builder_get_etag()` — fetch the optimistic-lock etag for
179
- the upcoming mutation.
180
- 7. `yootheme_builder_element_bind_source({ template_id, element_path, source_name: "<name>", etag: "<etag>" })`
181
- — apply the binding. Returns `{ path, etag, has_binding: true }`.
182
- Pass `source_id: "<origin>:<name>"` as well **only** when two
183
- plugins register a source with the same `source_name` (the server
184
- surfaces the ambiguity as an elicitation prompt; if the host
185
- doesn't support elicitation you'll see a structured error listing
186
- the candidates).
187
- 8. `yootheme_builder_page_save({ template_id, etag: "<fresh>" })`
188
- then `yootheme_builder_page_publish({ template_id, etag: "<fresh>" })`.
189
-
190
- **Common pitfalls:**
191
-
192
- - **Inventing `fieldMap`.** The bind tool's schema is just
193
- `template_id`, `element_path`, `source_name`, optional `source_id`,
194
- `etag`. Field mapping happens inside YOOtheme at render time based
195
- on the element's own field bindings — not via an MCP parameter.
196
- - **Wrong parameter names.** Use `template_id` (not `pageId`),
197
- `element_path` (not `path`), `source_name` (not `sourceName`),
198
- `etag` (not `ifMatch`).
199
- - **Source not in the list.** API Mapper sources only appear once
200
- they're PUBLISHED in API Mapper (not just saved). If
201
- `yootheme_builder_sources_list` returns no match for the name the
202
- user typed, send the user to API Mapper to publish it.
203
- - **Binding non-list elements.** Only multi-item element types (Grid,
204
- List, Switcher, Slider, Slideshow, Carousel, Map) accept a source
205
- binding. Binding a single-item element like Headline returns a
206
- structured `validation` error.
207
- - **Forgetting `etag`.** Every write requires the optimistic-lock
208
- etag. On `412 Precondition Failed` re-fetch via
209
- `yootheme_builder_get_etag` and retry.
210
-
211
- **Worked example (tool-call snippet):**
212
-
213
- ```jsonc
214
- // Step 7 — bind a Posts source onto a Grid element.
215
- yootheme_builder_element_bind_source({
216
- template_id: "home",
217
- element_path: "/0/children/2/children/0",
218
- source_name: "wp_posts",
219
- etag: "abc123"
220
- // source_id: "wordpress:wp_posts" // pass ONLY when name collides
221
- })
222
- // Response: { path: "/0/children/2/children/0", etag: "def456", has_binding: true }
223
- // Verify:
224
- yootheme_builder_element_get_binding({
225
- template_id: "home",
226
- element_path: "/0/children/2/children/0"
227
- })
228
- // → { source_name: "wp_posts", source_config: { ... }, ... }
229
- ```
230
-
231
- **Edge case:** A Source can render zero items at runtime (e.g. empty
232
- search filter). The bind call still succeeds; the front-end Grid just
233
- shows the YOOtheme "no items" placeholder. Don't treat empty render
234
- as a binding failure — verify by re-reading
235
- `yootheme_builder_element_get_binding`.
236
-
237
- **Success criterion:** After publish, the Grid on the front-end shows
238
- items from the Source (verify by item count and at least one
239
- field-value spot-check). `yootheme_builder_element_get_binding`
240
- returns the new `source_name`.
241
-
242
- ---
243
-
244
- ## Workflow 3: Clone & modify a section within a template
245
-
246
- **Goal:** Duplicate a section inside the SAME template and tweak the
247
- copy. Common variants: A/B-style hero, repeated CTA blocks, mirroring
248
- a row layout. (Cross-template duplication is **not** supported by
249
- `element_clone` — see "Important scope note" below.)
250
-
251
- **Important scope note:** `yootheme_builder_element_clone` is
252
- **sibling-only and intra-template**. Its real schema is
253
- `{ template_id, element_path, etag }` — there is **no** `destPageId`
254
- or `destParentPath`. The cloned element lands at the same parent,
255
- right after the source. To move the clone elsewhere in the SAME
256
- template, call `yootheme_builder_element_move` afterwards. To
257
- duplicate into a DIFFERENT template, flag to the user that
258
- cross-template clone is not currently supported and suggest a
259
- WordPress-level template duplication.
260
-
261
- **Canonical tool-call sequence (real parameter names — snake_case):**
262
-
263
- 1. `yootheme_builder_health` — confirm plugin reachable.
264
- 2. `yootheme_builder_pages_list({ fields: ["id", "label"] })` —
265
- locate the template by `label`. Note its `id`.
266
- 3. `yootheme_builder_page_get_schema({ template_id })` — flat schema
267
- view (lighter than `page_get_layout`) showing every element path
268
- + type. Pick the JSON-Pointer path of the section to clone.
269
- 4. `yootheme_builder_get_etag()` — fetch the optimistic-lock etag.
270
- 5. `yootheme_builder_element_clone({ template_id, element_path: "<src-path>", etag: "<etag>" })`
271
- — clone as sibling. Returns `{ path: "<new-path>", etag: "<fresh>" }`.
272
- The new path is at the same parent, immediately after the source.
273
- 6. (Optional) `yootheme_builder_element_move({ template_id, element_path: "<new-path>", to_parent_path: "<other-parent>", to_index: 0, etag: "<fresh>" })`
274
- — re-parent the clone within the same template if needed.
275
- 7. `yootheme_builder_element_update_settings({ template_id, element_path: "<final-path>", props: { ... }, etag: "<fresh>" })`
276
- — replace the `props` on the clone. **Existing props NOT in the
277
- request are removed** (update_settings is a full replace, not a
278
- merge). Read the current props first via
279
- `yootheme_builder_element_get` if you only want to tweak a subset.
280
- 8. `yootheme_builder_page_save({ template_id, etag })` then
281
- `yootheme_builder_page_publish({ template_id, etag })`.
282
-
283
- **Common pitfalls:**
284
-
285
- - **Inventing destination parameters.** `element_clone` does NOT
286
- accept `destPageId`, `destParentPath`, or any cross-template
287
- argument. It's sibling-only within ONE template.
288
- - **Treating `element_update_settings` as a merge.** The handler
289
- REPLACES the entire `props` object on the element; any key you
290
- don't include is removed. Use `element_get` first if you need to
291
- preserve siblings of the field you're changing.
292
- - **Clone-then-update path drift.** The clone returns a path that's
293
- correct at the moment of the call. If you fire off many ops in
294
- parallel, a concurrent edit may shift indices — refresh via
295
- `get_etag` + `page_get_schema` between independent batches.
296
- - **Cloning a bound element keeps the binding.** `element_clone`
297
- copies the entire element including `props.source`. If the user
298
- wanted a "data-free" copy, call
299
- `yootheme_builder_element_unbind_source` on the new path
300
- afterwards.
301
- - **Wrong parameter names.** Use `template_id`, `element_path`,
302
- `etag` (NOT `pageId`, `srcPath`, `ifMatch`).
303
-
304
- **Worked example (tool-call snippet):**
305
-
306
- ```jsonc
307
- // Step 5 — clone the section element as a sibling.
308
- yootheme_builder_element_clone({
309
- template_id: "home",
310
- element_path: "/0/children/2", // the hero section to duplicate
311
- etag: "abc123"
312
- })
313
- // Response: { path: "/0/children/3", etag: "def456" }
314
-
315
- // Step 7 — tweak the clone (replace props entirely).
316
- // First read the current shape so you can preserve siblings:
317
- const current = yootheme_builder_element_get({
318
- template_id: "home",
319
- element_path: "/0/children/3",
320
- });
321
- yootheme_builder_element_update_settings({
322
- template_id: "home",
323
- element_path: "/0/children/3",
324
- props: { ...current.props, background: "secondary" },
325
- etag: "def456"
326
- })
327
- ```
328
-
329
- **Edge case:** When cloning a Grid with a source binding, the binding
330
- is preserved (same `source_name`). If the user wants a "data-free"
331
- copy, follow up with `yootheme_builder_element_unbind_source` on the
332
- new path. Verify with `yootheme_builder_element_get_binding`.
333
-
334
- **Success criterion:** After publish,
335
- `yootheme_builder_page_get_schema({ template_id })` shows the new
336
- section at the cloned path with the user's tweaks reflected in
337
- `element_get` on that path.
338
-
339
- ---
340
-
341
- ## Workflow 4: Diagnose a 401 / 403 / auth failure
342
-
343
- **Goal:** Recover from `401 Unauthorized` or `403 Forbidden` without
344
- guessing — and without rotating the user's key unnecessarily.
345
-
346
- **Canonical tool-call sequence:**
347
-
348
- 1. `yootheme_builder_diagnose` — single probe that hits `/health` (no
349
- auth) and then `/etag` (Bearer auth). Returns
350
- `{ plugin_reachable, plugin_version, yootheme_loaded, yootheme_version,
351
- endpoint_count, bearer_valid, bearer_error?, summary? }`. Call this
352
- **before** any other tool when you see auth errors. (Takes no
353
- arguments — the schema is `{}`.)
354
- 2. **Interpret the result:**
355
- - `plugin_reachable: false` → the WordPress install is down OR the
356
- plugin is deactivated. Send the user to wp-admin → Plugins →
357
- activate "YT Builder MCP". Do not retry until they confirm.
358
- - `plugin_reachable: true, bearer_valid: false` → the Bearer key is
359
- wrong (typo, revoked, or wrong key for this install). The
360
- `bearer_error` field carries the upstream HTTP status. Send the
361
- user to wp-admin → Tools → "YT Builder MCP" → Bearer Keys → either
362
- copy the existing key into their MCP client config, or generate
363
- a new one. Then they must restart the AI client.
364
- - `plugin_reachable: true, bearer_valid: true` but the original
365
- tool returned a 403 → the key works but the scope is too low for
366
- the tool's required scope (`write` for mutations, `admin` for
367
- destructive operations). Ask the user to regenerate the key with
368
- a higher scope and restart the AI client.
369
- 3. **Walk the user through key rotation if needed:**
370
- - "Go to wp-admin → Tools → YT Builder MCP → Bearer Keys."
371
- - "Click 'Generate New Key', pick the scope (admin for full access)."
372
- - "Copy the key — it's shown ONCE; you cannot recover it later."
373
- - "Update your AI client config: replace `YTB_MCP_BEARER_TOKEN`
374
- with the new key. The fastest way is to re-run
375
- `npx -y @wootsup/yt-builder-mcp setup`."
376
- - "Restart Claude / Cursor / Zed / Continue / Cline / Roo Code."
377
- - "Confirm with `yootheme_builder_diagnose` that
378
- `bearer_valid: true` before retrying the original task."
379
-
380
- **Common pitfalls:**
381
-
382
- - **Treating 401 as a network error.** A network error has no HTTP
383
- status — it's a TCP/TLS / DNS failure. 401 means the server
384
- responded "I do not accept this key", which is fundamentally a
385
- config problem.
386
- - **Stripping the `Bearer ` prefix.** The MCP server adds it
387
- automatically when reading `YTB_MCP_BEARER_TOKEN`. If the user
388
- pasted the prefix into the env var, the value sent will be
389
- `Bearer Bearer ytb_live_…` and every request 401s.
390
- - **Trying every tool to "see which ones work".** Don't. One
391
- `yootheme_builder_diagnose` call tells you whether the failure is
392
- reachability, auth, or scope.
393
- - **Confusing 401 with 403.** 401 = "I don't recognise this key"
394
- (rotate). 403 = "I recognise the key but it lacks the required
395
- scope" (regenerate with higher scope). Different error codes,
396
- different recovery — never collapse them into one branch.
397
-
398
- **Worked example (tool-call snippet):**
399
-
400
- ```jsonc
401
- // First — never retry blindly. Call diagnose (no args).
402
- yootheme_builder_diagnose({})
403
- // Response shape:
404
- // {
405
- // plugin_reachable: true,
406
- // plugin_version: "0.1.0-alpha.1",
407
- // yootheme_loaded: true,
408
- // yootheme_version: "5.0.22",
409
- // endpoint_count: 16,
410
- // bearer_valid: false, // ← key is bad
411
- // bearer_error: "HTTP 401: invalid_token"
412
- // }
413
- // → diagnosis: rotate the key. Send user to wp-admin → Settings.
414
- ```
415
-
416
- **Edge case:** `plugin_reachable: true` but `yootheme_loaded: false`
417
- — the user installed the MCP plugin but YOOtheme itself isn't
418
- active. The MCP server still answers, but every tool that touches
419
- the YOOtheme layout returns an empty/error response. Surface the
420
- mismatch ("YOOtheme is not active on this install") instead of
421
- retrying.
422
-
423
- **Success criterion:** A subsequent `yootheme_builder_diagnose`
424
- returns `plugin_reachable: true` AND `bearer_valid: true`. The
425
- original tool now returns a non-auth response.
426
-
427
- ---
428
-
429
- ## Workflow 5: Add a custom element type to a page
430
-
431
- **Goal:** Inspect what element types are installed on the user's
432
- YOOtheme install (built-ins + YOOtheme Pro + YOOessentials + child
433
- theme + plugin-contributed elements), pick the right one, and place
434
- an instance with a sensible default props payload.
435
-
436
- **Canonical tool-call sequence (real parameter names — snake_case):**
437
-
438
- 1. `yootheme_builder_health` — note the YOOtheme version; custom
439
- elements often require a minimum YOOtheme major.
440
- 2. `yootheme_builder_element_types_list({ fields: ["name", "label", "origin"] })`
441
- — narrow the catalogue with sparse-fields. Returns rows like
442
- `{ name: "headline", label: "Headline", origin: "core", ... }`.
443
- 3. `yootheme_builder_element_type_get_schema({ type_name: "<picked>" })`
444
- — fetch the prop schema for the chosen type. Returns the field
445
- definitions you can pass via `props`. Note: the parameter is
446
- `type_name`, not `name`.
447
- 4. `yootheme_builder_pages_list({ fields: ["id", "label"] })` and
448
- `yootheme_builder_page_get_layout({ template_id, flat: false })`
449
- — locate the `parent_path` (JSON-Pointer) where the new element
450
- should land.
451
- 5. `yootheme_builder_get_etag()` — fetch the optimistic-lock etag.
452
- 6. `yootheme_builder_element_add({ template_id, parent_path: "<path>", element_type: "<picked-name>", props: { ... }, etag })`
453
- — the server validates `props` against the type schema and
454
- returns a structured `validation` error with a per-field issue
455
- list if anything is missing or malformed.
456
- 7. (Optional) `yootheme_builder_element_update_settings({ template_id, element_path: "<new-path>", props: { ... }, etag })`
457
- — iterate on the props. **Note: this REPLACES `props` entirely**
458
- — include every key you want to keep.
459
- 8. `yootheme_builder_page_save({ template_id, etag })` then
460
- `yootheme_builder_page_publish({ template_id, etag })`.
461
-
462
- **Common pitfalls:**
463
-
464
- - **Wrong parameter name on the type-schema tool.** It's
465
- `type_name`, not `name`. The server's Zod schema rejects unknown
466
- keys.
467
- - **Wrong parameter names on `element_add`.** Use `template_id`
468
- (not `pageId`), `parent_path` (not `parentPath`), `element_type`
469
- (not `type` / `name`), `props` (not `settings`), `etag` (not
470
- `ifMatch`).
471
- - **`element_update_settings` is a full replace, not a merge.** Any
472
- key NOT in the request is REMOVED from `props`. Read the existing
473
- shape via `yootheme_builder_element_get` first if you only want
474
- to tweak a subset.
475
- - **Custom elements without a schema.** A poorly-built third-party
476
- element may not register a prop schema. In that case
477
- `yootheme_builder_element_type_get_schema` returns an empty/sparse
478
- schema and the server accepts arbitrary `props`. Don't assume "no
479
- schema = no required fields" — read the third-party element's
480
- docs.
481
- - **Type name vs. label confusion.** The `name` field on the
482
- catalogue row is the machine identifier (e.g. `pro_slider`); the
483
- `label` is the human display string ("Pro Slider"). Always pass
484
- the `name` (as `element_type` / `type_name`).
485
- - **Pro-only types on a Free install.** YOOtheme Free does not
486
- register `pro_*` element types. Surface that to the user instead
487
- of retrying with a different filter.
488
-
489
- **Worked example (tool-call snippet):**
490
-
491
- ```jsonc
492
- // Step 2 — narrow the catalogue with sparse-fields to save tokens.
493
- yootheme_builder_element_types_list({
494
- fields: ["name", "label", "origin"]
495
- })
496
- // Rows: [{ name: "headline", label: "Headline", origin: "core" }, ...]
497
-
498
- // Step 3 — fetch the schema (note: type_name, not name).
499
- yootheme_builder_element_type_get_schema({ type_name: "headline" })
500
- // Returns the field definitions for the headline's `props`.
501
-
502
- // Step 6 — place the element.
503
- yootheme_builder_element_add({
504
- template_id: "home",
505
- parent_path: "/0/children/2", // row inside section
506
- element_type: "headline",
507
- props: { content: "Welcome", tag: "h1" },
508
- etag: "abc123"
509
- })
510
- // Response: { path: "/0/children/2/children/0", etag: "def456" }
511
- ```
512
-
513
- **Edge case:** A child theme can override a built-in element's
514
- schema in PHP. The `origin` field will read `child_theme` instead
515
- of `core`. If you see surprising required keys, that's the override
516
- talking — surface this to the user so they know their theme is
517
- customising element defaults.
518
-
519
- **Success criterion:** After publish, the front-end shows the new
520
- element rendered with its default props. A re-read of the layout
521
- shows the element under `parent_path` with the chosen `element_type`
522
- and the props payload you passed.
523
-
524
- ---
525
-
526
- ## When something doesn't fit one of these 5 workflows
527
-
528
- - **Move an element** (intra-template reorder/reparent): use
529
- `yootheme_builder_element_move({ template_id, element_path,
530
- to_parent_path, to_index, etag })`. Reorders or reparents without
531
- re-creating.
532
- - **Delete an element**: use `yootheme_builder_element_delete({
533
- template_id, element_path, etag, confirm: true })`.
534
- Elicitation-aware — confirms via the AI client prompt before
535
- destroying state when `confirm` is omitted. On hosts without
536
- elicitation, it returns a preview-with-confirm-required response;
537
- call again with `confirm: true`.
538
- - **Unbind a source**: use `yootheme_builder_element_unbind_source({
539
- template_id, element_path, etag, confirm: true })`. Same
540
- elicitation flow as delete.
541
- - **Flat schema inspection** (e.g. enumerate every element path +
542
- type without fetching the whole nested tree): use
543
- `yootheme_builder_page_get_schema({ template_id })`.
544
- - **Etag-only fetch** (e.g. polling for concurrent edits): use
545
- `yootheme_builder_get_etag()` (takes no arguments) — cheaper than
546
- fetching the full layout.
547
-
548
- If the user asks for something none of the above covers (e.g. global
549
- theme settings, menu management, media library), tell them clearly:
550
- "This MCP server only covers the YOOtheme Page Builder surface. For
551
- <X> you'll need <YOOtheme MCP / WP REST / direct wp-admin>." Don't
552
- fabricate tool calls.
553
-
554
- ## Appendix: Tool Catalog (auto-generated)
555
-
556
- <!-- TOOL-CATALOG:BEGIN -->
557
-
558
- **24 tools** — 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.
559
-
560
- | Tool | Kind | Input keys | Description |
561
- | --- | --- | --- | --- |
562
- | `yootheme_builder_clean_implode_directives` | idempotent | `element_path`, `etag`, `template_id` | Strips `props.source.props.*.implode` directives from an element binding. Returns audit log + new ETag. Idempotent (cleaned_count: 0 when nothing to remove). Requires ETag. |
563
- | `yootheme_builder_diagnose` | read+idempotent | _(none)_ | Run a full diagnostic: hit /health (no auth), then attempt an authenticated call (/etag) to confirm the Bearer key is valid. Use when health passes but tools return 401/403. |
564
- | `yootheme_builder_element_add` | mutating | `children`, `element_type`, `etag`, `parent_path`, `props`, `template_id` | Add a new element to a template. Provide `parent_path` (or "" for root), `element_type` (e.g. "headline", "text", "grid"), and optional `props` / `children`. Returns the new element's JSON-Pointer path. Requires ETag. |
565
- | `yootheme_builder_element_bind_source` | idempotent | `bindingLevel`, `element_path`, `etag`, `field_mappings`, `source_id`, `source_name`, `template_id` | Binds a Builder source to an element (sets `props.source`). Use bindingLevel "item" on Multi-Items containers (grid/slideshow/switcher/…) to bind on the first *_item child instead of the container itself. Requires ETag. |
566
- | `yootheme_builder_element_clone` | mutating | `element_path`, `etag`, `template_id` | Clone an element as a sibling (same parent, immediately after the source). Returns the new element's path. Requires ETag. |
567
- | `yootheme_builder_element_delete` | destructive | `confirm`, `element_path`, `etag`, `template_id` | PERMANENTLY delete an element and all its children. Cannot be undone. Always ask the user to confirm first, then call again with `confirm: true`. Requires ETag. |
568
- | `yootheme_builder_element_get` | read+idempotent | `element_path`, `template_id` | Get the full element object at a specific JSON-Pointer path, including props and children. Use yootheme_builder_element_list to discover paths. |
569
- | `yootheme_builder_element_get_binding` | read+idempotent | `element_path`, `template_id` | Read the source binding (and source_config/source_args/etc.) attached to an element. Returns the empty object if the element is not bound. |
570
- | `yootheme_builder_element_list` | read+idempotent | `fields`, `template_id` | List all elements in a template as a flat array with JSON-Pointer paths + element types. Best starting-point for "find the element I want to edit". Pass `fields:["path","element_type"]` to narrow each row. |
571
- | `yootheme_builder_element_move` | idempotent | `element_path`, `etag`, `template_id`, `to_index`, `to_parent_path` | Move an element to a new parent + index in the tree. Useful for reordering or reparenting (e.g. moving a card from one grid column to another). Requires ETag. |
572
- | `yootheme_builder_element_type_get_schema` | read+idempotent | `type_name` | Get the prop/field schema for a single element type. Use the result to discover valid keys for `props` when calling yootheme_builder_element_add or _update_settings. |
573
- | `yootheme_builder_element_types_list` | read+idempotent | `fields` | List element types registered on this site (built-ins + YOOessentials/uEssentials extras). Names feed `element_type` of element_add. Pass `fields[]` to narrow each row. |
574
- | `yootheme_builder_element_unbind_source` | destructive | `confirm`, `element_path`, `etag`, `template_id` | Remove the source binding from an element. Clears `props.source`. Destructive in the sense that it may break dynamic-content rendering — always ask the user to confirm. Requires ETag. |
575
- | `yootheme_builder_element_update_settings` | idempotent | `element_path`, `etag`, `merge`, `props`, `template_id` | Update `props` on an element. Default replaces all props; pass `merge:true` for server-side deep-merge (only request keys overwritten, others survive — avoids read-modify-write races). Requires ETag. |
576
- | `yootheme_builder_get_etag` | read+idempotent | _(none)_ | Get the current top-level state ETag. Pass this back via the `etag` parameter on any write tool to prevent overwriting concurrent edits. |
577
- | `yootheme_builder_health` | read+idempotent | _(none)_ | Check that the YT Builder MCP plugin is installed and reachable. Returns plugin version, YOOtheme Pro version (if loaded), and the list of available REST endpoints. Unauthenticated probe — call this first when troubleshooting connectivity. |
578
- | `yootheme_builder_inspect_multi_items_binding` | read+idempotent | `element_path`, `template_id` | Reports Multi-Items binding state: container/item pair (grid↔grid_item, slideshow↔slideshow_item, …), current binding level (none\|container\|item), and a recommended_fix when the binding sits on the container instead of the child. |
579
- | `yootheme_builder_page_get_layout` | read+idempotent | `fields`, `flat`, `template_id` | Get full layout tree for one template. Default nested `{layout, etag}`. Set `flat:true` for depth-first array `{elements:[...], etag}`; combine with `fields[]` to project per-element. |
580
- | `yootheme_builder_page_get_schema` | read+idempotent | `template_id` | Get the flat schema for a template — a list of nodes with their JSON-Pointer paths and element types. Best entry-point for navigation: lighter than page_get_layout, sufficient to locate elements before editing. |
581
- | `yootheme_builder_page_publish` | idempotent | `etag`, `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. |
582
- | `yootheme_builder_page_save` | idempotent | `etag`, `template_id` | Re-run save-transforms and persist. ETag optional — when provided, 412 on conflict; when omitted, last-write-wins. Recommended for collaborative edits. No-op when state is byte-identical (returns `no_changes:true`, ETag unchanged). |
583
- | `yootheme_builder_pages_list` | read+idempotent | `fields` | List all YOOtheme templates ("pages") on the site. Returns id, label and usage metadata for each. Use this first to discover template IDs. Pass `fields:["id","label"]` to project per-item to a smaller shape. |
584
- | `yootheme_builder_sources_list` | read+idempotent | `fields` | List Builder sources grouped by origin (apimapper/wordpress/essentials). Returns name+label per source — pick one for `element_bind_source`. Pass `fields[]` to narrow each row. |
585
- | `yootheme_builder_template_summary` | read+idempotent | `template_id` | Token-efficient template overview: element counts by type, binding count, max nesting depth, and named landmark sections — computed server-side in one call. Use this to grasp a large template before pulling element_list or page_get_layout. |
586
-
587
- <!-- TOOL-CATALOG:END -->