@diviops/mcp-server 0.2.28 → 1.0.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.
- package/README.md +66 -59
- package/dist/compatibility.d.ts +1 -1
- package/dist/compatibility.js +1 -1
- package/dist/index.js +387 -118
- package/dist/wp-cli-fs-validator.d.ts +6 -2
- package/dist/wp-cli-fs-validator.js +64 -2
- package/dist/wp-cli.d.ts +19 -3
- package/dist/wp-cli.js +95 -56
- package/dist/wp-client.js +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -83,7 +83,7 @@ const server = new McpServer({
|
|
|
83
83
|
version: SERVER_VERSION,
|
|
84
84
|
});
|
|
85
85
|
// ── Read Tools ───────────────────────────────────────────────────────
|
|
86
|
-
server.registerTool("
|
|
86
|
+
server.registerTool("diviops_page_list", {
|
|
87
87
|
description: "List pages/posts in the WordPress site. Returns title, ID, URL, status, and whether each page uses Divi builder.",
|
|
88
88
|
inputSchema: {
|
|
89
89
|
post_type: z
|
|
@@ -99,7 +99,7 @@ server.registerTool("diviops_list_pages", {
|
|
|
99
99
|
page: z.number().optional().default(1).describe("Page number"),
|
|
100
100
|
},
|
|
101
101
|
}, async ({ post_type, per_page, page }) => {
|
|
102
|
-
const result = await wp.request("/
|
|
102
|
+
const result = await wp.request("/page/list", {
|
|
103
103
|
params: {
|
|
104
104
|
post_type: post_type ?? "page",
|
|
105
105
|
per_page: String(per_page ?? 20),
|
|
@@ -112,20 +112,20 @@ server.registerTool("diviops_list_pages", {
|
|
|
112
112
|
],
|
|
113
113
|
};
|
|
114
114
|
});
|
|
115
|
-
server.registerTool("
|
|
115
|
+
server.registerTool("diviops_page_get", {
|
|
116
116
|
description: "Get detailed info about a specific page including its raw Divi block content.",
|
|
117
117
|
inputSchema: {
|
|
118
118
|
page_id: z.number().describe("WordPress post/page ID"),
|
|
119
119
|
},
|
|
120
120
|
}, async ({ page_id }) => {
|
|
121
|
-
const result = await wp.request(`/page/${page_id}`);
|
|
121
|
+
const result = await wp.request(`/page/get/${page_id}`);
|
|
122
122
|
return {
|
|
123
123
|
content: [
|
|
124
124
|
{ type: "text", text: JSON.stringify(result) },
|
|
125
125
|
],
|
|
126
126
|
};
|
|
127
127
|
});
|
|
128
|
-
server.registerTool("
|
|
128
|
+
server.registerTool("diviops_page_get_layout", {
|
|
129
129
|
description: "Get the parsed block tree for a page. Returns slim targeting metadata by default (block names, admin labels, text previews, auto_index). Use full: true for complete attrs (warning: can be very large on complex pages).",
|
|
130
130
|
inputSchema: {
|
|
131
131
|
page_id: z.number().describe("WordPress post/page ID"),
|
|
@@ -136,7 +136,7 @@ server.registerTool("diviops_get_page_layout", {
|
|
|
136
136
|
.describe("Include full block attrs and raw content (default: false for slim mode)"),
|
|
137
137
|
},
|
|
138
138
|
}, async ({ page_id, full }) => {
|
|
139
|
-
const result = await wp.request(`/page/${page_id}
|
|
139
|
+
const result = await wp.request(`/page/get-layout/${page_id}`, {
|
|
140
140
|
params: full ? { full: "true" } : {},
|
|
141
141
|
});
|
|
142
142
|
return {
|
|
@@ -145,17 +145,17 @@ server.registerTool("diviops_get_page_layout", {
|
|
|
145
145
|
],
|
|
146
146
|
};
|
|
147
147
|
});
|
|
148
|
-
server.registerTool("
|
|
148
|
+
server.registerTool("diviops_schema_list_modules", {
|
|
149
149
|
description: "List all available Divi modules (block types) with their names, titles, and categories. Use this to discover what modules can be used in layouts.",
|
|
150
150
|
}, async () => {
|
|
151
|
-
const result = await wp.request("/modules");
|
|
151
|
+
const result = await wp.request("/schema/modules");
|
|
152
152
|
return {
|
|
153
153
|
content: [
|
|
154
154
|
{ type: "text", text: JSON.stringify(result) },
|
|
155
155
|
],
|
|
156
156
|
};
|
|
157
157
|
});
|
|
158
|
-
server.registerTool("
|
|
158
|
+
server.registerTool("diviops_schema_get_module", {
|
|
159
159
|
description: "Get the attribute schema for a Divi module. Returns optimized schema by default (~70% smaller) with content-relevant fields only. Use raw: true for the full schema including CSS selectors and VB metadata.",
|
|
160
160
|
inputSchema: {
|
|
161
161
|
module_name: z
|
|
@@ -168,7 +168,7 @@ server.registerTool("diviops_get_module_schema", {
|
|
|
168
168
|
.describe("Return full schema including CSS selectors and VB metadata"),
|
|
169
169
|
},
|
|
170
170
|
}, async ({ module_name, raw }) => {
|
|
171
|
-
const result = await wp.request(`/module/${encodeURIComponent(module_name)}`);
|
|
171
|
+
const result = await wp.request(`/schema/module/${encodeURIComponent(module_name)}`);
|
|
172
172
|
const output = raw ? result : optimizeSchema(result);
|
|
173
173
|
return {
|
|
174
174
|
content: [
|
|
@@ -176,28 +176,28 @@ server.registerTool("diviops_get_module_schema", {
|
|
|
176
176
|
],
|
|
177
177
|
};
|
|
178
178
|
});
|
|
179
|
-
server.registerTool("
|
|
179
|
+
server.registerTool("diviops_schema_get_settings", {
|
|
180
180
|
description: "Get Divi site settings including theme options, site info, and builder version. Useful for understanding the site context before generating content.",
|
|
181
181
|
}, async () => {
|
|
182
|
-
const result = await wp.request("/settings");
|
|
182
|
+
const result = await wp.request("/schema/settings");
|
|
183
183
|
return {
|
|
184
184
|
content: [
|
|
185
185
|
{ type: "text", text: JSON.stringify(result) },
|
|
186
186
|
],
|
|
187
187
|
};
|
|
188
188
|
});
|
|
189
|
-
server.registerTool("
|
|
189
|
+
server.registerTool("diviops_global_color_list", {
|
|
190
190
|
description: "Get the global color palette defined in Divi. Returns all global colors that can be referenced by modules.",
|
|
191
191
|
}, async () => {
|
|
192
|
-
const result = await wp.request("/global-
|
|
192
|
+
const result = await wp.request("/global-color/list");
|
|
193
193
|
return {
|
|
194
194
|
content: [
|
|
195
195
|
{ type: "text", text: JSON.stringify(result) },
|
|
196
196
|
],
|
|
197
197
|
};
|
|
198
198
|
});
|
|
199
|
-
server.registerTool("
|
|
200
|
-
description: "Add a new global color to Divi's palette.
|
|
199
|
+
server.registerTool("diviops_global_color_create", {
|
|
200
|
+
description: "Add a new global color to Divi's palette. The plugin mints a fresh `gcid-<uuid>` ID (the server forwards the color entry without an id and the WP-side handler generates one) and writes to the et_global_data option in the canonical Divi shape `{color, folder, label, lastUpdated, status, usedInPosts}`. The color appears in the VB color picker after save and can be referenced via `$variable({type:color,value:{name:gcid-...}})$` tokens. Note: Divi's AI Agent bundle has a Zod schema gap that drops `label` on its own writes — our PHP path goes around that bug by writing directly to the option. CONCURRENCY: this is a read-modify-write on a single WP option with no conflict detection. If a Visual Builder session holds stale global data, its next save can clobber colors written here in the interim. Coordinate writes when VB sessions are active, or have the user reload VB after MCP color writes.",
|
|
201
201
|
inputSchema: {
|
|
202
202
|
color: z
|
|
203
203
|
.string()
|
|
@@ -224,18 +224,18 @@ server.registerTool("diviops_add_global_color", {
|
|
|
224
224
|
colorEntry.folder = folder;
|
|
225
225
|
if (status)
|
|
226
226
|
colorEntry.status = status;
|
|
227
|
-
const result = await wp.request("/global-
|
|
227
|
+
const result = await wp.request("/global-color/upsert", {
|
|
228
228
|
method: "POST",
|
|
229
229
|
body: { colors: [colorEntry], mode: "merge" },
|
|
230
230
|
});
|
|
231
231
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
232
232
|
});
|
|
233
|
-
server.registerTool("
|
|
234
|
-
description: "Update an existing global color by gcid. Only provided fields are updated; omitted fields are preserved. The lastUpdated timestamp is bumped on every write. Use
|
|
233
|
+
server.registerTool("diviops_global_color_update", {
|
|
234
|
+
description: "Update an existing global color by gcid. Only provided fields are updated; omitted fields are preserved. The lastUpdated timestamp is bumped on every write. Use diviops_global_color_list first to find the gcid for a color. CONCURRENCY: same VB-session race caveat as diviops_global_color_create — the write is read-modify-write on a single WP option, so an active VB session's next save can clobber this update.",
|
|
235
235
|
inputSchema: {
|
|
236
236
|
gcid: z
|
|
237
237
|
.string()
|
|
238
|
-
.describe('Global color ID, e.g. "gcid-abc123..." (must start with "gcid-"). Get from
|
|
238
|
+
.describe('Global color ID, e.g. "gcid-abc123..." (must start with "gcid-"). Get from diviops_global_color_list.'),
|
|
239
239
|
color: z
|
|
240
240
|
.string()
|
|
241
241
|
.optional()
|
|
@@ -263,14 +263,14 @@ server.registerTool("diviops_update_global_color", {
|
|
|
263
263
|
colorEntry.folder = folder;
|
|
264
264
|
if (status)
|
|
265
265
|
colorEntry.status = status;
|
|
266
|
-
const result = await wp.request("/global-
|
|
266
|
+
const result = await wp.request("/global-color/upsert", {
|
|
267
267
|
method: "POST",
|
|
268
268
|
body: { colors: [colorEntry], mode: "merge" },
|
|
269
269
|
});
|
|
270
270
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
271
271
|
});
|
|
272
|
-
server.registerTool("
|
|
273
|
-
description: "Delete a global color from the registry by gcid. Refuses by default if the color is tracked as referenced by any post (per Divi's `usedInPosts` index — pass `force: true` to delete anyway; orphan refs will render as invalid CSS until pages are re-saved through VB). Always refuses to delete the 5 customizer-bound defaults (gcid-primary-color, gcid-secondary-color, gcid-heading-color, gcid-body-color, gcid-link-color) regardless of force — those must be edited via WP Customizer. CONCURRENCY: same VB-session race caveat as
|
|
272
|
+
server.registerTool("diviops_global_color_delete", {
|
|
273
|
+
description: "Delete a global color from the registry by gcid. Refuses by default if the color is tracked as referenced by any post (per Divi's `usedInPosts` index — pass `force: true` to delete anyway; orphan refs will render as invalid CSS until pages are re-saved through VB). Always refuses to delete the 5 customizer-bound defaults (gcid-primary-color, gcid-secondary-color, gcid-heading-color, gcid-body-color, gcid-link-color) regardless of force — those must be edited via WP Customizer. CONCURRENCY: same VB-session race caveat as diviops_global_color_create — an active VB session's next save can re-introduce a color we just deleted if the session held stale data.",
|
|
274
274
|
inputSchema: {
|
|
275
275
|
gcid: z
|
|
276
276
|
.string()
|
|
@@ -285,23 +285,23 @@ server.registerTool("diviops_delete_global_color", {
|
|
|
285
285
|
const body = { gcid };
|
|
286
286
|
if (force)
|
|
287
287
|
body.force = true;
|
|
288
|
-
const result = await wp.request("/global-color
|
|
288
|
+
const result = await wp.request("/global-color/delete", {
|
|
289
289
|
method: "POST",
|
|
290
290
|
body,
|
|
291
291
|
});
|
|
292
292
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
293
293
|
});
|
|
294
|
-
server.registerTool("
|
|
294
|
+
server.registerTool("diviops_global_font_list", {
|
|
295
295
|
description: "Get the global font definitions from Divi settings.",
|
|
296
296
|
}, async () => {
|
|
297
|
-
const result = await wp.request("/global-
|
|
297
|
+
const result = await wp.request("/global-font/list");
|
|
298
298
|
return {
|
|
299
299
|
content: [
|
|
300
300
|
{ type: "text", text: JSON.stringify(result) },
|
|
301
301
|
],
|
|
302
302
|
};
|
|
303
303
|
});
|
|
304
|
-
server.registerTool("
|
|
304
|
+
server.registerTool("diviops_meta_find_icon", {
|
|
305
305
|
description: "Search for icons by keyword. Returns matching icons with unicode, type (fa/divi), and weight. Use the returned unicode/type/weight in Blurb icon or Icon module attributes.",
|
|
306
306
|
inputSchema: {
|
|
307
307
|
query: z
|
|
@@ -319,7 +319,7 @@ server.registerTool("diviops_find_icon", {
|
|
|
319
319
|
.describe("Max results (default 10, max 50)"),
|
|
320
320
|
},
|
|
321
321
|
}, async ({ query, type, limit }) => {
|
|
322
|
-
const result = await wp.request(`/
|
|
322
|
+
const result = await wp.request(`/meta/find-icon?q=${encodeURIComponent(query)}&type=${type ?? "all"}&limit=${limit ?? 10}`);
|
|
323
323
|
return {
|
|
324
324
|
content: [
|
|
325
325
|
{ type: "text", text: JSON.stringify(result) },
|
|
@@ -327,7 +327,7 @@ server.registerTool("diviops_find_icon", {
|
|
|
327
327
|
};
|
|
328
328
|
});
|
|
329
329
|
// ── Write Tools ──────────────────────────────────────────────────────
|
|
330
|
-
server.registerTool("
|
|
330
|
+
server.registerTool("diviops_page_update_content", {
|
|
331
331
|
description: "Update the content of a page with Divi block markup. The content should be valid WordPress block markup using divi/* blocks. IMPORTANT: This overwrites the entire page content.",
|
|
332
332
|
inputSchema: {
|
|
333
333
|
page_id: z.number().describe("WordPress post/page ID to update"),
|
|
@@ -338,8 +338,8 @@ server.registerTool("diviops_update_page_content", {
|
|
|
338
338
|
}, async ({ page_id, content }) => {
|
|
339
339
|
const hits = findForeignVarRefs(content, "content");
|
|
340
340
|
if (hits.length > 0)
|
|
341
|
-
return isolationErrorResult("
|
|
342
|
-
const result = await wp.request(`/page/${page_id}
|
|
341
|
+
return isolationErrorResult("diviops_page_update_content", hits);
|
|
342
|
+
const result = await wp.request(`/page/update-content/${page_id}`, {
|
|
343
343
|
method: "POST",
|
|
344
344
|
body: { content },
|
|
345
345
|
});
|
|
@@ -371,7 +371,7 @@ server.registerTool("diviops_validate_blocks", {
|
|
|
371
371
|
content: z.string().describe("Divi block markup to validate"),
|
|
372
372
|
},
|
|
373
373
|
}, async ({ content }) => {
|
|
374
|
-
const result = await wp.request("/validate", {
|
|
374
|
+
const result = await wp.request("/validate/blocks", {
|
|
375
375
|
method: "POST",
|
|
376
376
|
body: { content },
|
|
377
377
|
});
|
|
@@ -381,7 +381,7 @@ server.registerTool("diviops_validate_blocks", {
|
|
|
381
381
|
],
|
|
382
382
|
};
|
|
383
383
|
});
|
|
384
|
-
server.registerTool("
|
|
384
|
+
server.registerTool("diviops_section_append", {
|
|
385
385
|
description: "Append a Divi section to an existing page without overwriting other content. Use this to incrementally build pages.",
|
|
386
386
|
inputSchema: {
|
|
387
387
|
page_id: z.number().describe("WordPress post/page ID"),
|
|
@@ -397,8 +397,8 @@ server.registerTool("diviops_append_section", {
|
|
|
397
397
|
}, async ({ page_id, content, position }) => {
|
|
398
398
|
const hits = findForeignVarRefs(content, "content");
|
|
399
399
|
if (hits.length > 0)
|
|
400
|
-
return isolationErrorResult("
|
|
401
|
-
const result = await wp.request(`/
|
|
400
|
+
return isolationErrorResult("diviops_section_append", hits);
|
|
401
|
+
const result = await wp.request(`/section/append/${page_id}`, {
|
|
402
402
|
method: "POST",
|
|
403
403
|
body: { content, position: position ?? "end" },
|
|
404
404
|
});
|
|
@@ -408,7 +408,7 @@ server.registerTool("diviops_append_section", {
|
|
|
408
408
|
],
|
|
409
409
|
};
|
|
410
410
|
});
|
|
411
|
-
server.registerTool("
|
|
411
|
+
server.registerTool("diviops_section_replace", {
|
|
412
412
|
description: "Replace a section on a page. Target by admin label OR text content. Use occurrence when multiple sections match.",
|
|
413
413
|
inputSchema: {
|
|
414
414
|
page_id: z.number().describe("WordPress post/page ID"),
|
|
@@ -434,13 +434,13 @@ server.registerTool("diviops_replace_section", {
|
|
|
434
434
|
}, async ({ page_id, label, match_text, content, occurrence }) => {
|
|
435
435
|
const hits = findForeignVarRefs(content, "content");
|
|
436
436
|
if (hits.length > 0)
|
|
437
|
-
return isolationErrorResult("
|
|
437
|
+
return isolationErrorResult("diviops_section_replace", hits);
|
|
438
438
|
const body = { content, occurrence };
|
|
439
439
|
if (label)
|
|
440
440
|
body.label = label;
|
|
441
441
|
if (match_text)
|
|
442
442
|
body.match_text = match_text;
|
|
443
|
-
const result = await wp.request(`/
|
|
443
|
+
const result = await wp.request(`/section/replace/${page_id}`, {
|
|
444
444
|
method: "POST",
|
|
445
445
|
body,
|
|
446
446
|
});
|
|
@@ -450,7 +450,7 @@ server.registerTool("diviops_replace_section", {
|
|
|
450
450
|
],
|
|
451
451
|
};
|
|
452
452
|
});
|
|
453
|
-
server.registerTool("
|
|
453
|
+
server.registerTool("diviops_section_remove", {
|
|
454
454
|
description: "Remove a section from a page. Target by admin label OR text content. Use occurrence when multiple sections match.",
|
|
455
455
|
inputSchema: {
|
|
456
456
|
page_id: z.number().describe("WordPress post/page ID"),
|
|
@@ -476,7 +476,7 @@ server.registerTool("diviops_remove_section", {
|
|
|
476
476
|
body.label = label;
|
|
477
477
|
if (match_text)
|
|
478
478
|
body.match_text = match_text;
|
|
479
|
-
const result = await wp.request(`/
|
|
479
|
+
const result = await wp.request(`/section/remove/${page_id}`, {
|
|
480
480
|
method: "POST",
|
|
481
481
|
body,
|
|
482
482
|
});
|
|
@@ -486,7 +486,7 @@ server.registerTool("diviops_remove_section", {
|
|
|
486
486
|
],
|
|
487
487
|
};
|
|
488
488
|
});
|
|
489
|
-
server.registerTool("
|
|
489
|
+
server.registerTool("diviops_section_get", {
|
|
490
490
|
description: "Get the raw block markup of a section. Target by admin label OR text content. Use occurrence when multiple sections match. Returns total_matches warning when duplicates exist.",
|
|
491
491
|
inputSchema: {
|
|
492
492
|
page_id: z.number().describe("WordPress post/page ID"),
|
|
@@ -513,15 +513,15 @@ server.registerTool("diviops_get_section", {
|
|
|
513
513
|
if (match_text)
|
|
514
514
|
params.match_text = match_text;
|
|
515
515
|
const qs = new URLSearchParams(params).toString();
|
|
516
|
-
const result = await wp.request(`/
|
|
516
|
+
const result = await wp.request(`/section/get/${page_id}?${qs}`);
|
|
517
517
|
return {
|
|
518
518
|
content: [
|
|
519
519
|
{ type: "text", text: JSON.stringify(result) },
|
|
520
520
|
],
|
|
521
521
|
};
|
|
522
522
|
});
|
|
523
|
-
server.registerTool("
|
|
524
|
-
description: 'Update specific attributes of a module. Target by auto_index (e.g. "text:5"), admin label, or text content. Uses dot notation for attribute paths. Example: {"content.decoration.headingFont.h2.font.desktop.value.color": "#ff0000"}. Priority: auto_index > label > match_text. Use occurrence with label when duplicates exist.',
|
|
523
|
+
server.registerTool("diviops_module_update", {
|
|
524
|
+
description: 'Update specific attributes of a module. Target by auto_index (e.g. "text:5"), admin label, or text content. Uses dot notation for attribute paths. Example: {"content.decoration.headingFont.h2.font.desktop.value.color": "#ff0000"}. For paths whose key segments contain literal dots — notably Composable Settings preset slots like groupPreset["title.decoration.spacing"] — escape the inner dots with `\\.` to keep the segment intact: {"groupPreset.title\\\\.decoration\\\\.spacing.presetId": ["uuid"]}. Priority: auto_index > label > match_text. Use occurrence with label when duplicates exist.',
|
|
525
525
|
inputSchema: {
|
|
526
526
|
page_id: z.number().describe("WordPress post/page ID"),
|
|
527
527
|
label: z
|
|
@@ -535,7 +535,7 @@ server.registerTool("diviops_update_module", {
|
|
|
535
535
|
auto_index: z
|
|
536
536
|
.string()
|
|
537
537
|
.optional()
|
|
538
|
-
.describe('Auto-index target in "type:N" format (e.g. "text:5", "icon:3"). Get from
|
|
538
|
+
.describe('Auto-index target in "type:N" format (e.g. "text:5", "icon:3"). Get from diviops_page_get_layout. Takes priority over label/match_text.'),
|
|
539
539
|
occurrence: z
|
|
540
540
|
.number()
|
|
541
541
|
.int()
|
|
@@ -550,7 +550,7 @@ server.registerTool("diviops_update_module", {
|
|
|
550
550
|
}, async ({ page_id, label, match_text, auto_index, occurrence, attrs }) => {
|
|
551
551
|
const hits = scanAttrsForForeignVarRefs(attrs);
|
|
552
552
|
if (hits.length > 0)
|
|
553
|
-
return isolationErrorResult("
|
|
553
|
+
return isolationErrorResult("diviops_module_update", hits);
|
|
554
554
|
const body = { attrs };
|
|
555
555
|
if (auto_index)
|
|
556
556
|
body.auto_index = auto_index;
|
|
@@ -560,7 +560,7 @@ server.registerTool("diviops_update_module", {
|
|
|
560
560
|
body.match_text = match_text;
|
|
561
561
|
if (occurrence > 1)
|
|
562
562
|
body.occurrence = occurrence;
|
|
563
|
-
const result = await wp.request(`/
|
|
563
|
+
const result = await wp.request(`/module/update/${page_id}`, {
|
|
564
564
|
method: "POST",
|
|
565
565
|
body,
|
|
566
566
|
});
|
|
@@ -570,7 +570,7 @@ server.registerTool("diviops_update_module", {
|
|
|
570
570
|
],
|
|
571
571
|
};
|
|
572
572
|
});
|
|
573
|
-
server.registerTool("
|
|
573
|
+
server.registerTool("diviops_module_move", {
|
|
574
574
|
description: 'Move a module to a new position on the page. Specify source and target blocks using auto_index (e.g. "text:3"), admin label, or text content. Position "before" or "after" the target. Works with any block type including sections, rows, and modules. Both blocks are found in the original content, so auto_index values refer to positions before the move.',
|
|
575
575
|
inputSchema: {
|
|
576
576
|
page_id: z.number().describe("WordPress post/page ID"),
|
|
@@ -634,7 +634,7 @@ server.registerTool("diviops_move_module", {
|
|
|
634
634
|
body.target_auto_index = target_auto_index;
|
|
635
635
|
if (target_occurrence > 1)
|
|
636
636
|
body.target_occurrence = target_occurrence;
|
|
637
|
-
const result = await wp.request(`/
|
|
637
|
+
const result = await wp.request(`/module/move/${page_id}`, {
|
|
638
638
|
method: "POST",
|
|
639
639
|
body,
|
|
640
640
|
});
|
|
@@ -644,8 +644,8 @@ server.registerTool("diviops_move_module", {
|
|
|
644
644
|
],
|
|
645
645
|
};
|
|
646
646
|
});
|
|
647
|
-
server.registerTool("
|
|
648
|
-
description: 'Lock a module so VB users cannot edit it. Sets attrs.locked = {desktop: {value: "on"}} per Divi\'s per-breakpoint convention (verified via VB-save probe). Locked modules render normally on frontend; only VB-side editing is gated. Same targeting pattern as
|
|
647
|
+
server.registerTool("diviops_module_lock", {
|
|
648
|
+
description: 'Lock a module so VB users cannot edit it. Sets attrs.locked = {desktop: {value: "on"}} per Divi\'s per-breakpoint convention (verified via VB-save probe). Locked modules render normally on frontend; only VB-side editing is gated. Same targeting pattern as diviops_module_update — pick one of label / match_text / auto_index. Use diviops_module_unlock to reverse.',
|
|
649
649
|
inputSchema: {
|
|
650
650
|
page_id: z.number().describe("WordPress post/page ID"),
|
|
651
651
|
label: z.string().optional().describe("Admin label of the module to lock (exact match)"),
|
|
@@ -663,11 +663,11 @@ server.registerTool("diviops_lock_module", {
|
|
|
663
663
|
body.auto_index = auto_index;
|
|
664
664
|
if (occurrence && occurrence > 1)
|
|
665
665
|
body.occurrence = occurrence;
|
|
666
|
-
const result = await wp.request(`/
|
|
666
|
+
const result = await wp.request(`/module/lock/${page_id}`, { method: "POST", body });
|
|
667
667
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
668
668
|
});
|
|
669
|
-
server.registerTool("
|
|
670
|
-
description: "Unlock a module by removing attrs.locked entirely. Matches Divi VB's convention: unlocked = attribute absent (NOT {value: 'off'}) — VB doesn't write a falsy value on unlock, it removes the field. Same targeting pattern as
|
|
669
|
+
server.registerTool("diviops_module_unlock", {
|
|
670
|
+
description: "Unlock a module by removing attrs.locked entirely. Matches Divi VB's convention: unlocked = attribute absent (NOT {value: 'off'}) — VB doesn't write a falsy value on unlock, it removes the field. Same targeting pattern as diviops_module_lock.",
|
|
671
671
|
inputSchema: {
|
|
672
672
|
page_id: z.number().describe("WordPress post/page ID"),
|
|
673
673
|
label: z.string().optional().describe("Admin label of the module to unlock (exact match)"),
|
|
@@ -685,11 +685,11 @@ server.registerTool("diviops_unlock_module", {
|
|
|
685
685
|
body.auto_index = auto_index;
|
|
686
686
|
if (occurrence && occurrence > 1)
|
|
687
687
|
body.occurrence = occurrence;
|
|
688
|
-
const result = await wp.request(`/
|
|
688
|
+
const result = await wp.request(`/module/unlock/${page_id}`, { method: "POST", body });
|
|
689
689
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
690
690
|
});
|
|
691
|
-
server.registerTool("
|
|
692
|
-
description: 'Clone a module by deep-copying its block JSON and inserting it next to the source within the same parent container. Position controls before/after placement (default "after"). Module IDs are reassigned by Divi at render time from the block tree position, so the clone gets fresh IDs automatically. Same targeting pattern as
|
|
691
|
+
server.registerTool("diviops_module_clone", {
|
|
692
|
+
description: 'Clone a module by deep-copying its block JSON and inserting it next to the source within the same parent container. Position controls before/after placement (default "after"). Module IDs are reassigned by Divi at render time from the block tree position, so the clone gets fresh IDs automatically. Same targeting pattern as diviops_module_lock.',
|
|
693
693
|
inputSchema: {
|
|
694
694
|
page_id: z.number().describe("WordPress post/page ID"),
|
|
695
695
|
label: z.string().optional().describe("Admin label of the module to clone (exact match)"),
|
|
@@ -710,10 +710,10 @@ server.registerTool("diviops_clone_module", {
|
|
|
710
710
|
body.occurrence = occurrence;
|
|
711
711
|
if (position)
|
|
712
712
|
body.position = position;
|
|
713
|
-
const result = await wp.request(`/
|
|
713
|
+
const result = await wp.request(`/module/clone/${page_id}`, { method: "POST", body });
|
|
714
714
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
715
715
|
});
|
|
716
|
-
server.registerTool("
|
|
716
|
+
server.registerTool("diviops_page_create", {
|
|
717
717
|
description: "Create a new WordPress page, optionally with Divi block content.",
|
|
718
718
|
inputSchema: {
|
|
719
719
|
title: z.string().describe("Page title"),
|
|
@@ -732,7 +732,7 @@ server.registerTool("diviops_create_page", {
|
|
|
732
732
|
if (content) {
|
|
733
733
|
const hits = findForeignVarRefs(content, "content");
|
|
734
734
|
if (hits.length > 0)
|
|
735
|
-
return isolationErrorResult("
|
|
735
|
+
return isolationErrorResult("diviops_page_create", hits);
|
|
736
736
|
}
|
|
737
737
|
const result = await wp.request("/page/create", {
|
|
738
738
|
method: "POST",
|
|
@@ -748,7 +748,7 @@ server.registerTool("diviops_create_page", {
|
|
|
748
748
|
server.registerTool("diviops_preset_audit", {
|
|
749
749
|
description: "Audit all Divi presets (module + group). Each entry reports `block_ref_count` (page-content refs via modulePreset / groupPreset block markup), `group_ref_count` (in-registry chain refs from other presets — module presets via top-level `groupPresets.<slot>.presetId`, group presets via `attrs.groupPreset.<slot>.presetId`), and `referenced` (true if either > 0). Group presets that are chain-referenced also expose `referenced_by_presets` (UUIDs of the presets that wire them in — typically module presets, but type-agnostic). Use this before deleting — orphan-cleanup based only on page refs would silently wipe load-bearing chain-wired group presets (font, border, box-shadow, spacing, button). Also reports `orphan_default_pointers`: per-bucket `default` pointers that reference a UUID no longer present in `items[]` (caused by past unsafe deletes). Render-safe but blocks Divi's lazy recreate-on-VB-use path; clear via diviops_preset_set_default with unset=true on the affected module/group.",
|
|
750
750
|
}, async () => {
|
|
751
|
-
const result = await wp.request("/preset
|
|
751
|
+
const result = await wp.request("/preset/audit");
|
|
752
752
|
return {
|
|
753
753
|
content: [
|
|
754
754
|
{ type: "text", text: JSON.stringify(result) },
|
|
@@ -791,7 +791,7 @@ server.registerTool("diviops_preset_cleanup", {
|
|
|
791
791
|
body.prefix = prefix;
|
|
792
792
|
if (action === "remove_orphans" && scope)
|
|
793
793
|
body.scope = scope;
|
|
794
|
-
const result = await wp.request("/preset
|
|
794
|
+
const result = await wp.request("/preset/cleanup", {
|
|
795
795
|
method: "POST",
|
|
796
796
|
body,
|
|
797
797
|
});
|
|
@@ -824,7 +824,7 @@ server.registerTool("diviops_preset_update", {
|
|
|
824
824
|
body.attrs = attrs;
|
|
825
825
|
if (typeof priority === "number")
|
|
826
826
|
body.priority = priority;
|
|
827
|
-
const result = await wp.request("/preset
|
|
827
|
+
const result = await wp.request("/preset/update", {
|
|
828
828
|
method: "POST",
|
|
829
829
|
body,
|
|
830
830
|
});
|
|
@@ -847,7 +847,7 @@ server.registerTool("diviops_preset_delete", {
|
|
|
847
847
|
const body = { preset_id };
|
|
848
848
|
if (force !== undefined)
|
|
849
849
|
body.force = force;
|
|
850
|
-
const result = await wp.request("/preset
|
|
850
|
+
const result = await wp.request("/preset/delete", {
|
|
851
851
|
method: "POST",
|
|
852
852
|
body,
|
|
853
853
|
});
|
|
@@ -909,7 +909,7 @@ server.registerTool("diviops_preset_create", {
|
|
|
909
909
|
body.make_default = true;
|
|
910
910
|
if (typeof priority === "number")
|
|
911
911
|
body.priority = priority;
|
|
912
|
-
const result = await wp.request("/preset
|
|
912
|
+
const result = await wp.request("/preset/create", { method: "POST", body });
|
|
913
913
|
return {
|
|
914
914
|
content: [
|
|
915
915
|
{ type: "text", text: JSON.stringify(result) },
|
|
@@ -955,7 +955,7 @@ server.registerTool("diviops_preset_reassign", {
|
|
|
955
955
|
};
|
|
956
956
|
if (page_ids)
|
|
957
957
|
body.page_ids = page_ids;
|
|
958
|
-
const result = await wp.request("/preset
|
|
958
|
+
const result = await wp.request("/preset/reassign", {
|
|
959
959
|
method: "POST",
|
|
960
960
|
body,
|
|
961
961
|
});
|
|
@@ -968,7 +968,7 @@ server.registerTool("diviops_preset_reassign", {
|
|
|
968
968
|
server.registerTool("diviops_preset_scan_orphans", {
|
|
969
969
|
description: "Scan page content for modulePreset UUIDs that are not in the D5 registry. Categorizes as dangling orphans (preset was deleted, reference remains) or D4-legacy candidates (preset exists in the legacy builder_global_presets_ng option but not in D5). Use before diviops_preset_reassign to identify stale UUIDs for consolidation.",
|
|
970
970
|
}, async () => {
|
|
971
|
-
const result = await wp.request("/preset
|
|
971
|
+
const result = await wp.request("/preset/scan-orphans");
|
|
972
972
|
return {
|
|
973
973
|
content: [
|
|
974
974
|
{ type: "text", text: JSON.stringify(result) },
|
|
@@ -1005,7 +1005,7 @@ server.registerTool("diviops_preset_set_default", {
|
|
|
1005
1005
|
body.module = module;
|
|
1006
1006
|
if (unset)
|
|
1007
1007
|
body.unset = true;
|
|
1008
|
-
const result = await wp.request("/preset
|
|
1008
|
+
const result = await wp.request("/preset/set-default", {
|
|
1009
1009
|
method: "POST",
|
|
1010
1010
|
body,
|
|
1011
1011
|
});
|
|
@@ -1016,7 +1016,7 @@ server.registerTool("diviops_preset_set_default", {
|
|
|
1016
1016
|
};
|
|
1017
1017
|
});
|
|
1018
1018
|
// ── Library Tools ───────────────────────────────────────────────────
|
|
1019
|
-
server.registerTool("
|
|
1019
|
+
server.registerTool("diviops_library_list", {
|
|
1020
1020
|
description: "List saved Divi Library items. Filter by layout_type (section, row, module) and scope (global, non_global).",
|
|
1021
1021
|
inputSchema: {
|
|
1022
1022
|
layout_type: z
|
|
@@ -1041,27 +1041,27 @@ server.registerTool("diviops_list_library", {
|
|
|
1041
1041
|
params.scope = scope;
|
|
1042
1042
|
if (per_page)
|
|
1043
1043
|
params.per_page = String(per_page);
|
|
1044
|
-
const result = await wp.request("/library", { params });
|
|
1044
|
+
const result = await wp.request("/library/items", { params });
|
|
1045
1045
|
return {
|
|
1046
1046
|
content: [
|
|
1047
1047
|
{ type: "text", text: JSON.stringify(result) },
|
|
1048
1048
|
],
|
|
1049
1049
|
};
|
|
1050
1050
|
});
|
|
1051
|
-
server.registerTool("
|
|
1052
|
-
description: "Get a Divi Library item's content by ID. Returns the raw block markup that can be used with
|
|
1051
|
+
server.registerTool("diviops_library_get", {
|
|
1052
|
+
description: "Get a Divi Library item's content by ID. Returns the raw block markup that can be used with diviops_section_append or diviops_page_update_content.",
|
|
1053
1053
|
inputSchema: {
|
|
1054
1054
|
item_id: z.number().describe("Library item ID"),
|
|
1055
1055
|
},
|
|
1056
1056
|
}, async ({ item_id }) => {
|
|
1057
|
-
const result = await wp.request(`/library/${item_id}`);
|
|
1057
|
+
const result = await wp.request(`/library/item/${item_id}`);
|
|
1058
1058
|
return {
|
|
1059
1059
|
content: [
|
|
1060
1060
|
{ type: "text", text: JSON.stringify(result) },
|
|
1061
1061
|
],
|
|
1062
1062
|
};
|
|
1063
1063
|
});
|
|
1064
|
-
server.registerTool("
|
|
1064
|
+
server.registerTool("diviops_library_save", {
|
|
1065
1065
|
description: 'Save Divi block markup to the Divi Library for reuse. Saved items appear in the VB\'s "Add From Library" panel.',
|
|
1066
1066
|
inputSchema: {
|
|
1067
1067
|
title: z.string().describe("Display name for the library item"),
|
|
@@ -1096,7 +1096,7 @@ server.registerTool("diviops_save_to_library", {
|
|
|
1096
1096
|
};
|
|
1097
1097
|
});
|
|
1098
1098
|
// ── Theme Builder Tools ─────────────────────────────────────────────
|
|
1099
|
-
server.registerTool("
|
|
1099
|
+
server.registerTool("diviops_tb_template_list", {
|
|
1100
1100
|
description: "List all Theme Builder templates with their conditions, layout IDs, and enabled status. Shows which template applies to which pages/post types.",
|
|
1101
1101
|
inputSchema: {
|
|
1102
1102
|
per_page: z
|
|
@@ -1113,36 +1113,36 @@ server.registerTool("diviops_list_tb_templates", {
|
|
|
1113
1113
|
params.per_page = String(per_page);
|
|
1114
1114
|
if (page)
|
|
1115
1115
|
params.page = String(page);
|
|
1116
|
-
const result = await wp.request("/theme-builder/
|
|
1116
|
+
const result = await wp.request("/theme-builder/template/list", { params });
|
|
1117
1117
|
return {
|
|
1118
1118
|
content: [
|
|
1119
1119
|
{ type: "text", text: JSON.stringify(result) },
|
|
1120
1120
|
],
|
|
1121
1121
|
};
|
|
1122
1122
|
});
|
|
1123
|
-
server.registerTool("
|
|
1124
|
-
description: "Get a Theme Builder layout's block markup content (header, body, or footer). Use the layout IDs from
|
|
1123
|
+
server.registerTool("diviops_tb_layout_get", {
|
|
1124
|
+
description: "Get a Theme Builder layout's block markup content (header, body, or footer). Use the layout IDs from diviops_tb_template_list.",
|
|
1125
1125
|
inputSchema: {
|
|
1126
1126
|
layout_id: z
|
|
1127
1127
|
.number()
|
|
1128
1128
|
.describe("Layout post ID (from template header_layout_id, body_layout_id, or footer_layout_id)"),
|
|
1129
1129
|
},
|
|
1130
1130
|
}, async ({ layout_id }) => {
|
|
1131
|
-
const result = await wp.request(`/theme-builder/layout/${layout_id}`);
|
|
1131
|
+
const result = await wp.request(`/theme-builder/layout/get/${layout_id}`);
|
|
1132
1132
|
return {
|
|
1133
1133
|
content: [
|
|
1134
1134
|
{ type: "text", text: JSON.stringify(result) },
|
|
1135
1135
|
],
|
|
1136
1136
|
};
|
|
1137
1137
|
});
|
|
1138
|
-
server.registerTool("
|
|
1138
|
+
server.registerTool("diviops_tb_layout_update", {
|
|
1139
1139
|
description: "Update a Theme Builder layout's block markup (header, body, or footer). Replaces the full content.",
|
|
1140
1140
|
inputSchema: {
|
|
1141
1141
|
layout_id: z.number().describe("Layout post ID to update"),
|
|
1142
1142
|
content: z.string().describe("New block markup content"),
|
|
1143
1143
|
},
|
|
1144
1144
|
}, async ({ layout_id, content }) => {
|
|
1145
|
-
const result = await wp.request(`/theme-builder/layout/${layout_id}`, {
|
|
1145
|
+
const result = await wp.request(`/theme-builder/layout/update/${layout_id}`, {
|
|
1146
1146
|
method: "PUT",
|
|
1147
1147
|
body: { content },
|
|
1148
1148
|
});
|
|
@@ -1152,7 +1152,7 @@ server.registerTool("diviops_update_tb_layout", {
|
|
|
1152
1152
|
],
|
|
1153
1153
|
};
|
|
1154
1154
|
});
|
|
1155
|
-
server.registerTool("
|
|
1155
|
+
server.registerTool("diviops_tb_template_create", {
|
|
1156
1156
|
description: "Create a Theme Builder template with custom header and/or footer. Automatically creates layout posts, sets conditions, and links to Theme Builder.",
|
|
1157
1157
|
inputSchema: {
|
|
1158
1158
|
title: z.string().describe('Template name (e.g. "Landing Pages")'),
|
|
@@ -1171,7 +1171,7 @@ server.registerTool("diviops_create_tb_template", {
|
|
|
1171
1171
|
.describe("Footer block markup (empty = inherit from default template)"),
|
|
1172
1172
|
},
|
|
1173
1173
|
}, async ({ title, condition, header_content, footer_content }) => {
|
|
1174
|
-
const result = await wp.request("/theme-builder/template", {
|
|
1174
|
+
const result = await wp.request("/theme-builder/template/create", {
|
|
1175
1175
|
method: "POST",
|
|
1176
1176
|
body: { title, condition, header_content, footer_content },
|
|
1177
1177
|
});
|
|
@@ -1182,7 +1182,7 @@ server.registerTool("diviops_create_tb_template", {
|
|
|
1182
1182
|
};
|
|
1183
1183
|
});
|
|
1184
1184
|
// ── Canvas Tools ────────────────────────────────────────────────────
|
|
1185
|
-
server.registerTool("
|
|
1185
|
+
server.registerTool("diviops_canvas_create", {
|
|
1186
1186
|
description: "Create a canvas (off-canvas workspace) linked to a page. Used for popups, off-canvas menus, modals. Content uses standard Divi block markup.",
|
|
1187
1187
|
inputSchema: {
|
|
1188
1188
|
title: z
|
|
@@ -1226,7 +1226,7 @@ server.registerTool("diviops_create_canvas", {
|
|
|
1226
1226
|
],
|
|
1227
1227
|
};
|
|
1228
1228
|
});
|
|
1229
|
-
server.registerTool("
|
|
1229
|
+
server.registerTool("diviops_canvas_list", {
|
|
1230
1230
|
description: "List canvases (off-canvas workspaces). Filter by parent page or list all.",
|
|
1231
1231
|
inputSchema: {
|
|
1232
1232
|
parent_page_id: z
|
|
@@ -1248,29 +1248,29 @@ server.registerTool("diviops_list_canvases", {
|
|
|
1248
1248
|
params.parent_page_id = String(parent_page_id);
|
|
1249
1249
|
if (per_page)
|
|
1250
1250
|
params.per_page = String(per_page);
|
|
1251
|
-
const result = await wp.request("/
|
|
1251
|
+
const result = await wp.request("/canvas/list", { params });
|
|
1252
1252
|
return {
|
|
1253
1253
|
content: [
|
|
1254
1254
|
{ type: "text", text: JSON.stringify(result) },
|
|
1255
1255
|
],
|
|
1256
1256
|
};
|
|
1257
1257
|
});
|
|
1258
|
-
server.registerTool("
|
|
1258
|
+
server.registerTool("diviops_canvas_get", {
|
|
1259
1259
|
description: "Get a canvas's block content and metadata.",
|
|
1260
1260
|
inputSchema: {
|
|
1261
1261
|
canvas_post_id: z
|
|
1262
1262
|
.number()
|
|
1263
|
-
.describe("Canvas post ID (from
|
|
1263
|
+
.describe("Canvas post ID (from diviops_canvas_list)"),
|
|
1264
1264
|
},
|
|
1265
1265
|
}, async ({ canvas_post_id }) => {
|
|
1266
|
-
const result = await wp.request(`/canvas/${canvas_post_id}`);
|
|
1266
|
+
const result = await wp.request(`/canvas/get/${canvas_post_id}`);
|
|
1267
1267
|
return {
|
|
1268
1268
|
content: [
|
|
1269
1269
|
{ type: "text", text: JSON.stringify(result) },
|
|
1270
1270
|
],
|
|
1271
1271
|
};
|
|
1272
1272
|
});
|
|
1273
|
-
server.registerTool("
|
|
1273
|
+
server.registerTool("diviops_canvas_update", {
|
|
1274
1274
|
description: "Update a canvas's content and/or metadata. Content replaces the entire canvas.",
|
|
1275
1275
|
inputSchema: {
|
|
1276
1276
|
canvas_post_id: z.number().describe("Canvas post ID"),
|
|
@@ -1295,7 +1295,7 @@ server.registerTool("diviops_update_canvas", {
|
|
|
1295
1295
|
body.append_to_main = append_to_main;
|
|
1296
1296
|
if (z_index !== undefined)
|
|
1297
1297
|
body.z_index = z_index;
|
|
1298
|
-
const result = await wp.request(`/canvas/${canvas_post_id}`, {
|
|
1298
|
+
const result = await wp.request(`/canvas/update/${canvas_post_id}`, {
|
|
1299
1299
|
method: "POST",
|
|
1300
1300
|
body,
|
|
1301
1301
|
});
|
|
@@ -1305,14 +1305,14 @@ server.registerTool("diviops_update_canvas", {
|
|
|
1305
1305
|
],
|
|
1306
1306
|
};
|
|
1307
1307
|
});
|
|
1308
|
-
server.registerTool("
|
|
1308
|
+
server.registerTool("diviops_canvas_delete", {
|
|
1309
1309
|
description: "Delete a canvas. This permanently removes the canvas post.",
|
|
1310
1310
|
inputSchema: {
|
|
1311
1311
|
canvas_post_id: z.number().describe("Canvas post ID to delete"),
|
|
1312
1312
|
},
|
|
1313
1313
|
}, async ({ canvas_post_id }) => {
|
|
1314
|
-
const result = await wp.request(`/canvas/${canvas_post_id}`, {
|
|
1315
|
-
method: "
|
|
1314
|
+
const result = await wp.request(`/canvas/delete/${canvas_post_id}`, {
|
|
1315
|
+
method: "POST",
|
|
1316
1316
|
});
|
|
1317
1317
|
return {
|
|
1318
1318
|
content: [
|
|
@@ -1321,8 +1321,8 @@ server.registerTool("diviops_delete_canvas", {
|
|
|
1321
1321
|
};
|
|
1322
1322
|
});
|
|
1323
1323
|
// ── WP-CLI ──────────────────────────────────────────────────────────
|
|
1324
|
-
server.registerTool("
|
|
1325
|
-
description: "Run a WP-CLI command on the WordPress site. Requires WP_PATH env var (LOCAL_SITE_ID auto-detected from Local by Flywheel), or WP_CLI_CMD for containerized wrappers. Commands validated against a safety allowlist. Default tier covers read ops across options/posts/post-types/taxonomies/users/info/core/db, non-destructive writes (post/term create+update, post meta read/write, cache/rewrite/transient flush), ACF schema ops (export/import/list/get
|
|
1324
|
+
server.registerTool("diviops_meta_wp_cli", {
|
|
1325
|
+
description: "Run a WP-CLI command on the WordPress site. Requires WP_PATH env var (LOCAL_SITE_ID auto-detected from Local by Flywheel), or WP_CLI_CMD for containerized wrappers. Commands validated against a safety allowlist. Default tier covers read ops across options/posts/post-types/taxonomies/users/info/core/db, non-destructive writes (post/term create+update, post meta read/write, cache/rewrite/transient flush), ACF/SCF schema ops (`acf export/import/field-group list/get` plus SCF 6.8.4+ `scf json {status,sync,import,export}` and the `acf json …` aliases), and WXR export. Extended tier (requires DIVIOPS_WP_CLI_ALLOW env var) adds destructive or bulk-modifying ops: option update, post/post meta/term delete, search-replace, import, plugin activate/deactivate, eval-file. Filesystem-touching commands (`wp export`, `acf export/import`, `scf|acf json export/import`) are additionally constrained: path arguments must resolve under a safe root (defaults to `<WP_PATH>/.diviops-tmp/`, overridable via DIVIOPS_WP_CLI_SAFE_FS_ROOT, disable via DIVIOPS_WP_CLI_UNSAFE_FS=1); `wp export` and `scf json export` require an explicit `--dir=<path>` (or `--stdout`). In WP_CLI_CMD wrapper mode, DIVIOPS_WP_CLI_SAFE_FS_ROOT is required for FS-sensitive commands. Prefer the typed `diviops_scf_*` wrappers for SCF round-trips — they're easier to invoke and accept the same safe-root scoping. Use --format=json for structured output. Full allowlist + tier rationale + filesystem semantics in the MCP server README.",
|
|
1326
1326
|
inputSchema: {
|
|
1327
1327
|
command: z
|
|
1328
1328
|
.string()
|
|
@@ -1345,8 +1345,277 @@ server.registerTool("diviops_wp_cli", {
|
|
|
1345
1345
|
: `Error: ${result.error}\n${result.output}`;
|
|
1346
1346
|
return { content: [{ type: "text", text: output }] };
|
|
1347
1347
|
});
|
|
1348
|
+
// ── SCF (Secure Custom Fields / ACF) wrappers ───────────────────────
|
|
1349
|
+
//
|
|
1350
|
+
// Typed wrappers over SCF 6.8.4+'s `wp scf json {status,sync,import,export}`
|
|
1351
|
+
// CLI family (also reachable as `wp acf json …`). The plugin file at
|
|
1352
|
+
// wp-content/plugins/secure-custom-fields/src/CLI/JsonCommand.php is the
|
|
1353
|
+
// upstream source of truth for flag shapes — keep these wrappers aligned.
|
|
1354
|
+
function ensureWpCli() {
|
|
1355
|
+
if (!wpCli) {
|
|
1356
|
+
return {
|
|
1357
|
+
ok: false,
|
|
1358
|
+
text: "WP-CLI not configured. Set WP_PATH (Local by Flywheel auto-detect) " +
|
|
1359
|
+
"or WP_CLI_CMD (containerized wrappers) to enable SCF round-trip tools.",
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
return { ok: true };
|
|
1363
|
+
}
|
|
1364
|
+
function pushScfFlag(args, name, value) {
|
|
1365
|
+
if (!value)
|
|
1366
|
+
return;
|
|
1367
|
+
// Each `--name=value` becomes a single argv entry — execFile handles spaces
|
|
1368
|
+
// and quotes inside the value transparently. No string concatenation, no
|
|
1369
|
+
// parseCommand round-trip, so values like "Bob's Group" or filenames with
|
|
1370
|
+
// spaces flow through verbatim.
|
|
1371
|
+
args.push(`--${name}=${value}`);
|
|
1372
|
+
}
|
|
1373
|
+
server.registerTool("diviops_scf_status", {
|
|
1374
|
+
description: "Show SCF (Secure Custom Fields) sync status — how many field groups, post types, taxonomies, and options pages have JSON-on-disk newer than the database (or absent from DB). Read-only. Wraps `wp scf json status`. Requires SCF 6.8.4+ and WP_PATH or WP_CLI_CMD.",
|
|
1375
|
+
inputSchema: {
|
|
1376
|
+
type: z
|
|
1377
|
+
.enum(["field-group", "post-type", "taxonomy", "options-page"])
|
|
1378
|
+
.optional()
|
|
1379
|
+
.describe("Limit to a single item type. Defaults to all types. options-page requires ACF PRO."),
|
|
1380
|
+
detailed: z
|
|
1381
|
+
.boolean()
|
|
1382
|
+
.optional()
|
|
1383
|
+
.describe("List the individual pending items (key/title/type/action) instead of just counts."),
|
|
1384
|
+
},
|
|
1385
|
+
}, async ({ type, detailed }) => {
|
|
1386
|
+
const gate = ensureWpCli();
|
|
1387
|
+
if (!gate.ok) {
|
|
1388
|
+
return { content: [{ type: "text", text: gate.text }] };
|
|
1389
|
+
}
|
|
1390
|
+
const args = ["scf", "json", "status", "--format=json"];
|
|
1391
|
+
pushScfFlag(args, "type", type);
|
|
1392
|
+
if (detailed)
|
|
1393
|
+
args.push("--detailed");
|
|
1394
|
+
const result = await wpCli.runArgs(args);
|
|
1395
|
+
const output = result.success
|
|
1396
|
+
? result.output
|
|
1397
|
+
: `Error: ${result.error}\n${result.output}`;
|
|
1398
|
+
return { content: [{ type: "text", text: output }] };
|
|
1399
|
+
});
|
|
1400
|
+
server.registerTool("diviops_scf_export", {
|
|
1401
|
+
description: "Export SCF field groups, post types, taxonomies, and options pages as JSON — to a directory under the safe-root (`<WP_PATH>/.diviops-tmp/` by default, override via DIVIOPS_WP_CLI_SAFE_FS_ROOT) or to stdout. Wraps `wp scf json export`. Either `dir` or `stdout: true` is required. Filters can be combined; without filters, all items are exported. Note: SCF writes a fixed filename `acf-export-YYYY-MM-DD.json` inside `dir` — two exports on the same day silently overwrite. Copy/rename if you're archiving baselines.",
|
|
1402
|
+
inputSchema: {
|
|
1403
|
+
dir: z
|
|
1404
|
+
.string()
|
|
1405
|
+
.optional()
|
|
1406
|
+
.describe("Absolute output directory under the WP-CLI safe-root. Mutually exclusive with `stdout`. SCF writes a single `acf-export-YYYY-MM-DD.json` file inside this dir."),
|
|
1407
|
+
stdout: z
|
|
1408
|
+
.boolean()
|
|
1409
|
+
.optional()
|
|
1410
|
+
.describe("Print JSON to stdout instead of writing a file. Mutually exclusive with `dir`."),
|
|
1411
|
+
field_groups: z
|
|
1412
|
+
.string()
|
|
1413
|
+
.optional()
|
|
1414
|
+
.describe("Comma-separated field-group ACF keys (`group_abc123`) or admin titles (`My Field Group`). NOT WP post slugs — SCF matches against the def's `key` field or its `title` (case-insensitive). Use `diviops_scf_field_group_list` to discover keys (post_name column)."),
|
|
1415
|
+
post_types: z
|
|
1416
|
+
.string()
|
|
1417
|
+
.optional()
|
|
1418
|
+
.describe("Comma-separated SCF post-type def keys (`post_type_xxx`) or admin titles (`Programm`). IMPORTANT: this is the SCF def's identifier, NOT the registered post-type slug (`event`, `book`). The registered slug is what `wp post list` and REST URLs use, but SCF's filter matches against the def's `key` field or its `title`. To discover def keys, run `diviops_scf_export --stdout` (no filter) and inspect the top-level entries with `parent='post-type'`."),
|
|
1419
|
+
taxonomies: z
|
|
1420
|
+
.string()
|
|
1421
|
+
.optional()
|
|
1422
|
+
.describe("Comma-separated SCF taxonomy def keys (`taxonomy_xxx`) or admin titles. Same caveat as `post_types`: NOT the registered taxonomy slug — the SCF def's `key` or `title`. Discover via `diviops_scf_export --stdout`."),
|
|
1423
|
+
options_pages: z
|
|
1424
|
+
.string()
|
|
1425
|
+
.optional()
|
|
1426
|
+
.describe("Comma-separated options-page def keys or admin titles. Requires ACF PRO."),
|
|
1427
|
+
},
|
|
1428
|
+
}, async ({ dir, stdout, field_groups, post_types, taxonomies, options_pages }) => {
|
|
1429
|
+
const gate = ensureWpCli();
|
|
1430
|
+
if (!gate.ok) {
|
|
1431
|
+
return { content: [{ type: "text", text: gate.text }] };
|
|
1432
|
+
}
|
|
1433
|
+
if (!dir && !stdout) {
|
|
1434
|
+
return {
|
|
1435
|
+
content: [
|
|
1436
|
+
{
|
|
1437
|
+
type: "text",
|
|
1438
|
+
text: "Error: pass either `dir` (absolute path under DIVIOPS_WP_CLI_SAFE_FS_ROOT) or `stdout: true`.",
|
|
1439
|
+
},
|
|
1440
|
+
],
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
if (dir && stdout) {
|
|
1444
|
+
return {
|
|
1445
|
+
content: [
|
|
1446
|
+
{
|
|
1447
|
+
type: "text",
|
|
1448
|
+
text: "Error: `dir` and `stdout` are mutually exclusive — pick one.",
|
|
1449
|
+
},
|
|
1450
|
+
],
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
const args = ["scf", "json", "export"];
|
|
1454
|
+
if (stdout)
|
|
1455
|
+
args.push("--stdout");
|
|
1456
|
+
pushScfFlag(args, "dir", dir);
|
|
1457
|
+
pushScfFlag(args, "field-groups", field_groups);
|
|
1458
|
+
pushScfFlag(args, "post-types", post_types);
|
|
1459
|
+
pushScfFlag(args, "taxonomies", taxonomies);
|
|
1460
|
+
pushScfFlag(args, "options-pages", options_pages);
|
|
1461
|
+
const result = await wpCli.runArgs(args);
|
|
1462
|
+
const output = result.success
|
|
1463
|
+
? result.output
|
|
1464
|
+
: `Error: ${result.error}\n${result.output}`;
|
|
1465
|
+
return { content: [{ type: "text", text: output }] };
|
|
1466
|
+
});
|
|
1467
|
+
server.registerTool("diviops_scf_import", {
|
|
1468
|
+
description: "Import SCF field groups, post types, taxonomies, options pages from a JSON file. Mutates the database. File path must resolve under the safe-root (`<WP_PATH>/.diviops-tmp/` by default, override via DIVIOPS_WP_CLI_SAFE_FS_ROOT). Idempotent — existing items with matching keys are updated. Wraps `wp scf json import <file>`.",
|
|
1469
|
+
inputSchema: {
|
|
1470
|
+
file: z
|
|
1471
|
+
.string()
|
|
1472
|
+
.describe("Absolute path to the .json file to import. Must resolve under DIVIOPS_WP_CLI_SAFE_FS_ROOT."),
|
|
1473
|
+
},
|
|
1474
|
+
}, async ({ file }) => {
|
|
1475
|
+
const gate = ensureWpCli();
|
|
1476
|
+
if (!gate.ok) {
|
|
1477
|
+
return { content: [{ type: "text", text: gate.text }] };
|
|
1478
|
+
}
|
|
1479
|
+
const result = await wpCli.runArgs(["scf", "json", "import", file]);
|
|
1480
|
+
const output = result.success
|
|
1481
|
+
? result.output
|
|
1482
|
+
: `Error: ${result.error}\n${result.output}`;
|
|
1483
|
+
return { content: [{ type: "text", text: output }] };
|
|
1484
|
+
});
|
|
1485
|
+
server.registerTool("diviops_scf_sync", {
|
|
1486
|
+
description: "Apply pending JSON-on-disk SCF changes to the database. Reads JSON files from the theme/plugin acf-json directory and creates/updates DB entries. Defaults to `dry_run: true` for safety — caller must opt in to mutation. Wraps `wp scf json sync`.",
|
|
1487
|
+
inputSchema: {
|
|
1488
|
+
type: z
|
|
1489
|
+
.enum(["field-group", "post-type", "taxonomy", "options-page"])
|
|
1490
|
+
.optional()
|
|
1491
|
+
.describe("Limit sync to a single item type."),
|
|
1492
|
+
key: z
|
|
1493
|
+
.string()
|
|
1494
|
+
.optional()
|
|
1495
|
+
.describe("Sync only the item with this ACF key (e.g. `group_abc123`)."),
|
|
1496
|
+
dry_run: z
|
|
1497
|
+
.boolean()
|
|
1498
|
+
.optional()
|
|
1499
|
+
.default(true)
|
|
1500
|
+
.describe("Preview pending changes without mutating the database. Defaults to true. Pass `false` to commit."),
|
|
1501
|
+
},
|
|
1502
|
+
}, async ({ type, key, dry_run }) => {
|
|
1503
|
+
const gate = ensureWpCli();
|
|
1504
|
+
if (!gate.ok) {
|
|
1505
|
+
return { content: [{ type: "text", text: gate.text }] };
|
|
1506
|
+
}
|
|
1507
|
+
const args = ["scf", "json", "sync"];
|
|
1508
|
+
pushScfFlag(args, "type", type);
|
|
1509
|
+
pushScfFlag(args, "key", key);
|
|
1510
|
+
if (dry_run !== false)
|
|
1511
|
+
args.push("--dry-run");
|
|
1512
|
+
const result = await wpCli.runArgs(args);
|
|
1513
|
+
const output = result.success
|
|
1514
|
+
? result.output
|
|
1515
|
+
: `Error: ${result.error}\n${result.output}`;
|
|
1516
|
+
return { content: [{ type: "text", text: output }] };
|
|
1517
|
+
});
|
|
1518
|
+
server.registerTool("diviops_scf_field_group_list", {
|
|
1519
|
+
description: "List all SCF/ACF field groups in the database (post_name = ACF key, post_title, post_status, post_modified). Read-only. Queries the underlying `acf-field-group` post type via `wp post list` — works on both SCF 6.8.4+ (which dropped the legacy `wp acf field-group …` family in favor of the `wp scf json` namespace) and older ACF installs.",
|
|
1520
|
+
}, async () => {
|
|
1521
|
+
const gate = ensureWpCli();
|
|
1522
|
+
if (!gate.ok) {
|
|
1523
|
+
return { content: [{ type: "text", text: gate.text }] };
|
|
1524
|
+
}
|
|
1525
|
+
const result = await wpCli.runArgs([
|
|
1526
|
+
"post",
|
|
1527
|
+
"list",
|
|
1528
|
+
"--post_type=acf-field-group",
|
|
1529
|
+
"--post_status=any",
|
|
1530
|
+
"--fields=ID,post_name,post_title,post_status,post_modified",
|
|
1531
|
+
"--format=json",
|
|
1532
|
+
]);
|
|
1533
|
+
const output = result.success
|
|
1534
|
+
? result.output
|
|
1535
|
+
: `Error: ${result.error}\n${result.output}`;
|
|
1536
|
+
return { content: [{ type: "text", text: output }] };
|
|
1537
|
+
});
|
|
1538
|
+
server.registerTool("diviops_scf_field_group_get", {
|
|
1539
|
+
description: "Fetch a single SCF/ACF field group from the `acf-field-group` post type — by ACF key (`group_abc123`, looked up via `post_name`) or by numeric WP post ID. Returns the WP post fields (post_name, post_title, post_content with serialized fields blob, post_status, post_modified). For the parsed/structured field tree including nested fields, use `diviops_scf_export --field-groups=<key> --stdout` instead. Read-only. SCF 6.8.4 dropped the legacy `wp acf field-group get` command, so this wrapper queries the post type directly via `wp post`.",
|
|
1540
|
+
inputSchema: {
|
|
1541
|
+
key: z
|
|
1542
|
+
.string()
|
|
1543
|
+
.describe("ACF field-group key (`group_abc123`, matched against post_name) or numeric WP post ID."),
|
|
1544
|
+
},
|
|
1545
|
+
}, async ({ key }) => {
|
|
1546
|
+
const gate = ensureWpCli();
|
|
1547
|
+
if (!gate.ok) {
|
|
1548
|
+
return { content: [{ type: "text", text: gate.text }] };
|
|
1549
|
+
}
|
|
1550
|
+
// If the input looks like a numeric ID, hand it to `wp post get` directly.
|
|
1551
|
+
// Otherwise treat it as an ACF key and resolve via post_name first.
|
|
1552
|
+
const isNumericId = /^\d+$/.test(key);
|
|
1553
|
+
if (isNumericId) {
|
|
1554
|
+
const result = await wpCli.runArgs([
|
|
1555
|
+
"post",
|
|
1556
|
+
"get",
|
|
1557
|
+
key,
|
|
1558
|
+
"--format=json",
|
|
1559
|
+
]);
|
|
1560
|
+
const output = result.success
|
|
1561
|
+
? result.output
|
|
1562
|
+
: `Error: ${result.error}\n${result.output}`;
|
|
1563
|
+
return { content: [{ type: "text", text: output }] };
|
|
1564
|
+
}
|
|
1565
|
+
// Resolve ACF key → post ID via `wp post list --name=<key>`. Single-row
|
|
1566
|
+
// lookup; returns [] if the key isn't found.
|
|
1567
|
+
const lookup = await wpCli.runArgs([
|
|
1568
|
+
"post",
|
|
1569
|
+
"list",
|
|
1570
|
+
"--post_type=acf-field-group",
|
|
1571
|
+
"--post_status=any",
|
|
1572
|
+
`--name=${key}`,
|
|
1573
|
+
"--fields=ID",
|
|
1574
|
+
"--format=json",
|
|
1575
|
+
]);
|
|
1576
|
+
if (!lookup.success) {
|
|
1577
|
+
return {
|
|
1578
|
+
content: [
|
|
1579
|
+
{
|
|
1580
|
+
type: "text",
|
|
1581
|
+
text: `Error looking up field-group key "${key}": ${lookup.error}\n${lookup.output}`,
|
|
1582
|
+
},
|
|
1583
|
+
],
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
let postId = null;
|
|
1587
|
+
try {
|
|
1588
|
+
const rows = JSON.parse(lookup.output);
|
|
1589
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
1590
|
+
postId = String(rows[0].ID);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
catch {
|
|
1594
|
+
// Fall through — postId stays null, return a clear "not found" error.
|
|
1595
|
+
}
|
|
1596
|
+
if (!postId) {
|
|
1597
|
+
return {
|
|
1598
|
+
content: [
|
|
1599
|
+
{
|
|
1600
|
+
type: "text",
|
|
1601
|
+
text: `No field-group found for key "${key}". Expected an ACF key (e.g. "group_5f8a1b2c3d4e5") or a numeric WP post ID (e.g. "287"). Use diviops_scf_field_group_list to see available keys (post_name field).`,
|
|
1602
|
+
},
|
|
1603
|
+
],
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
const result = await wpCli.runArgs([
|
|
1607
|
+
"post",
|
|
1608
|
+
"get",
|
|
1609
|
+
postId,
|
|
1610
|
+
"--format=json",
|
|
1611
|
+
]);
|
|
1612
|
+
const output = result.success
|
|
1613
|
+
? result.output
|
|
1614
|
+
: `Error: ${result.error}\n${result.output}`;
|
|
1615
|
+
return { content: [{ type: "text", text: output }] };
|
|
1616
|
+
});
|
|
1348
1617
|
// ── Connection ──────────────────────────────────────────────────────
|
|
1349
|
-
server.registerTool("
|
|
1618
|
+
server.registerTool("diviops_meta_ping", {
|
|
1350
1619
|
description: "Test the connection to the WordPress site and verify the Divi MCP plugin is active.",
|
|
1351
1620
|
}, async () => {
|
|
1352
1621
|
const result = await wp.testConnection();
|
|
@@ -1356,7 +1625,7 @@ server.registerTool("diviops_test_connection", {
|
|
|
1356
1625
|
],
|
|
1357
1626
|
};
|
|
1358
1627
|
});
|
|
1359
|
-
server.registerTool("
|
|
1628
|
+
server.registerTool("diviops_meta_info", {
|
|
1360
1629
|
description: "Returns DiviOps MCP server identity, version, license type, and available capabilities.",
|
|
1361
1630
|
}, async () => {
|
|
1362
1631
|
const info = {
|
|
@@ -1455,8 +1724,8 @@ Attributes are JSON in the block comment. Structure:
|
|
|
1455
1724
|
- \`divi/cta\` — Call to action blocks
|
|
1456
1725
|
|
|
1457
1726
|
## Tips
|
|
1458
|
-
1. Always use \`
|
|
1459
|
-
2. Use \`
|
|
1727
|
+
1. Always use \`diviops_schema_get_module\` to check exact attribute names before building markup.
|
|
1728
|
+
2. Use \`diviops_page_get_layout\` on existing pages to learn the format from real examples.
|
|
1460
1729
|
3. Use \`diviops_render_preview\` to validate markup before saving.
|
|
1461
1730
|
`;
|
|
1462
1731
|
// ── Template Resources ──────────────────────────────────────────────
|
|
@@ -1479,7 +1748,7 @@ function loadTemplates() {
|
|
|
1479
1748
|
}
|
|
1480
1749
|
const templates = loadTemplates();
|
|
1481
1750
|
// Register a list tool so Claude can discover available templates
|
|
1482
|
-
server.registerTool("
|
|
1751
|
+
server.registerTool("diviops_template_list", {
|
|
1483
1752
|
description: "List available Divi page section templates. Each template contains verified block markup patterns that can be used as a base for page generation.",
|
|
1484
1753
|
}, async () => {
|
|
1485
1754
|
const list = Array.from(templates.entries()).map(([name, t]) => ({
|
|
@@ -1492,7 +1761,7 @@ server.registerTool("diviops_list_templates", {
|
|
|
1492
1761
|
content: [{ type: "text", text: JSON.stringify(list) }],
|
|
1493
1762
|
};
|
|
1494
1763
|
});
|
|
1495
|
-
server.registerTool("
|
|
1764
|
+
server.registerTool("diviops_template_get", {
|
|
1496
1765
|
description: "Get a specific Divi template with verified block markup, customizable variables, and usage notes. Use this to generate pages based on proven patterns.",
|
|
1497
1766
|
inputSchema: {
|
|
1498
1767
|
template_name: z
|
|
@@ -1519,7 +1788,7 @@ server.registerTool("diviops_get_template", {
|
|
|
1519
1788
|
};
|
|
1520
1789
|
});
|
|
1521
1790
|
// ── Variable Manager CRUD ─────────────────────────────────────────────
|
|
1522
|
-
server.registerTool("
|
|
1791
|
+
server.registerTool("diviops_variable_list", {
|
|
1523
1792
|
description: "List all design token variables from the Divi Variable Manager. Colors (gcid-*) come from et_global_data, numbers/strings/etc (gvid-*) from et_divi_global_variables. Filter by type or ID prefix.",
|
|
1524
1793
|
inputSchema: {
|
|
1525
1794
|
type: z
|
|
@@ -1537,14 +1806,14 @@ server.registerTool("diviops_list_variables", {
|
|
|
1537
1806
|
params.type = type;
|
|
1538
1807
|
if (prefix)
|
|
1539
1808
|
params.prefix = prefix;
|
|
1540
|
-
const result = await wp.request("/
|
|
1809
|
+
const result = await wp.request("/variable/list", { params });
|
|
1541
1810
|
return {
|
|
1542
1811
|
content: [
|
|
1543
1812
|
{ type: "text", text: JSON.stringify(result) },
|
|
1544
1813
|
],
|
|
1545
1814
|
};
|
|
1546
1815
|
});
|
|
1547
|
-
server.registerTool("
|
|
1816
|
+
server.registerTool("diviops_variable_create", {
|
|
1548
1817
|
description: 'Create a design token variable in the Divi Variable Manager. Colors (type "colors") use gcid-* IDs and hex values. Numbers/strings/etc use gvid-* IDs. For type="numbers" fluid tokens, pass min+max shorthand (anchors default to 320px/1920px) or explicit targets — server generates arithmetically-correct clamp() formulas. All-px inputs emit px (safe default, root-agnostic). Rem inputs OR rem output require explicit opt-in: pass output_unit="rem" (accepts the 1rem=16px default) or root_font_size_px:N (declares your site\'s actual root font-size for correct rem emission on non-16px-root sites). Mutually exclusive with value.',
|
|
1549
1818
|
inputSchema: {
|
|
1550
1819
|
type: z
|
|
@@ -1612,8 +1881,8 @@ server.registerTool("diviops_create_variable", {
|
|
|
1612
1881
|
],
|
|
1613
1882
|
};
|
|
1614
1883
|
});
|
|
1615
|
-
server.registerTool("
|
|
1616
|
-
description: "Batch-emit a fluid typography + spacing + radius variable set in one call — mirrors Divi 5.4.0's Variable Generator Modal at the algorithm level (clamp() math is identical to
|
|
1884
|
+
server.registerTool("diviops_variable_create_fluid_system", {
|
|
1885
|
+
description: "Batch-emit a fluid typography + spacing + radius variable set in one call — mirrors Divi 5.4.0's Variable Generator Modal at the algorithm level (clamp() math is identical to diviops_variable_create's fluid mode) but layers profile-selectable anchors over it. Each category is independent and optional. Use for: (1) bootstrapping a design system in one call instead of 20+ individual diviops_variable_create invocations; (2) mirroring ET's variable layout so your tokens coexist with VB-generated ones in the Variable Manager; (3) deterministic preflight via dry_run before committing the registry change. By default, refuses to overwrite existing IDs (returns them in `skipped`) — pass overwrite=true to update in place. Persists in a single atomic write to the variable registry; mid-batch failures roll back cleanly.",
|
|
1617
1886
|
inputSchema: {
|
|
1618
1887
|
profile: z
|
|
1619
1888
|
.enum(["divi-default", "wide", "custom"])
|
|
@@ -1770,7 +2039,7 @@ server.registerTool("diviops_create_fluid_system", {
|
|
|
1770
2039
|
body.dry_run = dry_run;
|
|
1771
2040
|
if (overwrite !== undefined)
|
|
1772
2041
|
body.overwrite = overwrite;
|
|
1773
|
-
const result = await wp.request("/
|
|
2042
|
+
const result = await wp.request("/variable/create-fluid-system", {
|
|
1774
2043
|
method: "POST",
|
|
1775
2044
|
body,
|
|
1776
2045
|
});
|
|
@@ -1780,8 +2049,8 @@ server.registerTool("diviops_create_fluid_system", {
|
|
|
1780
2049
|
],
|
|
1781
2050
|
};
|
|
1782
2051
|
});
|
|
1783
|
-
server.registerTool("
|
|
1784
|
-
description: "Delete a design token variable by ID. Auto-detects storage from ID prefix (gcid-* = colors, gvid-* = numbers/strings/etc). Returns HTTP 409 when live references exist unless force=true — run
|
|
2052
|
+
server.registerTool("diviops_variable_delete", {
|
|
2053
|
+
description: "Delete a design token variable by ID. Auto-detects storage from ID prefix (gcid-* = colors, gvid-* = numbers/strings/etc). Returns HTTP 409 when live references exist unless force=true — run diviops_variable_scan_orphans to see where the references live. Returns HTTP 403 for Divi's customizer-bound defaults (gcid-primary-color, gcid-secondary-color, gcid-heading-color, gcid-body-color, gcid-link-color); those are managed via WP Customizer theme options and can't be deleted via this tool.",
|
|
1785
2054
|
inputSchema: {
|
|
1786
2055
|
id: z
|
|
1787
2056
|
.string()
|
|
@@ -1790,7 +2059,7 @@ server.registerTool("diviops_delete_variable", {
|
|
|
1790
2059
|
.boolean()
|
|
1791
2060
|
.optional()
|
|
1792
2061
|
.default(false)
|
|
1793
|
-
.describe("Delete even if live references exist. Orphans will remain in page/preset content and render as invalid CSS on the frontend — run
|
|
2062
|
+
.describe("Delete even if live references exist. Orphans will remain in page/preset content and render as invalid CSS on the frontend — run diviops_variable_scan_orphans afterwards to audit."),
|
|
1794
2063
|
},
|
|
1795
2064
|
}, async ({ id, force }) => {
|
|
1796
2065
|
const result = await wp.request("/variable/delete", {
|
|
@@ -1803,18 +2072,18 @@ server.registerTool("diviops_delete_variable", {
|
|
|
1803
2072
|
],
|
|
1804
2073
|
};
|
|
1805
2074
|
});
|
|
1806
|
-
server.registerTool("
|
|
2075
|
+
server.registerTool("diviops_variable_scan_orphans", {
|
|
1807
2076
|
description: "Scan pages, Theme Builder layouts (header/body/footer), Divi Library items, canvas pages, and the preset registry for gvid-/gcid- references that have no backing entry in the Variable Manager (orphans), plus variables defined but referenced nowhere (unused). Orphans render as invalid CSS on the frontend — the $variable()$ resolver falls through with no fallback. Use after a deletion with force=true, or periodically as a hygiene check. Symmetric to diviops_preset_scan_orphans.",
|
|
1808
2077
|
}, async () => {
|
|
1809
|
-
const result = await wp.request("/
|
|
2078
|
+
const result = await wp.request("/variable/scan-orphans");
|
|
1810
2079
|
return {
|
|
1811
2080
|
content: [
|
|
1812
2081
|
{ type: "text", text: JSON.stringify(result) },
|
|
1813
2082
|
],
|
|
1814
2083
|
};
|
|
1815
2084
|
});
|
|
1816
|
-
server.registerTool("
|
|
1817
|
-
description: "Detect which numeric/font variable IDs a single page actually emits — the exact set Divi 5.4.0+ uses to scope selective `:root{--gvid-*}` CSS variable emission. Walks the same content stack the frontend assembles: post_content + active Theme Builder header/body/footer template content + appended canvas content (interaction targets etc.), plus presets referenced by that content. NOTE: this is `gvid-*` only — color variables (`gcid-*`) are emitted via a separate path (`GlobalData` color block) that is NOT scoped per-page in 5.4.0; this tool returns gvid IDs only. Use for per-page orphan validation (complements global
|
|
2085
|
+
server.registerTool("diviops_variable_used_on_page", {
|
|
2086
|
+
description: "Detect which numeric/font variable IDs a single page actually emits — the exact set Divi 5.4.0+ uses to scope selective `:root{--gvid-*}` CSS variable emission. Walks the same content stack the frontend assembles: post_content + active Theme Builder header/body/footer template content + appended canvas content (interaction targets etc.), plus presets referenced by that content. NOTE: this is `gvid-*` only — color variables (`gcid-*`) are emitted via a separate path (`GlobalData` color block) that is NOT scoped per-page in 5.4.0; this tool returns gvid IDs only. Use for per-page orphan validation (complements global diviops_variable_scan_orphans), preflight before bulk variable rename (know which pages are affected), or to debug why a numeric/font variable doesn't render on a specific page. Read-only. Returns variable_ids (sorted, deduped), count, and the tb_template_ids resolved for that post.",
|
|
1818
2087
|
inputSchema: {
|
|
1819
2088
|
post_id: z
|
|
1820
2089
|
.number()
|
|
@@ -1823,14 +2092,14 @@ server.registerTool("diviops_variables_used_on_page", {
|
|
|
1823
2092
|
.describe("WordPress post/page ID. The page does not need to be Divi-built — TB templates and canvases attached to non-Divi posts are still scanned."),
|
|
1824
2093
|
},
|
|
1825
2094
|
}, async ({ post_id }) => {
|
|
1826
|
-
const result = await wp.request(`/
|
|
2095
|
+
const result = await wp.request(`/variable/used-on-page/${post_id}`);
|
|
1827
2096
|
return {
|
|
1828
2097
|
content: [
|
|
1829
2098
|
{ type: "text", text: JSON.stringify(result) },
|
|
1830
2099
|
],
|
|
1831
2100
|
};
|
|
1832
2101
|
});
|
|
1833
|
-
server.registerTool("
|
|
2102
|
+
server.registerTool("diviops_meta_flush_cache", {
|
|
1834
2103
|
description: "Flush Divi's compiled static CSS cache under wp-content/et-cache/. wp cache flush does NOT touch these files — the frontend can keep serving stale CSS after a preset/variable/module mutation until the cache is cleared. Delegates to Divi's native ET_Core_PageResource::remove_static_resources when available (response backend: \"divi_native\"), which additionally clears Theme Builder CSS scattered across other post dirs, archive/taxonomy/home/notfound CSS, the object cache, module features cache, post features cache, Google Fonts cache, dynamic assets cache, and post meta caches. Falls back to a targeted filesystem walk of numeric-named et-cache subdirs when the Divi class is absent (backend: \"fs_fallback\"). Provide exactly one selector — no site-wide default to prevent accidental full flush. Idempotent: missing cache root returns 200 with empty list.",
|
|
1835
2104
|
inputSchema: {
|
|
1836
2105
|
post_id: z
|
|
@@ -1859,7 +2128,7 @@ server.registerTool("diviops_flush_static_cache", {
|
|
|
1859
2128
|
body.all = true;
|
|
1860
2129
|
if (after !== undefined)
|
|
1861
2130
|
body.after = after;
|
|
1862
|
-
const result = await wp.request("/flush-
|
|
2131
|
+
const result = await wp.request("/meta/flush-cache", {
|
|
1863
2132
|
method: "POST",
|
|
1864
2133
|
body,
|
|
1865
2134
|
});
|