@diviops/mcp-server 0.2.19 → 0.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/wp-client.js +66 -21
  2. package/package.json +1 -1
package/dist/wp-client.js CHANGED
@@ -6,43 +6,88 @@
6
6
  */
7
7
  import { MIN_PLUGIN_VERSION, compareVersions, } from './compatibility.js';
8
8
  /**
9
- * Normalize over-escaped variable-token quote sequences inside `$variable(...)$`
10
- * token regions only.
9
+ * Normalize quote-escape pathologies inside `$variable(...)$` token regions only.
11
10
  *
12
- * Divi block-attrs JSON uses `\"` (2-byte) for inner quotes inside Divi variable
13
- * tokens. Two over-escaped forms can leak in via callers that round-trip
14
- * pre-existing broken bytes:
15
- * - `\u0022` (11 bytes literal) — the mass-corruption form (backslash
16
- * itself unicode-escaped, observed in the wild on Divi 5.3.x sites)
17
- * - `\\u0022` (7 bytes literal) — produced when a caller passes the
18
- * 6-byte `"` form through an extra JSON-encoding layer
11
+ * Divi block-attrs JSON uses `\"` (2-byte: backslash + quote) for inner quotes
12
+ * inside variable token payloads. Three pathological forms can leak in through
13
+ * callers and silently break the WP block parser at write time.
19
14
  *
20
- * Both decode to the same value after Divi's resolver, but cause render asymmetry
21
- * across attr paths (`background.color`, `spacing.margin`, `border.color`,
22
- * `layout.columnGap` silently fail to resolve). We normalize defensively so any
23
- * write clean or pre-broken settles on canonical bytes.
15
+ * Over-escape (existing #395/#396 fix). Two forms produced when callers
16
+ * round-trip pre-existing broken bytes:
17
+ * - `\u005cu0022` (11 bytes literal) the
18
+ * mass-corruption form (backslash itself unicode-escaped, observed in the
19
+ * wild on Divi 5.3.x sites)
20
+ * - `\\u0022` (7 bytes literal: 2 backslashes + `u0022`) — produced when
21
+ * a caller passes the 6-byte unicode-escape form through an extra
22
+ * JSON-encoding layer
23
+ * These cause render-only failure: the resolver can't decode the token, and
24
+ * attr paths like `background.color`, `spacing.margin`, `border.color`,
25
+ * `layout.columnGap` silently fall through to defaults (or leak literal
26
+ * `0022` into emitted CSS).
27
+ *
28
+ * Under-escape (#409 fix). One form produced when an agent transcribes
29
+ * `get_section` markup (which emits inner quotes as `"` HTML entities) and
30
+ * a layer in the agent → MCP → WP pipeline strips one level of escaping:
31
+ * - bare `"` (1 byte) — the inner quote loses its `\` prefix and prematurely
32
+ * terminates the OUTER block-attrs string at parse time. The WP block
33
+ * parser then silently drops ALL attrs from the affected module. Section
34
+ * appears to save (`success: true`) but renders empty / broken.
35
+ *
36
+ * We normalize defensively so any write — clean, pre-broken, or
37
+ * agent-transcribed — settles on canonical 2-byte `\"`.
38
+ *
39
+ * Order matters: collapse over-escapes first, then escape under-escapes. The
40
+ * negative lookbehind on the under-escape rule skips `\"` produced by the
41
+ * over-escape pass (and any already-canonical input). Idempotent.
24
42
  *
25
43
  * Scope is intentionally narrow: rewrites only happen inside `$variable(...)$`
26
44
  * regions (Divi's actual resolver boundary). Bytes outside those regions —
27
45
  * arbitrary user text, code samples, string-variable values that happen to
28
- * contain `\u0022` — are left untouched.
46
+ * contain `\u005cu0022`, `\\u0022`, or bare `"` — are left untouched.
29
47
  */
30
48
  function normalizeQuoteEscapes(s) {
31
- return s.replace(/\$variable\([^$]*?\)\$/g, (token) => token.replace(/(?:\\u005cu0022|\\\\u0022)/g, '\\"'));
49
+ return s.replace(/\$variable\([^$]*?\)\$/g, (token) => {
50
+ // Pass 1: collapse over-escaped forms (#395/#396) to canonical \"
51
+ let normalized = token.replace(/(?:\\u005cu0022|\\\\u0022)/g, '\\"');
52
+ // Pass 2: escape any bare " (#409) to canonical \" — negative lookbehind
53
+ // skips properly-escaped quotes produced by Pass 1 or already canonical.
54
+ normalized = normalized.replace(/(?<!\\)"/g, '\\"');
55
+ return normalized;
56
+ });
32
57
  }
33
- function normalizeBody(value) {
34
- if (typeof value === 'string')
35
- return normalizeQuoteEscapes(value);
58
+ /**
59
+ * Body keys whose values (and descendants) carry Divi block markup or block
60
+ * attribute trees, where `$variable(...)$` token-region normalization is the
61
+ * intended behavior. Strings reachable only through other top-level keys
62
+ * — variable values, labels, match-text predicates, descriptions, etc.
63
+ * — are passed through verbatim so a literal `$variable({"x":"y"})$`
64
+ * docs example in a `string_variable_value` is preserved (#409 review:
65
+ * Codex-flagged regression — without this scoping, the bare-quote pass would
66
+ * silently rewrite literal token-shaped substrings in user-prose fields).
67
+ */
68
+ const BLOCK_CONTENT_KEYS = new Set([
69
+ 'content', // update_page_content, render_preview, validate_blocks,
70
+ // append_section, replace_section, update_tb_layout,
71
+ // save_to_library, create_page
72
+ 'attrs', // update_module — attr values embedded in block JSON
73
+ 'header_content', // create_tb_template
74
+ 'footer_content', // create_tb_template
75
+ ]);
76
+ function normalizeBody(value, withinBlockTree = false) {
77
+ if (typeof value === 'string') {
78
+ return withinBlockTree ? normalizeQuoteEscapes(value) : value;
79
+ }
36
80
  if (Array.isArray(value))
37
- return value.map(normalizeBody);
81
+ return value.map((v) => normalizeBody(v, withinBlockTree));
38
82
  // Restrict recursion to plain objects so Date / RegExp / class instances
39
83
  // with custom `toJSON` keep their canonical serialization.
40
84
  if (value &&
41
85
  typeof value === 'object' &&
42
86
  Object.getPrototypeOf(value) === Object.prototype) {
43
87
  const out = {};
44
- for (const [k, v] of Object.entries(value))
45
- out[k] = normalizeBody(v);
88
+ for (const [k, v] of Object.entries(value)) {
89
+ out[k] = normalizeBody(v, withinBlockTree || BLOCK_CONTENT_KEYS.has(k));
90
+ }
46
91
  return out;
47
92
  }
48
93
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diviops/mcp-server",
3
- "version": "0.2.19",
3
+ "version": "0.2.20",
4
4
  "description": "MCP server exposing Divi 5 Visual Builder as tools for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",