@diviops/mcp-server 0.2.11 → 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/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" },
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diviops/mcp-server",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "description": "MCP server exposing Divi 5 Visual Builder as tools for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",