@diviops/mcp-server 1.5.3 → 1.5.5
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/dist/envelope.d.ts +37 -1
- package/dist/envelope.js +63 -2
- package/dist/index.js +97 -85
- package/package.json +1 -1
package/dist/envelope.d.ts
CHANGED
|
@@ -110,8 +110,44 @@ export declare function wrapResponse<T>(producer: () => Promise<T | DiviopsRespo
|
|
|
110
110
|
* (e.g. shrink schema, project fields) without unwrapping by hand.
|
|
111
111
|
*/
|
|
112
112
|
export declare function envelopeMap<T, U>(response: DiviopsResponse<T>, fn: (data: T) => U): DiviopsResponse<U>;
|
|
113
|
+
/**
|
|
114
|
+
* Idempotency vocabulary surfaced to clients via `_meta.idempotent` on
|
|
115
|
+
* every tool response (#597). Mirrors the registration-level `_meta.idempotent`
|
|
116
|
+
* declared at each `registerTool()` callsite so per-call consumers see the
|
|
117
|
+
* field where they naturally look (response envelope), not just at
|
|
118
|
+
* `tools/list` discovery time.
|
|
119
|
+
*/
|
|
120
|
+
export type IdempotentVerdict = "true" | "false" | "conditional";
|
|
121
|
+
/**
|
|
122
|
+
* Record a tool's idempotency verdict at registration time so per-call
|
|
123
|
+
* `serializeEnvelope(result, toolName)` can emit it on every response.
|
|
124
|
+
* Throws if the registration is missing `_meta.idempotent` — fail-loud
|
|
125
|
+
* matches `feedback_explicit_reject_over_silent_sanitize`: a tool that
|
|
126
|
+
* skipped the audit would silently lack the field, defeating #597.
|
|
127
|
+
*/
|
|
128
|
+
export declare function recordIdempotent(name: string, meta: {
|
|
129
|
+
idempotent?: string;
|
|
130
|
+
} | undefined): void;
|
|
131
|
+
/**
|
|
132
|
+
* Look up a tool's idempotency verdict. Returns undefined if the tool
|
|
133
|
+
* hasn't been recorded — caller decides whether to fail or skip emission.
|
|
134
|
+
*/
|
|
135
|
+
export declare function getRecordedIdempotent(name: string): IdempotentVerdict | undefined;
|
|
136
|
+
/**
|
|
137
|
+
* Snapshot of the recorded table — used by tests + the CI gate. Returns
|
|
138
|
+
* a fresh object so callers can't mutate the live registry.
|
|
139
|
+
*/
|
|
140
|
+
export declare function snapshotIdempotentTable(): Record<string, IdempotentVerdict>;
|
|
113
141
|
/**
|
|
114
142
|
* Serialize a `DiviopsResponse` as the JSON string an MCP tool emits in its
|
|
115
143
|
* `content[0].text` slot. Single emit point keeps the wire shape consistent.
|
|
144
|
+
*
|
|
145
|
+
* When `toolName` is provided AND the tool has been recorded via
|
|
146
|
+
* `recordIdempotent()`, injects `_meta.idempotent` onto the response
|
|
147
|
+
* envelope before serialization (#597). Per-call emission is a strict
|
|
148
|
+
* superset of the `tools/list` registration-level `_meta` surface — both
|
|
149
|
+
* keep working, per-call consumers get the field where they look. Applied
|
|
150
|
+
* to ok:true AND ok:false envelopes since idempotency is a property of
|
|
151
|
+
* the tool, not the call outcome.
|
|
116
152
|
*/
|
|
117
|
-
export declare function serializeEnvelope<T>(response: DiviopsResponse<T
|
|
153
|
+
export declare function serializeEnvelope<T>(response: DiviopsResponse<T>, toolName?: string): string;
|
package/dist/envelope.js
CHANGED
|
@@ -162,10 +162,71 @@ export function envelopeMap(response, fn) {
|
|
|
162
162
|
return { ok: true, data: fn(response.data) };
|
|
163
163
|
return response;
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Tool-name → idempotent-verdict registry, populated at server startup by
|
|
167
|
+
* `recordIdempotent()` from each tool's registration `_meta`. The audit
|
|
168
|
+
* doc (`docs/idempotency-audit.md`) remains the single source of truth;
|
|
169
|
+
* this table is just the runtime mirror used to enrich per-call responses.
|
|
170
|
+
*/
|
|
171
|
+
const IDEMPOTENT_TABLE = new Map();
|
|
172
|
+
/**
|
|
173
|
+
* Record a tool's idempotency verdict at registration time so per-call
|
|
174
|
+
* `serializeEnvelope(result, toolName)` can emit it on every response.
|
|
175
|
+
* Throws if the registration is missing `_meta.idempotent` — fail-loud
|
|
176
|
+
* matches `feedback_explicit_reject_over_silent_sanitize`: a tool that
|
|
177
|
+
* skipped the audit would silently lack the field, defeating #597.
|
|
178
|
+
*/
|
|
179
|
+
export function recordIdempotent(name, meta) {
|
|
180
|
+
const verdict = meta?.idempotent;
|
|
181
|
+
if (verdict !== "true" && verdict !== "false" && verdict !== "conditional") {
|
|
182
|
+
throw new Error(`Tool '${name}' is missing or has invalid _meta.idempotent declaration ` +
|
|
183
|
+
`(got: ${verdict === undefined ? "undefined" : JSON.stringify(verdict)}). ` +
|
|
184
|
+
`Every tool must declare idempotent: "true" | "false" | "conditional" ` +
|
|
185
|
+
`per docs/idempotency-audit.md.`);
|
|
186
|
+
}
|
|
187
|
+
IDEMPOTENT_TABLE.set(name, verdict);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Look up a tool's idempotency verdict. Returns undefined if the tool
|
|
191
|
+
* hasn't been recorded — caller decides whether to fail or skip emission.
|
|
192
|
+
*/
|
|
193
|
+
export function getRecordedIdempotent(name) {
|
|
194
|
+
return IDEMPOTENT_TABLE.get(name);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Snapshot of the recorded table — used by tests + the CI gate. Returns
|
|
198
|
+
* a fresh object so callers can't mutate the live registry.
|
|
199
|
+
*/
|
|
200
|
+
export function snapshotIdempotentTable() {
|
|
201
|
+
return Object.fromEntries(IDEMPOTENT_TABLE.entries());
|
|
202
|
+
}
|
|
165
203
|
/**
|
|
166
204
|
* Serialize a `DiviopsResponse` as the JSON string an MCP tool emits in its
|
|
167
205
|
* `content[0].text` slot. Single emit point keeps the wire shape consistent.
|
|
206
|
+
*
|
|
207
|
+
* When `toolName` is provided AND the tool has been recorded via
|
|
208
|
+
* `recordIdempotent()`, injects `_meta.idempotent` onto the response
|
|
209
|
+
* envelope before serialization (#597). Per-call emission is a strict
|
|
210
|
+
* superset of the `tools/list` registration-level `_meta` surface — both
|
|
211
|
+
* keep working, per-call consumers get the field where they look. Applied
|
|
212
|
+
* to ok:true AND ok:false envelopes since idempotency is a property of
|
|
213
|
+
* the tool, not the call outcome.
|
|
168
214
|
*/
|
|
169
|
-
export function serializeEnvelope(response) {
|
|
170
|
-
|
|
215
|
+
export function serializeEnvelope(response, toolName) {
|
|
216
|
+
if (toolName === undefined) {
|
|
217
|
+
return JSON.stringify(response);
|
|
218
|
+
}
|
|
219
|
+
const verdict = IDEMPOTENT_TABLE.get(toolName);
|
|
220
|
+
if (verdict === undefined) {
|
|
221
|
+
return JSON.stringify(response);
|
|
222
|
+
}
|
|
223
|
+
const existingMetaRaw = response._meta;
|
|
224
|
+
const existingMeta = typeof existingMetaRaw === "object" && existingMetaRaw !== null
|
|
225
|
+
? existingMetaRaw
|
|
226
|
+
: {};
|
|
227
|
+
const enriched = {
|
|
228
|
+
...response,
|
|
229
|
+
_meta: { ...existingMeta, idempotent: verdict },
|
|
230
|
+
};
|
|
231
|
+
return JSON.stringify(enriched);
|
|
171
232
|
}
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { WPClient } from "./wp-client.js";
|
|
15
15
|
import { MissingCapabilityError } from "./compatibility.js";
|
|
16
|
-
import { ErrorCodes, envelopeMap, serializeEnvelope, withCode, wrapResponse, } from "./envelope.js";
|
|
16
|
+
import { ErrorCodes, envelopeMap, recordIdempotent, serializeEnvelope, withCode, wrapResponse, } from "./envelope.js";
|
|
17
17
|
import { optimizeSchema } from "./schema-optimizer.js";
|
|
18
18
|
import { createWpCli } from "./wp-cli.js";
|
|
19
19
|
import { findForeignVarRefs, scanAttrsForForeignVarRefs, isolationErrorResult, } from "./validate-attrs.js";
|
|
@@ -122,8 +122,20 @@ function registerPluginTool(name, config, handler) {
|
|
|
122
122
|
}
|
|
123
123
|
return handler(args);
|
|
124
124
|
});
|
|
125
|
+
recordIdempotent(name, config?._meta);
|
|
125
126
|
server.registerTool(name, config, wrapped);
|
|
126
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Server-local tools (no plugin dependency) register via this thin shim
|
|
130
|
+
* instead of `server.registerTool` directly. Same recording obligation
|
|
131
|
+
* as `registerPluginTool` — every tool surface needs `_meta.idempotent`
|
|
132
|
+
* captured into the runtime table so `serializeEnvelope(result, name)`
|
|
133
|
+
* can emit it on per-call responses (#597).
|
|
134
|
+
*/
|
|
135
|
+
function registerLocalTool(name, config, handler) {
|
|
136
|
+
recordIdempotent(name, config?._meta);
|
|
137
|
+
server.registerTool(name, config, handler);
|
|
138
|
+
}
|
|
127
139
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
128
140
|
// ── dry_run convention ──────────────────────────────────────────────
|
|
129
141
|
//
|
|
@@ -172,7 +184,7 @@ registerPluginTool("diviops_page_list", {
|
|
|
172
184
|
});
|
|
173
185
|
return {
|
|
174
186
|
content: [
|
|
175
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
187
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_page_list") },
|
|
176
188
|
],
|
|
177
189
|
};
|
|
178
190
|
});
|
|
@@ -187,7 +199,7 @@ registerPluginTool("diviops_page_get", {
|
|
|
187
199
|
const result = await wp.requestEnveloped(`/page/get/${page_id}`);
|
|
188
200
|
return {
|
|
189
201
|
content: [
|
|
190
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
202
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_page_get") },
|
|
191
203
|
],
|
|
192
204
|
};
|
|
193
205
|
});
|
|
@@ -209,7 +221,7 @@ registerPluginTool("diviops_page_get_layout", {
|
|
|
209
221
|
});
|
|
210
222
|
return {
|
|
211
223
|
content: [
|
|
212
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
224
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_page_get_layout") },
|
|
213
225
|
],
|
|
214
226
|
};
|
|
215
227
|
});
|
|
@@ -221,7 +233,7 @@ registerPluginTool("diviops_schema_list_modules", {
|
|
|
221
233
|
const result = await wp.requestEnveloped("/schema/modules");
|
|
222
234
|
return {
|
|
223
235
|
content: [
|
|
224
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
236
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_schema_list_modules") },
|
|
225
237
|
],
|
|
226
238
|
};
|
|
227
239
|
});
|
|
@@ -263,12 +275,12 @@ registerPluginTool("diviops_schema_get_module", {
|
|
|
263
275
|
},
|
|
264
276
|
};
|
|
265
277
|
return {
|
|
266
|
-
content: [{ type: "text", text: serializeEnvelope(failure) }],
|
|
278
|
+
content: [{ type: "text", text: serializeEnvelope(failure, "diviops_schema_get_module") }],
|
|
267
279
|
};
|
|
268
280
|
}
|
|
269
281
|
const result = await wp.requestEnveloped("/schema/module/dump-all");
|
|
270
282
|
return {
|
|
271
|
-
content: [{ type: "text", text: serializeEnvelope(result) }],
|
|
283
|
+
content: [{ type: "text", text: serializeEnvelope(result, "diviops_schema_get_module") }],
|
|
272
284
|
};
|
|
273
285
|
}
|
|
274
286
|
if (!module_name) {
|
|
@@ -280,14 +292,14 @@ registerPluginTool("diviops_schema_get_module", {
|
|
|
280
292
|
},
|
|
281
293
|
};
|
|
282
294
|
return {
|
|
283
|
-
content: [{ type: "text", text: serializeEnvelope(failure) }],
|
|
295
|
+
content: [{ type: "text", text: serializeEnvelope(failure, "diviops_schema_get_module") }],
|
|
284
296
|
};
|
|
285
297
|
}
|
|
286
298
|
const result = await wp.requestEnveloped(`/schema/module/${encodeURIComponent(module_name)}`);
|
|
287
299
|
const projected = envelopeMap(result, (data) => raw ? data : optimizeSchema(data));
|
|
288
300
|
return {
|
|
289
301
|
content: [
|
|
290
|
-
{ type: "text", text: serializeEnvelope(projected) },
|
|
302
|
+
{ type: "text", text: serializeEnvelope(projected, "diviops_schema_get_module") },
|
|
291
303
|
],
|
|
292
304
|
};
|
|
293
305
|
});
|
|
@@ -299,7 +311,7 @@ registerPluginTool("diviops_schema_get_settings", {
|
|
|
299
311
|
const result = await wp.requestEnveloped("/schema/settings");
|
|
300
312
|
return {
|
|
301
313
|
content: [
|
|
302
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
314
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_schema_get_settings") },
|
|
303
315
|
],
|
|
304
316
|
};
|
|
305
317
|
});
|
|
@@ -311,7 +323,7 @@ registerPluginTool("diviops_global_color_list", {
|
|
|
311
323
|
const result = await wp.requestEnveloped("/global-color/list");
|
|
312
324
|
return {
|
|
313
325
|
content: [
|
|
314
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
326
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_global_color_list") },
|
|
315
327
|
],
|
|
316
328
|
};
|
|
317
329
|
});
|
|
@@ -354,7 +366,7 @@ registerPluginTool("diviops_global_color_create", {
|
|
|
354
366
|
method: "POST",
|
|
355
367
|
body,
|
|
356
368
|
});
|
|
357
|
-
return { content: [{ type: "text", text: serializeEnvelope(result) }] };
|
|
369
|
+
return { content: [{ type: "text", text: serializeEnvelope(result, "diviops_global_color_create") }] };
|
|
358
370
|
});
|
|
359
371
|
registerPluginTool("diviops_global_color_update", {
|
|
360
372
|
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. NOTE: the underlying upsert is merge-mode — supplying a gcid that doesn't yet exist creates a new color with that gcid (provided it satisfies the gcid charset/length rules) rather than failing as 'not found'. Pre-check via diviops_global_color_list if you need strict-update semantics. 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. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; malformed gcid charset/length returns code 'invalid_input' with `error.data` documenting the failed field; non-CSS color value returns code 'invalid_input'; attempts to write to a customizer-bound default (gcid-primary-color / gcid-secondary-color / gcid-heading-color / gcid-body-color / gcid-link-color) return code 'variable.customizer_default_immutable' (HTTP 403) with `error.data = { id, managed_by: 'wp_customizer' }` — same code as diviops_variable_delete because the identity is identical (5.4+ unified gcid-* into the variable manager while preserving customizer-binding for the five legacy defaults)." +
|
|
@@ -400,10 +412,10 @@ registerPluginTool("diviops_global_color_update", {
|
|
|
400
412
|
method: "POST",
|
|
401
413
|
body,
|
|
402
414
|
});
|
|
403
|
-
return { content: [{ type: "text", text: serializeEnvelope(result) }] };
|
|
415
|
+
return { content: [{ type: "text", text: serializeEnvelope(result, "diviops_global_color_update") }] };
|
|
404
416
|
});
|
|
405
417
|
registerPluginTool("diviops_global_color_delete", {
|
|
406
|
-
description: "Delete a global color from the registry by gcid. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }. Live-reference
|
|
418
|
+
description: "Delete a global color from the registry by gcid. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }. Live-reference detection uses parse_blocks over post_content across pages / TB layouts / library / canvas + the preset registry (mirrors diviops_variable_delete) — MCP-authored content is detected reliably, not just VB-saved content. Returns code 'conflict' (HTTP 409) when references exist with `error.data = { id, ref_count, locations[], scan_truncated, scanned_posts }`. Pass `force: true` to override; orphan refs will render as invalid CSS until pages are re-authored. 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 — returns code 'variable.customizer_default_immutable' (HTTP 403) with `error.data = { id, managed_by: 'wp_customizer' }`. Missing gcids return 'not_found' (HTTP 404). Malformed gcid (empty or missing `gcid-` prefix) returns 'invalid_input'. 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." +
|
|
407
419
|
DRY_RUN_DESC_SUFFIX,
|
|
408
420
|
inputSchema: {
|
|
409
421
|
gcid: z
|
|
@@ -413,7 +425,7 @@ registerPluginTool("diviops_global_color_delete", {
|
|
|
413
425
|
.boolean()
|
|
414
426
|
.optional()
|
|
415
427
|
.default(false)
|
|
416
|
-
.describe("If true, delete even when
|
|
428
|
+
.describe("If true, delete even when live references exist. Customizer-bound defaults remain protected regardless."),
|
|
417
429
|
dry_run: DRY_RUN_FIELD,
|
|
418
430
|
},
|
|
419
431
|
annotations: { idempotentHint: true },
|
|
@@ -428,7 +440,7 @@ registerPluginTool("diviops_global_color_delete", {
|
|
|
428
440
|
method: "POST",
|
|
429
441
|
body,
|
|
430
442
|
});
|
|
431
|
-
return { content: [{ type: "text", text: serializeEnvelope(result) }] };
|
|
443
|
+
return { content: [{ type: "text", text: serializeEnvelope(result, "diviops_global_color_delete") }] };
|
|
432
444
|
});
|
|
433
445
|
registerPluginTool("diviops_global_font_list", {
|
|
434
446
|
description: "Get the global font definitions from Divi settings. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }.",
|
|
@@ -438,7 +450,7 @@ registerPluginTool("diviops_global_font_list", {
|
|
|
438
450
|
const result = await wp.requestEnveloped("/global-font/list");
|
|
439
451
|
return {
|
|
440
452
|
content: [
|
|
441
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
453
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_global_font_list") },
|
|
442
454
|
],
|
|
443
455
|
};
|
|
444
456
|
});
|
|
@@ -465,7 +477,7 @@ registerPluginTool("diviops_meta_find_icon", {
|
|
|
465
477
|
const result = await wp.requestEnveloped(`/meta/find-icon?q=${encodeURIComponent(query)}&type=${type ?? "all"}&limit=${limit ?? 10}`);
|
|
466
478
|
return {
|
|
467
479
|
content: [
|
|
468
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
480
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_meta_find_icon") },
|
|
469
481
|
],
|
|
470
482
|
};
|
|
471
483
|
});
|
|
@@ -495,7 +507,7 @@ registerPluginTool("diviops_page_update_content", {
|
|
|
495
507
|
});
|
|
496
508
|
return {
|
|
497
509
|
content: [
|
|
498
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
510
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_page_update_content") },
|
|
499
511
|
],
|
|
500
512
|
};
|
|
501
513
|
});
|
|
@@ -513,7 +525,7 @@ registerPluginTool("diviops_render_preview", {
|
|
|
513
525
|
});
|
|
514
526
|
return {
|
|
515
527
|
content: [
|
|
516
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
528
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_render_preview") },
|
|
517
529
|
],
|
|
518
530
|
};
|
|
519
531
|
});
|
|
@@ -531,7 +543,7 @@ registerPluginTool("diviops_validate_blocks", {
|
|
|
531
543
|
});
|
|
532
544
|
return {
|
|
533
545
|
content: [
|
|
534
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
546
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_validate_blocks") },
|
|
535
547
|
],
|
|
536
548
|
};
|
|
537
549
|
});
|
|
@@ -565,7 +577,7 @@ registerPluginTool("diviops_section_append", {
|
|
|
565
577
|
});
|
|
566
578
|
return {
|
|
567
579
|
content: [
|
|
568
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
580
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_section_append") },
|
|
569
581
|
],
|
|
570
582
|
};
|
|
571
583
|
});
|
|
@@ -613,7 +625,7 @@ registerPluginTool("diviops_section_replace", {
|
|
|
613
625
|
});
|
|
614
626
|
return {
|
|
615
627
|
content: [
|
|
616
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
628
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_section_replace") },
|
|
617
629
|
],
|
|
618
630
|
};
|
|
619
631
|
});
|
|
@@ -655,7 +667,7 @@ registerPluginTool("diviops_section_remove", {
|
|
|
655
667
|
});
|
|
656
668
|
return {
|
|
657
669
|
content: [
|
|
658
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
670
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_section_remove") },
|
|
659
671
|
],
|
|
660
672
|
};
|
|
661
673
|
});
|
|
@@ -691,7 +703,7 @@ registerPluginTool("diviops_section_get", {
|
|
|
691
703
|
const result = await wp.requestEnveloped(`/section/get/${page_id}?${qs}`);
|
|
692
704
|
return {
|
|
693
705
|
content: [
|
|
694
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
706
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_section_get") },
|
|
695
707
|
],
|
|
696
708
|
};
|
|
697
709
|
});
|
|
@@ -747,7 +759,7 @@ registerPluginTool("diviops_module_update", {
|
|
|
747
759
|
});
|
|
748
760
|
return {
|
|
749
761
|
content: [
|
|
750
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
762
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_module_update") },
|
|
751
763
|
],
|
|
752
764
|
};
|
|
753
765
|
});
|
|
@@ -827,7 +839,7 @@ registerPluginTool("diviops_module_move", {
|
|
|
827
839
|
});
|
|
828
840
|
return {
|
|
829
841
|
content: [
|
|
830
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
842
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_module_move") },
|
|
831
843
|
],
|
|
832
844
|
};
|
|
833
845
|
});
|
|
@@ -857,7 +869,7 @@ registerPluginTool("diviops_module_lock", {
|
|
|
857
869
|
if (dry_run)
|
|
858
870
|
body.dry_run = true;
|
|
859
871
|
const result = await wp.requestEnveloped(`/module/lock/${page_id}`, { method: "POST", body });
|
|
860
|
-
return { content: [{ type: "text", text: serializeEnvelope(result) }] };
|
|
872
|
+
return { content: [{ type: "text", text: serializeEnvelope(result, "diviops_module_lock") }] };
|
|
861
873
|
});
|
|
862
874
|
registerPluginTool("diviops_module_unlock", {
|
|
863
875
|
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. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; missing module returns 'not_found' with `error.data.target_kind = \"module\"`." +
|
|
@@ -885,7 +897,7 @@ registerPluginTool("diviops_module_unlock", {
|
|
|
885
897
|
if (dry_run)
|
|
886
898
|
body.dry_run = true;
|
|
887
899
|
const result = await wp.requestEnveloped(`/module/unlock/${page_id}`, { method: "POST", body });
|
|
888
|
-
return { content: [{ type: "text", text: serializeEnvelope(result) }] };
|
|
900
|
+
return { content: [{ type: "text", text: serializeEnvelope(result, "diviops_module_unlock") }] };
|
|
889
901
|
});
|
|
890
902
|
registerPluginTool("diviops_module_clone", {
|
|
891
903
|
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. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; missing module returns code "not_found" with error.data.target_kind = "module", malformed parent containers surface code "divi_error" (HTTP 500).' +
|
|
@@ -916,7 +928,7 @@ registerPluginTool("diviops_module_clone", {
|
|
|
916
928
|
if (dry_run)
|
|
917
929
|
body.dry_run = true;
|
|
918
930
|
const result = await wp.requestEnveloped(`/module/clone/${page_id}`, { method: "POST", body });
|
|
919
|
-
return { content: [{ type: "text", text: serializeEnvelope(result) }] };
|
|
931
|
+
return { content: [{ type: "text", text: serializeEnvelope(result, "diviops_module_clone") }] };
|
|
920
932
|
});
|
|
921
933
|
registerPluginTool("diviops_page_create", {
|
|
922
934
|
description: "Create a new WordPress page, optionally with Divi block content. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; non-string content or invalid status return code 'invalid_input' with `error.data` documenting the failed field." +
|
|
@@ -952,7 +964,7 @@ registerPluginTool("diviops_page_create", {
|
|
|
952
964
|
});
|
|
953
965
|
return {
|
|
954
966
|
content: [
|
|
955
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
967
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_page_create") },
|
|
956
968
|
],
|
|
957
969
|
};
|
|
958
970
|
});
|
|
@@ -983,7 +995,7 @@ registerPluginTool("diviops_page_trash", {
|
|
|
983
995
|
});
|
|
984
996
|
return {
|
|
985
997
|
content: [
|
|
986
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
998
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_page_trash") },
|
|
987
999
|
],
|
|
988
1000
|
};
|
|
989
1001
|
});
|
|
@@ -1019,7 +1031,7 @@ registerPluginTool("diviops_page_update_status", {
|
|
|
1019
1031
|
});
|
|
1020
1032
|
return {
|
|
1021
1033
|
content: [
|
|
1022
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1034
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_page_update_status") },
|
|
1023
1035
|
],
|
|
1024
1036
|
};
|
|
1025
1037
|
});
|
|
@@ -1032,7 +1044,7 @@ registerPluginTool("diviops_preset_audit", {
|
|
|
1032
1044
|
const result = await wp.requestEnveloped("/preset/audit");
|
|
1033
1045
|
return {
|
|
1034
1046
|
content: [
|
|
1035
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1047
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_preset_audit") },
|
|
1036
1048
|
],
|
|
1037
1049
|
};
|
|
1038
1050
|
});
|
|
@@ -1080,7 +1092,7 @@ registerPluginTool("diviops_preset_cleanup", {
|
|
|
1080
1092
|
});
|
|
1081
1093
|
return {
|
|
1082
1094
|
content: [
|
|
1083
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1095
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_preset_cleanup") },
|
|
1084
1096
|
],
|
|
1085
1097
|
};
|
|
1086
1098
|
});
|
|
@@ -1119,7 +1131,7 @@ registerPluginTool("diviops_preset_update", {
|
|
|
1119
1131
|
});
|
|
1120
1132
|
return {
|
|
1121
1133
|
content: [
|
|
1122
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1134
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_preset_update") },
|
|
1123
1135
|
],
|
|
1124
1136
|
};
|
|
1125
1137
|
});
|
|
@@ -1144,7 +1156,7 @@ registerPluginTool("diviops_preset_delete", {
|
|
|
1144
1156
|
});
|
|
1145
1157
|
return {
|
|
1146
1158
|
content: [
|
|
1147
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1159
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_preset_delete") },
|
|
1148
1160
|
],
|
|
1149
1161
|
};
|
|
1150
1162
|
});
|
|
@@ -1210,7 +1222,7 @@ registerPluginTool("diviops_preset_create", {
|
|
|
1210
1222
|
const result = await wp.requestEnveloped("/preset/create", { method: "POST", body });
|
|
1211
1223
|
return {
|
|
1212
1224
|
content: [
|
|
1213
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1225
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_preset_create") },
|
|
1214
1226
|
],
|
|
1215
1227
|
};
|
|
1216
1228
|
});
|
|
@@ -1261,7 +1273,7 @@ registerPluginTool("diviops_preset_reassign", {
|
|
|
1261
1273
|
});
|
|
1262
1274
|
return {
|
|
1263
1275
|
content: [
|
|
1264
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1276
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_preset_reassign") },
|
|
1265
1277
|
],
|
|
1266
1278
|
};
|
|
1267
1279
|
});
|
|
@@ -1273,7 +1285,7 @@ registerPluginTool("diviops_preset_scan_orphans", {
|
|
|
1273
1285
|
const result = await wp.requestEnveloped("/preset/scan-orphans");
|
|
1274
1286
|
return {
|
|
1275
1287
|
content: [
|
|
1276
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1288
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_preset_scan_orphans") },
|
|
1277
1289
|
],
|
|
1278
1290
|
};
|
|
1279
1291
|
});
|
|
@@ -1319,7 +1331,7 @@ registerPluginTool("diviops_preset_set_default", {
|
|
|
1319
1331
|
});
|
|
1320
1332
|
return {
|
|
1321
1333
|
content: [
|
|
1322
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1334
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_preset_set_default") },
|
|
1323
1335
|
],
|
|
1324
1336
|
};
|
|
1325
1337
|
});
|
|
@@ -1354,7 +1366,7 @@ registerPluginTool("diviops_library_list", {
|
|
|
1354
1366
|
const result = await wp.requestEnveloped("/library/items", { params });
|
|
1355
1367
|
return {
|
|
1356
1368
|
content: [
|
|
1357
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1369
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_library_list") },
|
|
1358
1370
|
],
|
|
1359
1371
|
};
|
|
1360
1372
|
});
|
|
@@ -1369,7 +1381,7 @@ registerPluginTool("diviops_library_get", {
|
|
|
1369
1381
|
const result = await wp.requestEnveloped(`/library/item/${item_id}`);
|
|
1370
1382
|
return {
|
|
1371
1383
|
content: [
|
|
1372
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1384
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_library_get") },
|
|
1373
1385
|
],
|
|
1374
1386
|
};
|
|
1375
1387
|
});
|
|
@@ -1405,7 +1417,7 @@ registerPluginTool("diviops_library_save", {
|
|
|
1405
1417
|
});
|
|
1406
1418
|
return {
|
|
1407
1419
|
content: [
|
|
1408
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1420
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_library_save") },
|
|
1409
1421
|
],
|
|
1410
1422
|
};
|
|
1411
1423
|
});
|
|
@@ -1432,7 +1444,7 @@ registerPluginTool("diviops_tb_template_list", {
|
|
|
1432
1444
|
const result = await wp.requestEnveloped("/theme-builder/template/list", { params });
|
|
1433
1445
|
return {
|
|
1434
1446
|
content: [
|
|
1435
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1447
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_tb_template_list") },
|
|
1436
1448
|
],
|
|
1437
1449
|
};
|
|
1438
1450
|
});
|
|
@@ -1449,7 +1461,7 @@ registerPluginTool("diviops_tb_layout_get", {
|
|
|
1449
1461
|
const result = await wp.requestEnveloped(`/theme-builder/layout/get/${layout_id}`);
|
|
1450
1462
|
return {
|
|
1451
1463
|
content: [
|
|
1452
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1464
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_tb_layout_get") },
|
|
1453
1465
|
],
|
|
1454
1466
|
};
|
|
1455
1467
|
});
|
|
@@ -1473,7 +1485,7 @@ registerPluginTool("diviops_tb_layout_update", {
|
|
|
1473
1485
|
});
|
|
1474
1486
|
return {
|
|
1475
1487
|
content: [
|
|
1476
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1488
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_tb_layout_update") },
|
|
1477
1489
|
],
|
|
1478
1490
|
};
|
|
1479
1491
|
});
|
|
@@ -1509,13 +1521,13 @@ registerPluginTool("diviops_tb_template_create", {
|
|
|
1509
1521
|
});
|
|
1510
1522
|
return {
|
|
1511
1523
|
content: [
|
|
1512
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1524
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_tb_template_create") },
|
|
1513
1525
|
],
|
|
1514
1526
|
};
|
|
1515
1527
|
});
|
|
1516
1528
|
// ── Canvas Tools ────────────────────────────────────────────────────
|
|
1517
1529
|
registerPluginTool("diviops_canvas_create", {
|
|
1518
|
-
description: "Create a canvas (off-canvas workspace) linked to a page. Used for popups, off-canvas menus, modals. Content uses standard Divi block markup. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; missing parent_page_id returns ok:false with code 'not_found'; non-string content / malformed canvas_id / append_to_main outside {above, below} returns 'invalid_input'." +
|
|
1530
|
+
description: "Create a canvas (off-canvas workspace) linked to a page. Used for popups, off-canvas menus, modals. Content uses standard Divi block markup. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; missing parent_page_id returns ok:false with code 'not_found'; non-string content / malformed canvas_id / append_to_main outside {above, below} returns 'invalid_input'. Returns code 'conflict' (HTTP 409) when a canvas with the same title already exists under the same parent_page_id — error.data = { existing_canvas_id, parent_page_id, title }. Mirrors diviops_preset_create's uniqueness contract." +
|
|
1519
1531
|
DRY_RUN_DESC_SUFFIX,
|
|
1520
1532
|
inputSchema: {
|
|
1521
1533
|
title: z
|
|
@@ -1542,7 +1554,7 @@ registerPluginTool("diviops_canvas_create", {
|
|
|
1542
1554
|
dry_run: DRY_RUN_FIELD,
|
|
1543
1555
|
},
|
|
1544
1556
|
annotations: { idempotentHint: false },
|
|
1545
|
-
_meta: { idempotent: "
|
|
1557
|
+
_meta: { idempotent: "conditional" },
|
|
1546
1558
|
}, async ({ title, parent_page_id, content, canvas_id, append_to_main, z_index, dry_run, }) => {
|
|
1547
1559
|
const body = {
|
|
1548
1560
|
title,
|
|
@@ -1560,7 +1572,7 @@ registerPluginTool("diviops_canvas_create", {
|
|
|
1560
1572
|
const result = await wp.requestEnveloped("/canvas/create", { method: "POST", body });
|
|
1561
1573
|
return {
|
|
1562
1574
|
content: [
|
|
1563
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1575
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_canvas_create") },
|
|
1564
1576
|
],
|
|
1565
1577
|
};
|
|
1566
1578
|
});
|
|
@@ -1591,7 +1603,7 @@ registerPluginTool("diviops_canvas_list", {
|
|
|
1591
1603
|
const result = await wp.requestEnveloped("/canvas/list", { params });
|
|
1592
1604
|
return {
|
|
1593
1605
|
content: [
|
|
1594
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1606
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_canvas_list") },
|
|
1595
1607
|
],
|
|
1596
1608
|
};
|
|
1597
1609
|
});
|
|
@@ -1608,7 +1620,7 @@ registerPluginTool("diviops_canvas_get", {
|
|
|
1608
1620
|
const result = await wp.requestEnveloped(`/canvas/get/${canvas_post_id}`);
|
|
1609
1621
|
return {
|
|
1610
1622
|
content: [
|
|
1611
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1623
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_canvas_get") },
|
|
1612
1624
|
],
|
|
1613
1625
|
};
|
|
1614
1626
|
});
|
|
@@ -1649,7 +1661,7 @@ registerPluginTool("diviops_canvas_update", {
|
|
|
1649
1661
|
});
|
|
1650
1662
|
return {
|
|
1651
1663
|
content: [
|
|
1652
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1664
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_canvas_update") },
|
|
1653
1665
|
],
|
|
1654
1666
|
};
|
|
1655
1667
|
});
|
|
@@ -1679,7 +1691,7 @@ registerPluginTool("diviops_canvas_duplicate", {
|
|
|
1679
1691
|
});
|
|
1680
1692
|
return {
|
|
1681
1693
|
content: [
|
|
1682
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1694
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_canvas_duplicate") },
|
|
1683
1695
|
],
|
|
1684
1696
|
};
|
|
1685
1697
|
});
|
|
@@ -1702,12 +1714,12 @@ registerPluginTool("diviops_canvas_delete", {
|
|
|
1702
1714
|
});
|
|
1703
1715
|
return {
|
|
1704
1716
|
content: [
|
|
1705
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
1717
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_canvas_delete") },
|
|
1706
1718
|
],
|
|
1707
1719
|
};
|
|
1708
1720
|
});
|
|
1709
1721
|
// ── WP-CLI ──────────────────────────────────────────────────────────
|
|
1710
|
-
|
|
1722
|
+
registerLocalTool("diviops_meta_wp_cli", {
|
|
1711
1723
|
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, `plugin update` from authenticated sources), 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. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }. Success payload: { stdout: string, stderr: string, exit_code: 0 }. Four failure modes converge on 'meta_wp_cli.command_failed' with error.data = { exit_code: number | null, stdout: string, stderr: string }: (a) numeric exit_code — wp-cli ran and exited non-zero; stdout/stderr are raw streams verbatim. (b) exit_code=null and message starts with 'wp-cli command terminated:' — execFile launched the child but it was killed (timeout or signal); stdout/stderr carry whatever streamed before the kill. (c) exit_code=null and message starts with 'wp-cli could not spawn:' — the OS refused to start the child (ENOENT/EACCES/EPERM); child never ran, stdout/stderr are empty. (d) exit_code=null and message is the rejection reason — pre-execution rejection by the allowlist / FS validator; rejection reason synthesized into error.data.stderr because the child never ran. A missing wp-cli configuration surfaces as 'meta_wp_cli.not_configured'. stdout is always passed through as a string (no server-side JSON parse) — pass --format=json and parse on the caller side when you want structured output.",
|
|
1712
1724
|
inputSchema: {
|
|
1713
1725
|
command: z
|
|
@@ -1803,7 +1815,7 @@ server.registerTool("diviops_meta_wp_cli", {
|
|
|
1803
1815
|
});
|
|
1804
1816
|
return {
|
|
1805
1817
|
content: [
|
|
1806
|
-
{ type: "text", text: serializeEnvelope(response) },
|
|
1818
|
+
{ type: "text", text: serializeEnvelope(response, "diviops_meta_wp_cli") },
|
|
1807
1819
|
],
|
|
1808
1820
|
};
|
|
1809
1821
|
});
|
|
@@ -1894,7 +1906,7 @@ function failScfCommand(result, args) {
|
|
|
1894
1906
|
command: [...args],
|
|
1895
1907
|
});
|
|
1896
1908
|
}
|
|
1897
|
-
|
|
1909
|
+
registerLocalTool("diviops_scf_status", {
|
|
1898
1910
|
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. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; success payload is { stdout: string, stderr: string }. wp-cli failures map to 'scf.command_failed' with `error.data = { exit_code, stdout, stderr, failure_kind, command }` (four failure_kind branches: 'exited'/'killed'/'spawn_failed'/'rejected' — see tools.md). Missing wp-cli configuration surfaces as 'scf.not_configured'.",
|
|
1899
1911
|
inputSchema: {
|
|
1900
1912
|
type: z
|
|
@@ -1922,11 +1934,11 @@ server.registerTool("diviops_scf_status", {
|
|
|
1922
1934
|
});
|
|
1923
1935
|
return {
|
|
1924
1936
|
content: [
|
|
1925
|
-
{ type: "text", text: serializeEnvelope(response) },
|
|
1937
|
+
{ type: "text", text: serializeEnvelope(response, "diviops_scf_status") },
|
|
1926
1938
|
],
|
|
1927
1939
|
};
|
|
1928
1940
|
});
|
|
1929
|
-
|
|
1941
|
+
registerLocalTool("diviops_scf_export", {
|
|
1930
1942
|
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. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; success payload is { stdout: string, stderr: string }. Pre-wp-cli input rejections (neither/both of `dir`/`stdout`) return code 'invalid_input' with `error.data` documenting the failed fields. wp-cli failures map to 'scf.command_failed' (same shape as scf_status). Missing wp-cli configuration surfaces as 'scf.not_configured'.",
|
|
1931
1943
|
inputSchema: {
|
|
1932
1944
|
dir: z
|
|
@@ -1980,11 +1992,11 @@ server.registerTool("diviops_scf_export", {
|
|
|
1980
1992
|
});
|
|
1981
1993
|
return {
|
|
1982
1994
|
content: [
|
|
1983
|
-
{ type: "text", text: serializeEnvelope(response) },
|
|
1995
|
+
{ type: "text", text: serializeEnvelope(response, "diviops_scf_export") },
|
|
1984
1996
|
],
|
|
1985
1997
|
};
|
|
1986
1998
|
});
|
|
1987
|
-
|
|
1999
|
+
registerLocalTool("diviops_scf_import", {
|
|
1988
2000
|
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>`. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; success payload is { stdout: string, stderr: string }. wp-cli failures (missing/unreadable file, malformed JSON, allowlist or FS-validator rejection) map to 'scf.command_failed' with `error.data = { exit_code, stdout, stderr, failure_kind, command }`. Missing wp-cli configuration surfaces as 'scf.not_configured'.",
|
|
1989
2001
|
inputSchema: {
|
|
1990
2002
|
file: z
|
|
@@ -2004,11 +2016,11 @@ server.registerTool("diviops_scf_import", {
|
|
|
2004
2016
|
});
|
|
2005
2017
|
return {
|
|
2006
2018
|
content: [
|
|
2007
|
-
{ type: "text", text: serializeEnvelope(response) },
|
|
2019
|
+
{ type: "text", text: serializeEnvelope(response, "diviops_scf_import") },
|
|
2008
2020
|
],
|
|
2009
2021
|
};
|
|
2010
2022
|
});
|
|
2011
|
-
|
|
2023
|
+
registerLocalTool("diviops_scf_sync", {
|
|
2012
2024
|
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`. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; success payload is { dry_run: boolean, stdout: string, stderr: string }. NOTE: `dry_run` is passed through as wp-cli's `--dry-run` flag — the upstream output shape is wp-cli's plain-text summary, NOT the standard `data.plan = { summary, changes[] }` shape used by plugin-routed `dry_run` tools. The `dry_run` boolean is reflected in the success payload so callers can branch without re-checking input args, but the SCF-on-disk preview is what wp-cli produced. wp-cli failures map to 'scf.command_failed'; missing wp-cli configuration surfaces as 'scf.not_configured'.",
|
|
2013
2025
|
inputSchema: {
|
|
2014
2026
|
type: z
|
|
@@ -2047,11 +2059,11 @@ server.registerTool("diviops_scf_sync", {
|
|
|
2047
2059
|
});
|
|
2048
2060
|
return {
|
|
2049
2061
|
content: [
|
|
2050
|
-
{ type: "text", text: serializeEnvelope(response) },
|
|
2062
|
+
{ type: "text", text: serializeEnvelope(response, "diviops_scf_sync") },
|
|
2051
2063
|
],
|
|
2052
2064
|
};
|
|
2053
2065
|
});
|
|
2054
|
-
|
|
2066
|
+
registerLocalTool("diviops_scf_field_group_list", {
|
|
2055
2067
|
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. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; success payload is `Array<{ ID, post_name, post_title, post_status, post_modified }>` parsed from wp-cli's JSON output (or an empty array on no results). wp-cli failures map to 'scf.command_failed'; missing wp-cli configuration surfaces as 'scf.not_configured'.",
|
|
2056
2068
|
annotations: { idempotentHint: true },
|
|
2057
2069
|
_meta: { idempotent: "true" },
|
|
@@ -2082,11 +2094,11 @@ server.registerTool("diviops_scf_field_group_list", {
|
|
|
2082
2094
|
});
|
|
2083
2095
|
return {
|
|
2084
2096
|
content: [
|
|
2085
|
-
{ type: "text", text: serializeEnvelope(response) },
|
|
2097
|
+
{ type: "text", text: serializeEnvelope(response, "diviops_scf_field_group_list") },
|
|
2086
2098
|
],
|
|
2087
2099
|
};
|
|
2088
2100
|
});
|
|
2089
|
-
|
|
2101
|
+
registerLocalTool("diviops_scf_field_group_get", {
|
|
2090
2102
|
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`. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; success payload is the parsed `wp post get --format=json` object. Unresolvable key (no row in the `acf-field-group` post type and not a numeric ID that wp-cli accepts) returns code 'not_found' with hint pointing to diviops_scf_field_group_list. wp-cli failures map to 'scf.command_failed'; missing wp-cli configuration surfaces as 'scf.not_configured'.",
|
|
2091
2103
|
inputSchema: {
|
|
2092
2104
|
key: z
|
|
@@ -2157,12 +2169,12 @@ server.registerTool("diviops_scf_field_group_get", {
|
|
|
2157
2169
|
});
|
|
2158
2170
|
return {
|
|
2159
2171
|
content: [
|
|
2160
|
-
{ type: "text", text: serializeEnvelope(response) },
|
|
2172
|
+
{ type: "text", text: serializeEnvelope(response, "diviops_scf_field_group_get") },
|
|
2161
2173
|
],
|
|
2162
2174
|
};
|
|
2163
2175
|
});
|
|
2164
2176
|
// ── Connection ──────────────────────────────────────────────────────
|
|
2165
|
-
|
|
2177
|
+
registerLocalTool("diviops_meta_ping", {
|
|
2166
2178
|
description: "Test the connection to the WordPress site and verify the Divi MCP plugin is active. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; success payload is { connected: true, message: \"Connected to Divi <version>\" } and connection failure surfaces as { ok: false, error: { code: 'wp_error', message } } with the underlying transport message preserved.",
|
|
2167
2179
|
annotations: { idempotentHint: true },
|
|
2168
2180
|
_meta: { idempotent: "true" },
|
|
@@ -2176,11 +2188,11 @@ server.registerTool("diviops_meta_ping", {
|
|
|
2176
2188
|
});
|
|
2177
2189
|
return {
|
|
2178
2190
|
content: [
|
|
2179
|
-
{ type: "text", text: serializeEnvelope(response) },
|
|
2191
|
+
{ type: "text", text: serializeEnvelope(response, "diviops_meta_ping") },
|
|
2180
2192
|
],
|
|
2181
2193
|
};
|
|
2182
2194
|
});
|
|
2183
|
-
|
|
2195
|
+
registerLocalTool("diviops_meta_info", {
|
|
2184
2196
|
description: "Returns DiviOps MCP server identity, version, license type, and available capabilities. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }.",
|
|
2185
2197
|
annotations: { idempotentHint: true },
|
|
2186
2198
|
_meta: { idempotent: "true" },
|
|
@@ -2207,7 +2219,7 @@ server.registerTool("diviops_meta_info", {
|
|
|
2207
2219
|
}));
|
|
2208
2220
|
return {
|
|
2209
2221
|
content: [
|
|
2210
|
-
{ type: "text", text: serializeEnvelope(response) },
|
|
2222
|
+
{ type: "text", text: serializeEnvelope(response, "diviops_meta_info") },
|
|
2211
2223
|
],
|
|
2212
2224
|
};
|
|
2213
2225
|
});
|
|
@@ -2305,7 +2317,7 @@ function loadTemplates() {
|
|
|
2305
2317
|
}
|
|
2306
2318
|
const templates = loadTemplates();
|
|
2307
2319
|
// Register a list tool so Claude can discover available templates
|
|
2308
|
-
|
|
2320
|
+
registerLocalTool("diviops_template_list", {
|
|
2309
2321
|
description: "List available Divi page section templates. Each template contains verified block markup patterns that can be used as a base for page generation. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; success payload is an array of { name, description, customizable, requires_css }.",
|
|
2310
2322
|
annotations: { idempotentHint: true },
|
|
2311
2323
|
_meta: { idempotent: "true" },
|
|
@@ -2318,11 +2330,11 @@ server.registerTool("diviops_template_list", {
|
|
|
2318
2330
|
})));
|
|
2319
2331
|
return {
|
|
2320
2332
|
content: [
|
|
2321
|
-
{ type: "text", text: serializeEnvelope(response) },
|
|
2333
|
+
{ type: "text", text: serializeEnvelope(response, "diviops_template_list") },
|
|
2322
2334
|
],
|
|
2323
2335
|
};
|
|
2324
2336
|
});
|
|
2325
|
-
|
|
2337
|
+
registerLocalTool("diviops_template_get", {
|
|
2326
2338
|
description: "Get a specific Divi template with verified block markup, customizable variables, and usage notes. Use this to generate pages based on proven patterns. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }. Missing template names return ok:false with code 'not_found' and error.data.available: string[] listing the registered template names.",
|
|
2327
2339
|
inputSchema: {
|
|
2328
2340
|
template_name: z
|
|
@@ -2341,7 +2353,7 @@ server.registerTool("diviops_template_get", {
|
|
|
2341
2353
|
});
|
|
2342
2354
|
return {
|
|
2343
2355
|
content: [
|
|
2344
|
-
{ type: "text", text: serializeEnvelope(response) },
|
|
2356
|
+
{ type: "text", text: serializeEnvelope(response, "diviops_template_get") },
|
|
2345
2357
|
],
|
|
2346
2358
|
};
|
|
2347
2359
|
});
|
|
@@ -2369,7 +2381,7 @@ registerPluginTool("diviops_variable_list", {
|
|
|
2369
2381
|
const result = await wp.requestEnveloped("/variable/list", { params });
|
|
2370
2382
|
return {
|
|
2371
2383
|
content: [
|
|
2372
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
2384
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_variable_list") },
|
|
2373
2385
|
],
|
|
2374
2386
|
};
|
|
2375
2387
|
});
|
|
@@ -2443,7 +2455,7 @@ registerPluginTool("diviops_variable_create", {
|
|
|
2443
2455
|
});
|
|
2444
2456
|
return {
|
|
2445
2457
|
content: [
|
|
2446
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
2458
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_variable_create") },
|
|
2447
2459
|
],
|
|
2448
2460
|
};
|
|
2449
2461
|
});
|
|
@@ -2613,7 +2625,7 @@ registerPluginTool("diviops_variable_create_fluid_system", {
|
|
|
2613
2625
|
});
|
|
2614
2626
|
return {
|
|
2615
2627
|
content: [
|
|
2616
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
2628
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_variable_create_fluid_system") },
|
|
2617
2629
|
],
|
|
2618
2630
|
};
|
|
2619
2631
|
});
|
|
@@ -2643,7 +2655,7 @@ registerPluginTool("diviops_variable_delete", {
|
|
|
2643
2655
|
});
|
|
2644
2656
|
return {
|
|
2645
2657
|
content: [
|
|
2646
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
2658
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_variable_delete") },
|
|
2647
2659
|
],
|
|
2648
2660
|
};
|
|
2649
2661
|
});
|
|
@@ -2655,7 +2667,7 @@ registerPluginTool("diviops_variable_scan_orphans", {
|
|
|
2655
2667
|
const result = await wp.requestEnveloped("/variable/scan-orphans");
|
|
2656
2668
|
return {
|
|
2657
2669
|
content: [
|
|
2658
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
2670
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_variable_scan_orphans") },
|
|
2659
2671
|
],
|
|
2660
2672
|
};
|
|
2661
2673
|
});
|
|
@@ -2674,7 +2686,7 @@ registerPluginTool("diviops_variable_used_on_page", {
|
|
|
2674
2686
|
const result = await wp.requestEnveloped(`/variable/used-on-page/${post_id}`);
|
|
2675
2687
|
return {
|
|
2676
2688
|
content: [
|
|
2677
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
2689
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_variable_used_on_page") },
|
|
2678
2690
|
],
|
|
2679
2691
|
};
|
|
2680
2692
|
});
|
|
@@ -2720,7 +2732,7 @@ registerPluginTool("diviops_meta_flush_cache", {
|
|
|
2720
2732
|
});
|
|
2721
2733
|
return {
|
|
2722
2734
|
content: [
|
|
2723
|
-
{ type: "text", text: serializeEnvelope(result) },
|
|
2735
|
+
{ type: "text", text: serializeEnvelope(result, "diviops_meta_flush_cache") },
|
|
2724
2736
|
],
|
|
2725
2737
|
};
|
|
2726
2738
|
});
|