@diviops/mcp-server 0.2.18 → 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.
package/README.md CHANGED
@@ -9,7 +9,8 @@ Claude Code <-> MCP Server (stdio) <-> WordPress REST API <-> Divi MCP Plugin
9
9
  ## Requirements
10
10
 
11
11
  - **Node.js** >= 18.0.0
12
- - **WordPress** >= 5.6 (Application Passwords support)
12
+ - **PHP** >= 7.4
13
+ - **WordPress** >= 6.5
13
14
  - **Divi 5** theme active
14
15
  - **DiviOps Agent** WordPress plugin installed and active
15
16
 
@@ -175,6 +176,8 @@ Read-only commands plus non-destructive writes needed for core MCP functionality
175
176
  | Cache | `cache flush`, `transient delete`, `rewrite flush` |
176
177
  | Export | `export` (WXR data export to file) |
177
178
  | Info | `cron event list`, `plugin list`, `theme list`, `menu list`, `site url` |
179
+ | Core (read-only) | `core version`, `core check-update`, `core is-installed`, `core verify-checksums`, `core language list` |
180
+ | DB (introspection) | `db columns`, `db size`, `db tables`, `db check`, `db search` |
178
181
 
179
182
  ### Extended commands (opt-in)
180
183
 
package/dist/wp-client.js CHANGED
@@ -5,6 +5,93 @@
5
5
  * Generate one at: WP Admin → Users → Your Profile → Application Passwords.
6
6
  */
7
7
  import { MIN_PLUGIN_VERSION, compareVersions, } from './compatibility.js';
8
+ /**
9
+ * Normalize quote-escape pathologies inside `$variable(...)$` token regions only.
10
+ *
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.
14
+ *
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 `&quot;` 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.
42
+ *
43
+ * Scope is intentionally narrow: rewrites only happen inside `$variable(...)$`
44
+ * regions (Divi's actual resolver boundary). Bytes outside those regions —
45
+ * arbitrary user text, code samples, string-variable values that happen to
46
+ * contain `\u005cu0022`, `\\u0022`, or bare `"` — are left untouched.
47
+ */
48
+ function normalizeQuoteEscapes(s) {
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
+ });
57
+ }
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
+ }
80
+ if (Array.isArray(value))
81
+ return value.map((v) => normalizeBody(v, withinBlockTree));
82
+ // Restrict recursion to plain objects so Date / RegExp / class instances
83
+ // with custom `toJSON` keep their canonical serialization.
84
+ if (value &&
85
+ typeof value === 'object' &&
86
+ Object.getPrototypeOf(value) === Object.prototype) {
87
+ const out = {};
88
+ for (const [k, v] of Object.entries(value)) {
89
+ out[k] = normalizeBody(v, withinBlockTree || BLOCK_CONTENT_KEYS.has(k));
90
+ }
91
+ return out;
92
+ }
93
+ return value;
94
+ }
8
95
  export class WPClient {
9
96
  baseUrl;
10
97
  authHeader;
@@ -34,7 +121,7 @@ export class WPClient {
34
121
  },
35
122
  };
36
123
  if (body && method !== 'GET') {
37
- fetchOptions.body = JSON.stringify(body);
124
+ fetchOptions.body = JSON.stringify(normalizeBody(body));
38
125
  }
39
126
  const response = await fetch(url, fetchOptions);
40
127
  if (!response.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diviops/mcp-server",
3
- "version": "0.2.18",
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",