@diviops/mcp-server 0.2.10 → 0.2.12
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 +1 -1
- package/dist/compatibility.d.ts +1 -1
- package/dist/compatibility.js +1 -1
- package/dist/index.js +36 -5
- package/dist/validate-attrs.d.ts +27 -0
- package/dist/validate-attrs.js +63 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ The server connects via standard WordPress REST API and works with any environme
|
|
|
141
141
|
| `diviops_save_to_library` | Save block markup to Divi Library |
|
|
142
142
|
| `diviops_update_tb_layout` | Update a Theme Builder layout's block markup |
|
|
143
143
|
| `diviops_create_tb_template` | Create Theme Builder template with header/footer and conditions |
|
|
144
|
-
| `diviops_create_variable` | Create a design token variable. For `type=numbers` fluid tokens, pass `min`+`max` shorthand (anchors default to 320px/1920px) or explicit `targets` like `{"320px":"20px","1920px":"60px"}` — server generates arithmetically-correct `clamp()` instead of hand-written math that silently under-reaches the stated max.
|
|
144
|
+
| `diviops_create_variable` | Create a design token variable. For `type=numbers` fluid tokens, pass `min`+`max` shorthand (anchors default to 320px/1920px) or explicit `targets` like `{"320px":"20px","1920px":"60px"}` — server generates arithmetically-correct `clamp()` instead of hand-written math that silently under-reaches the stated max. All-px inputs emit px (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, e.g. `10` for `html { font-size: 62.5% }`, `20` for `html { font-size: 20px }`) |
|
|
145
145
|
| `diviops_delete_variable` | Delete a variable by ID. Returns HTTP 409 when live references exist unless `force=true` (use `diviops_variables_scan_orphans` to find reference locations). Returns HTTP 403 for Divi's customizer-bound defaults (`gcid-primary-color`, `gcid-secondary-color`, `gcid-heading-color`, `gcid-body-color`, `gcid-link-color` — managed via WP Customizer) |
|
|
146
146
|
| `diviops_create_canvas` | Create a canvas page |
|
|
147
147
|
| `diviops_update_canvas` | Update canvas content |
|
package/dist/compatibility.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Version compatibility between MCP server and WP plugin.
|
|
3
3
|
*/
|
|
4
4
|
/** Minimum WP plugin version this server requires. */
|
|
5
|
-
export declare const MIN_PLUGIN_VERSION = "1.0.0-beta.
|
|
5
|
+
export declare const MIN_PLUGIN_VERSION = "1.0.0-beta.30";
|
|
6
6
|
/**
|
|
7
7
|
* Compare two semver-like version strings (supports pre-release tags).
|
|
8
8
|
*
|
package/dist/compatibility.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Version compatibility between MCP server and WP plugin.
|
|
3
3
|
*/
|
|
4
4
|
/** Minimum WP plugin version this server requires. */
|
|
5
|
-
export const MIN_PLUGIN_VERSION = '1.0.0-beta.
|
|
5
|
+
export const MIN_PLUGIN_VERSION = '1.0.0-beta.30';
|
|
6
6
|
/**
|
|
7
7
|
* Compare two semver-like version strings (supports pre-release tags).
|
|
8
8
|
*
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { z } from "zod";
|
|
|
14
14
|
import { WPClient } from "./wp-client.js";
|
|
15
15
|
import { optimizeSchema } from "./schema-optimizer.js";
|
|
16
16
|
import { createWpCli } from "./wp-cli.js";
|
|
17
|
+
import { findForeignVarRefs, scanAttrsForForeignVarRefs, isolationErrorResult, } from "./validate-attrs.js";
|
|
17
18
|
import { readFileSync, readdirSync } from "fs";
|
|
18
19
|
import { join, dirname } from "path";
|
|
19
20
|
import { fileURLToPath } from "url";
|
|
@@ -240,6 +241,9 @@ server.registerTool("diviops_update_page_content", {
|
|
|
240
241
|
.describe("Full page content in WordPress block markup format (<!-- wp:divi/section -->...<!-- /wp:divi/section -->)"),
|
|
241
242
|
},
|
|
242
243
|
}, async ({ page_id, content }) => {
|
|
244
|
+
const hits = findForeignVarRefs(content, "content");
|
|
245
|
+
if (hits.length > 0)
|
|
246
|
+
return isolationErrorResult("diviops_update_page_content", hits);
|
|
243
247
|
const result = await wp.request(`/page/${page_id}/content`, {
|
|
244
248
|
method: "POST",
|
|
245
249
|
body: { content },
|
|
@@ -296,6 +300,9 @@ server.registerTool("diviops_append_section", {
|
|
|
296
300
|
.describe('Where to insert: "start" or "end" (default)'),
|
|
297
301
|
},
|
|
298
302
|
}, async ({ page_id, content, position }) => {
|
|
303
|
+
const hits = findForeignVarRefs(content, "content");
|
|
304
|
+
if (hits.length > 0)
|
|
305
|
+
return isolationErrorResult("diviops_append_section", hits);
|
|
299
306
|
const result = await wp.request(`/page/${page_id}/append`, {
|
|
300
307
|
method: "POST",
|
|
301
308
|
body: { content, position: position ?? "end" },
|
|
@@ -330,6 +337,9 @@ server.registerTool("diviops_replace_section", {
|
|
|
330
337
|
.describe("Which match to target (1-based, default: 1)"),
|
|
331
338
|
},
|
|
332
339
|
}, async ({ page_id, label, match_text, content, occurrence }) => {
|
|
340
|
+
const hits = findForeignVarRefs(content, "content");
|
|
341
|
+
if (hits.length > 0)
|
|
342
|
+
return isolationErrorResult("diviops_replace_section", hits);
|
|
333
343
|
const body = { content, occurrence };
|
|
334
344
|
if (label)
|
|
335
345
|
body.label = label;
|
|
@@ -443,6 +453,9 @@ server.registerTool("diviops_update_module", {
|
|
|
443
453
|
.describe("Attribute paths (dot notation) and their new values"),
|
|
444
454
|
},
|
|
445
455
|
}, async ({ page_id, label, match_text, auto_index, occurrence, attrs }) => {
|
|
456
|
+
const hits = scanAttrsForForeignVarRefs(attrs);
|
|
457
|
+
if (hits.length > 0)
|
|
458
|
+
return isolationErrorResult("diviops_update_module", hits);
|
|
446
459
|
const body = { attrs };
|
|
447
460
|
if (auto_index)
|
|
448
461
|
body.auto_index = auto_index;
|
|
@@ -552,6 +565,11 @@ server.registerTool("diviops_create_page", {
|
|
|
552
565
|
.describe("Post status"),
|
|
553
566
|
},
|
|
554
567
|
}, async ({ title, content, status }) => {
|
|
568
|
+
if (content) {
|
|
569
|
+
const hits = findForeignVarRefs(content, "content");
|
|
570
|
+
if (hits.length > 0)
|
|
571
|
+
return isolationErrorResult("diviops_create_page", hits);
|
|
572
|
+
}
|
|
555
573
|
const result = await wp.request("/page/create", {
|
|
556
574
|
method: "POST",
|
|
557
575
|
body: { title, content: content ?? "", status: status ?? "draft" },
|
|
@@ -1285,7 +1303,7 @@ server.registerTool("diviops_list_variables", {
|
|
|
1285
1303
|
};
|
|
1286
1304
|
});
|
|
1287
1305
|
server.registerTool("diviops_create_variable", {
|
|
1288
|
-
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.
|
|
1306
|
+
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.',
|
|
1289
1307
|
inputSchema: {
|
|
1290
1308
|
type: z
|
|
1291
1309
|
.enum(["colors", "numbers", "strings", "images", "links", "fonts"])
|
|
@@ -1304,20 +1322,29 @@ server.registerTool("diviops_create_variable", {
|
|
|
1304
1322
|
min: z
|
|
1305
1323
|
.string()
|
|
1306
1324
|
.optional()
|
|
1307
|
-
.describe('Fluid minimum value
|
|
1325
|
+
.describe('Fluid minimum value (e.g. "20px" or "1.25rem"). Paired with max. Anchors default to 320px/1920px. Rem inputs require explicit opt-in via output_unit or root_font_size_px. type="numbers" only.'),
|
|
1308
1326
|
max: z
|
|
1309
1327
|
.string()
|
|
1310
1328
|
.optional()
|
|
1311
|
-
.describe('Fluid maximum value
|
|
1329
|
+
.describe('Fluid maximum value (e.g. "60px" or "3.75rem"). Paired with min.'),
|
|
1312
1330
|
targets: z
|
|
1313
1331
|
.record(z.string(), z.string())
|
|
1314
1332
|
.refine((m) => !m || Object.keys(m).length === 2, {
|
|
1315
1333
|
message: "targets must contain exactly 2 viewport entries",
|
|
1316
1334
|
})
|
|
1317
1335
|
.optional()
|
|
1318
|
-
.describe('Explicit two-anchor fluid spec, px only. Example: {"320px":"20px","1920px":"60px"} → clamp(20px, 12px + 2.5vw, 60px). Exactly 2 entries required. type="numbers" only. Mutually exclusive with min/max.'),
|
|
1336
|
+
.describe('Explicit two-anchor fluid spec, object keyed by viewport width (px only). Example: {"320px":"20px","1920px":"60px"} → clamp(20px, 12px + 2.5vw, 60px). Exactly 2 entries required. type="numbers" only. Mutually exclusive with min/max. Rem values require explicit opt-in via output_unit or root_font_size_px.'),
|
|
1337
|
+
output_unit: z
|
|
1338
|
+
.enum(["rem", "px"])
|
|
1339
|
+
.optional()
|
|
1340
|
+
.describe('Unit for generated clamp formula. Omit for all-px inputs (safe default — emits px, root-agnostic). Pass "rem" to emit rem (accepts the 1rem=16px assumption unless root_font_size_px is also passed); required when inputs include rem unless root_font_size_px is passed. Pass "px" to force px output regardless of input unit.'),
|
|
1341
|
+
root_font_size_px: z
|
|
1342
|
+
.number()
|
|
1343
|
+
.positive()
|
|
1344
|
+
.optional()
|
|
1345
|
+
.describe("Site's root font-size in px (positive number), used for correct rem↔px conversion in the generated clamp() formula. Defaults to 16 (standard browser default) when omitted. Pass explicitly for sites that customize `html { font-size }` (e.g. 10 for `html { font-size: 62.5% }`, 20 for `html { font-size: 20px }`). Also counts as an opt-in signal for rem emission — passing it alone (without output_unit) implies rem output. Only applies when min/max/targets is used."),
|
|
1319
1346
|
},
|
|
1320
|
-
}, async ({ type, id, label, value, min, max, targets }) => {
|
|
1347
|
+
}, async ({ type, id, label, value, min, max, targets, output_unit, root_font_size_px, }) => {
|
|
1321
1348
|
const body = { type, label };
|
|
1322
1349
|
if (value !== undefined)
|
|
1323
1350
|
body.value = value;
|
|
@@ -1329,6 +1356,10 @@ server.registerTool("diviops_create_variable", {
|
|
|
1329
1356
|
body.max = max;
|
|
1330
1357
|
if (targets !== undefined)
|
|
1331
1358
|
body.targets = targets;
|
|
1359
|
+
if (output_unit !== undefined)
|
|
1360
|
+
body.output_unit = output_unit;
|
|
1361
|
+
if (root_font_size_px !== undefined)
|
|
1362
|
+
body.root_font_size_px = root_font_size_px;
|
|
1332
1363
|
const result = await wp.request("/variable/create", {
|
|
1333
1364
|
method: "POST",
|
|
1334
1365
|
body,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolation-rule validator: bans cross-system var(--alias) refs in Divi
|
|
3
|
+
* module attrs. See SKILL.md rule 8 and references/module-formats.md
|
|
4
|
+
* §"Design Token References in Attrs".
|
|
5
|
+
*
|
|
6
|
+
* CSS spec: var(--undeclared-name) with no fallback falls through to the
|
|
7
|
+
* property's initial value (0 for padding, browser default for color).
|
|
8
|
+
* Tool reports success, renderer emits ref as-is, page silently breaks.
|
|
9
|
+
*
|
|
10
|
+
* Only var() refs to gcid-* / gvid-* pass — Divi-owned namespaces that
|
|
11
|
+
* auto-resolve via :root. Any other alias is rejected.
|
|
12
|
+
*/
|
|
13
|
+
export interface ForeignVarRef {
|
|
14
|
+
alias: string;
|
|
15
|
+
snippet: string;
|
|
16
|
+
location?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function findForeignVarRefs(value: unknown, location?: string): ForeignVarRef[];
|
|
19
|
+
export declare function scanAttrsForForeignVarRefs(attrs: Record<string, unknown>): ForeignVarRef[];
|
|
20
|
+
export declare function formatIsolationError(tool: string, hits: ForeignVarRef[]): string;
|
|
21
|
+
export declare function isolationErrorResult(tool: string, hits: ForeignVarRef[]): {
|
|
22
|
+
content: {
|
|
23
|
+
type: "text";
|
|
24
|
+
text: string;
|
|
25
|
+
}[];
|
|
26
|
+
isError: boolean;
|
|
27
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolation-rule validator: bans cross-system var(--alias) refs in Divi
|
|
3
|
+
* module attrs. See SKILL.md rule 8 and references/module-formats.md
|
|
4
|
+
* §"Design Token References in Attrs".
|
|
5
|
+
*
|
|
6
|
+
* CSS spec: var(--undeclared-name) with no fallback falls through to the
|
|
7
|
+
* property's initial value (0 for padding, browser default for color).
|
|
8
|
+
* Tool reports success, renderer emits ref as-is, page silently breaks.
|
|
9
|
+
*
|
|
10
|
+
* Only var() refs to gcid-* / gvid-* pass — Divi-owned namespaces that
|
|
11
|
+
* auto-resolve via :root. Any other alias is rejected.
|
|
12
|
+
*/
|
|
13
|
+
const VAR_REF_RE = /var\(\s*--([A-Za-z_][A-Za-z0-9_-]*)/g;
|
|
14
|
+
const ALLOWED_PREFIXES = ["gcid-", "gvid-"];
|
|
15
|
+
export function findForeignVarRefs(value, location) {
|
|
16
|
+
if (typeof value !== "string" || value.length === 0)
|
|
17
|
+
return [];
|
|
18
|
+
const hits = [];
|
|
19
|
+
VAR_REF_RE.lastIndex = 0;
|
|
20
|
+
let m;
|
|
21
|
+
while ((m = VAR_REF_RE.exec(value)) !== null) {
|
|
22
|
+
const alias = m[1];
|
|
23
|
+
if (ALLOWED_PREFIXES.some((p) => alias.startsWith(p)))
|
|
24
|
+
continue;
|
|
25
|
+
hits.push({ alias, snippet: `var(--${alias})`, location });
|
|
26
|
+
}
|
|
27
|
+
return hits;
|
|
28
|
+
}
|
|
29
|
+
export function scanAttrsForForeignVarRefs(attrs) {
|
|
30
|
+
const hits = [];
|
|
31
|
+
for (const [path, value] of Object.entries(attrs)) {
|
|
32
|
+
hits.push(...findForeignVarRefs(value, path));
|
|
33
|
+
}
|
|
34
|
+
return hits;
|
|
35
|
+
}
|
|
36
|
+
export function formatIsolationError(tool, hits) {
|
|
37
|
+
const uniq = new Map();
|
|
38
|
+
for (const h of hits) {
|
|
39
|
+
const k = `${h.snippet}::${h.location ?? ""}`;
|
|
40
|
+
if (!uniq.has(k))
|
|
41
|
+
uniq.set(k, h);
|
|
42
|
+
}
|
|
43
|
+
const lines = [
|
|
44
|
+
`Isolation-rule violation in ${tool}: module attrs cannot reference non-Divi CSS aliases.`,
|
|
45
|
+
"",
|
|
46
|
+
"Offending refs:",
|
|
47
|
+
...[...uniq.values()].map((h) => ` - ${h.snippet}${h.location ? ` (at ${h.location})` : ""}`),
|
|
48
|
+
"",
|
|
49
|
+
"Allowed: var(--gcid-*) and var(--gvid-*) (Divi-owned, auto-emitted to :root).",
|
|
50
|
+
'Canonical form: $variable({"type":"content","value":{"name":"gvid-your-token","settings":{}}})$',
|
|
51
|
+
"",
|
|
52
|
+
"Fix: register the token inside Divi Variable Manager (readable ID is fine, e.g. gvid-oa-space-3) and reference via $variable({...})$. See SKILL.md rule 8 / references/module-formats.md#design-token-references-in-attrs-canonical-variable-only.",
|
|
53
|
+
];
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
56
|
+
export function isolationErrorResult(tool, hits) {
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{ type: "text", text: formatIsolationError(tool, hits) },
|
|
60
|
+
],
|
|
61
|
+
isError: true,
|
|
62
|
+
};
|
|
63
|
+
}
|