@respira/wordpress-mcp-server 8.0.4 → 8.0.6

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.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Regression test for bug a3f65cf5: the ACF options-page tool family
3
+ * (`respira_acf_update_options`, `respira_acf_update_option`,
4
+ * `respira_acf_get_option`, `respira_acf_get_options`, and every other
5
+ * respira_acf_* tool) ignored a per-call `site_id` override and silently
6
+ * operated on the globally active site instead, while the response envelope
7
+ * still named the pinned site — a customer running a population pass pinned
8
+ * to site B had it silently land on site A instead (reported by
9
+ * john@localmarketingpros.com, 17-site DSO rollout).
10
+ *
11
+ * Root cause: same bug class as the mcp-v8.0.3 Bricks/Elementor fix. The
12
+ * dynamic-dispatch `default:` case in `dispatchToolCall` resolves a
13
+ * site_id-aware `client` at the top of the function (via `resolveClient`),
14
+ * but the ACF branch called `this.currentSite!.dispatchAcfTool(...)` — the
15
+ * globally active site — instead of `client.dispatchAcfTool(...)`.
16
+ *
17
+ * This test proves the actual outbound HTTP call lands on the pinned site's
18
+ * host, not the active site's, by installing a mock axios adapter (same
19
+ * pattern as rest-route-fallback.test.ts) and inspecting which baseURL
20
+ * serviced the ACF options request.
21
+ */
22
+ export {};
23
+ //# sourceMappingURL=acf-site-id-routing.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acf-site-id-routing.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/acf-site-id-routing.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Regression test for bug a3f65cf5: the ACF options-page tool family
3
+ * (`respira_acf_update_options`, `respira_acf_update_option`,
4
+ * `respira_acf_get_option`, `respira_acf_get_options`, and every other
5
+ * respira_acf_* tool) ignored a per-call `site_id` override and silently
6
+ * operated on the globally active site instead, while the response envelope
7
+ * still named the pinned site — a customer running a population pass pinned
8
+ * to site B had it silently land on site A instead (reported by
9
+ * john@localmarketingpros.com, 17-site DSO rollout).
10
+ *
11
+ * Root cause: same bug class as the mcp-v8.0.3 Bricks/Elementor fix. The
12
+ * dynamic-dispatch `default:` case in `dispatchToolCall` resolves a
13
+ * site_id-aware `client` at the top of the function (via `resolveClient`),
14
+ * but the ACF branch called `this.currentSite!.dispatchAcfTool(...)` — the
15
+ * globally active site — instead of `client.dispatchAcfTool(...)`.
16
+ *
17
+ * This test proves the actual outbound HTTP call lands on the pinned site's
18
+ * host, not the active site's, by installing a mock axios adapter (same
19
+ * pattern as rest-route-fallback.test.ts) and inspecting which baseURL
20
+ * serviced the ACF options request.
21
+ */
22
+ import { test } from 'node:test';
23
+ import assert from 'node:assert/strict';
24
+ import axios from 'axios';
25
+ import { RespiraWordPressServer } from '../server.js';
26
+ const callLog = [];
27
+ let currentHandler = null;
28
+ const originalAdapter = axios.defaults.adapter;
29
+ function installMockAdapter() {
30
+ callLog.length = 0;
31
+ axios.defaults.adapter = ((config) => {
32
+ callLog.push({
33
+ method: (config.method || 'get').toLowerCase(),
34
+ url: config.url,
35
+ baseURL: config.baseURL,
36
+ });
37
+ if (!currentHandler) {
38
+ return Promise.reject(new Error('no mock handler installed'));
39
+ }
40
+ const result = currentHandler(config);
41
+ return Promise.resolve(result).then((r) => ({
42
+ status: r.status,
43
+ statusText: 'OK',
44
+ headers: r.headers || { 'content-type': 'application/json' },
45
+ config,
46
+ data: r.data,
47
+ request: {},
48
+ }));
49
+ });
50
+ }
51
+ function restoreAdapter() {
52
+ axios.defaults.adapter = originalAdapter;
53
+ currentHandler = null;
54
+ }
55
+ function setHandler(h) {
56
+ currentHandler = h;
57
+ }
58
+ function fullUrl(c) {
59
+ return c.url.startsWith('http') ? c.url : `${c.baseURL || ''}${c.url}`;
60
+ }
61
+ const SITE_A = {
62
+ id: 'site-a',
63
+ name: 'Site A (active/default)',
64
+ url: 'https://site-a.example.com',
65
+ apiKey: 'respira_token_a',
66
+ default: true,
67
+ };
68
+ const SITE_B = {
69
+ id: 'site-b',
70
+ name: 'Site B (pinned)',
71
+ url: 'https://site-b.example.com',
72
+ apiKey: 'respira_token_b',
73
+ };
74
+ // Generic handler: ACF availability check (/context/site-info) always says
75
+ // ACF is installed, regardless of which site is asked. ACF options calls
76
+ // return a small JSON payload so dispatchAcfTool doesn't throw.
77
+ function genericHandler(config) {
78
+ const url = config.url || '';
79
+ if (/\/context\/site-info/.test(url)) {
80
+ return { status: 200, data: { addons: { acf: { installed: true } } } };
81
+ }
82
+ if (/\/acf\/options\//.test(url)) {
83
+ return { status: 200, data: { value: 'mock-value', field_key: 'hero_title' } };
84
+ }
85
+ // Any other probe (compatibility check, etc.) — respond harmlessly.
86
+ return { status: 200, data: {} };
87
+ }
88
+ test('respira_acf_get_option with site_id override hits the pinned site, not the active site', async () => {
89
+ installMockAdapter();
90
+ try {
91
+ setHandler(genericHandler);
92
+ const server = new RespiraWordPressServer([SITE_A, SITE_B]);
93
+ await server.dispatchToolCall('respira_acf_get_option', {
94
+ site_id: 'site-b',
95
+ page_id: 'general',
96
+ field_key: 'hero_title',
97
+ });
98
+ const acfCalls = callLog.filter((c) => /\/acf\/options\//.test(c.url));
99
+ assert.ok(acfCalls.length > 0, 'expected at least one /acf/options/ call');
100
+ for (const c of acfCalls) {
101
+ assert.match(fullUrl(c), /site-b\.example\.com/, `ACF call should hit site-b (pinned), got: ${fullUrl(c)}`);
102
+ }
103
+ // Site A (the active/default site) must never see this call.
104
+ assert.ok(!acfCalls.some((c) => /site-a\.example\.com/.test(fullUrl(c))), 'site-a (active site) must not receive the pinned ACF call');
105
+ }
106
+ finally {
107
+ restoreAdapter();
108
+ }
109
+ });
110
+ test('respira_acf_update_option with site_id override writes to the pinned site, not the active site', async () => {
111
+ installMockAdapter();
112
+ try {
113
+ setHandler(genericHandler);
114
+ const server = new RespiraWordPressServer([SITE_A, SITE_B]);
115
+ await server.dispatchToolCall('respira_acf_update_option', {
116
+ site_id: 'site-b',
117
+ page_id: 'general',
118
+ field_key: 'hero_title',
119
+ value: 'New Title',
120
+ });
121
+ const acfWrites = callLog.filter((c) => /\/acf\/options\//.test(c.url) && c.method === 'put');
122
+ assert.ok(acfWrites.length > 0, 'expected at least one PUT /acf/options/ call');
123
+ for (const c of acfWrites) {
124
+ assert.match(fullUrl(c), /site-b\.example\.com/, `ACF write should land on site-b (pinned), got: ${fullUrl(c)}`);
125
+ }
126
+ assert.ok(!acfWrites.some((c) => /site-a\.example\.com/.test(fullUrl(c))), 'site-a (active site) must never receive the pinned ACF write');
127
+ }
128
+ finally {
129
+ restoreAdapter();
130
+ }
131
+ });
132
+ test('the "wordpress_acf_*" alias (post normalizeToolName) also routes to the pinned site', async () => {
133
+ installMockAdapter();
134
+ try {
135
+ setHandler(genericHandler);
136
+ const server = new RespiraWordPressServer([SITE_A, SITE_B]);
137
+ // dispatchToolCall receives the already-normalized name in production,
138
+ // exactly as normalizeToolName() would rewrite respira_acf_get_options.
139
+ await server.dispatchToolCall('wordpress_acf_get_options', {
140
+ site_id: 'site-b',
141
+ page_id: 'general',
142
+ });
143
+ const acfCalls = callLog.filter((c) => /\/acf\/options\//.test(c.url));
144
+ assert.ok(acfCalls.length > 0, 'expected at least one /acf/options/ call');
145
+ for (const c of acfCalls) {
146
+ assert.match(fullUrl(c), /site-b\.example\.com/);
147
+ }
148
+ }
149
+ finally {
150
+ restoreAdapter();
151
+ }
152
+ });
153
+ // The response envelope's `site` block is built from getActiveSiteSummary(args),
154
+ // which already resolves args.site_id to the matching connected site regardless
155
+ // of which client actually serviced the call (see the v6.19.0 N6 fix). That means
156
+ // once the dispatch bug above is fixed so the HTTP call truly lands on the pinned
157
+ // site, the envelope and the actual write agree. This test locks in that
158
+ // agreement for the ACF family specifically.
159
+ test('getActiveSiteSummary(args) names the pinned site for an ACF call, matching where the write actually landed', () => {
160
+ const server = new RespiraWordPressServer([SITE_A, SITE_B]);
161
+ const summary = server.getActiveSiteSummary({ site_id: 'site-b', page_id: 'general', field_key: 'x' });
162
+ assert.ok(summary, 'expected a site summary');
163
+ assert.equal(summary.id ?? summary.site_id, 'site-b');
164
+ });
165
+ //# sourceMappingURL=acf-site-id-routing.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acf-site-id-routing.test.js","sourceRoot":"","sources":["../../src/__tests__/acf-site-id-routing.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH,OAAO,KAAK,EAAE,mBAAmB,EAAe,MAAM,kBAAkB,CAAC;AAsQzE,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,KAAK,CAA2C;IACxD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB,CAAS;IACpC,iFAAiF;IACjF,OAAO,CAAC,iBAAiB,CAAK;IAC9B,iFAAiF;IACjF,OAAO,CAAC,mBAAmB,CAAgC;IAE3D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAsB;IAEhE;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;gBA4Bb,WAAW,EAAE,mBAAmB,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE;IAiVvE,OAAO,CAAC,cAAc;IAItB;;;;;;;;;OASG;IACH,OAAO,CAAC,oBAAoB;IAkB5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IA4BrB,gEAAgE;IAChE,OAAO,CAAC,aAAa;IAUrB;;;;;;OAMG;YACW,UAAU;IA2CxB;;;;;;;;;;;;;;OAcG;YACW,WAAW;IAiJzB;;;;;;;;;OASG;YACW,kBAAkB;IA6KhC;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;YACW,qBAAqB;YAiBrB,kBAAkB;IA6FhC,yFAAyF;IACzF,OAAO,CAAC,gBAAgB;IASxB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,6BAA6B;IA0BrC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IA+BjC,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,aAAa;YAmQP,kBAAkB;YA6BlB,yBAAyB;IASvC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;YAyBd,QAAQ;IAuvGtB;;;;;;OAMG;IACH;;;;;;;;;;;;;oDAagD;IAChD,OAAO,CAAC,mBAAmB,CAAoD;IAC/E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAU;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAU;IAC1D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAS;IAC1D;;mDAE+C;IAC/C,OAAO,CAAC,0BAA0B,CAAoC;IAEtE;;;;;;;;OAQG;YACW,0BAA0B;YAyC1B,oBAAoB;YA6CpB,2BAA2B;IAQzC;;;;OAIG;YACW,cAAc;IAQ5B,OAAO,CAAC,mBAAmB;IAo7C3B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAe3C;IAEF;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;YAmCpB,cAAc;IA4G5B,oEAAoE;IACpE,OAAO,CAAC,iBAAiB;IAoBzB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,eAAe;YAuCT,gBAAgB;IAo0C9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA8YzB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA6UxB,GAAG;CAyCV"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH,OAAO,KAAK,EAAE,mBAAmB,EAAe,MAAM,kBAAkB,CAAC;AAsQzE,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,KAAK,CAA2C;IACxD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB,CAAS;IACpC,iFAAiF;IACjF,OAAO,CAAC,iBAAiB,CAAK;IAC9B,iFAAiF;IACjF,OAAO,CAAC,mBAAmB,CAAgC;IAE3D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAsB;IAEhE;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;gBA4Bb,WAAW,EAAE,mBAAmB,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE;IAiVvE,OAAO,CAAC,cAAc;IAItB;;;;;;;;;OASG;IACH,OAAO,CAAC,oBAAoB;IAkB5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IA4BrB,gEAAgE;IAChE,OAAO,CAAC,aAAa;IAUrB;;;;;;OAMG;YACW,UAAU;IA2CxB;;;;;;;;;;;;;;OAcG;YACW,WAAW;IAiJzB;;;;;;;;;OASG;YACW,kBAAkB;IA6KhC;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;YACW,qBAAqB;YAiBrB,kBAAkB;IA6FhC,yFAAyF;IACzF,OAAO,CAAC,gBAAgB;IASxB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,6BAA6B;IA0BrC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IA+BjC,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,aAAa;YAmQP,kBAAkB;YA6BlB,yBAAyB;IASvC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;YAyBd,QAAQ;IA+xGtB;;;;;;OAMG;IACH;;;;;;;;;;;;;oDAagD;IAChD,OAAO,CAAC,mBAAmB,CAAoD;IAC/E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAU;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAU;IAC1D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAS;IAC1D;;mDAE+C;IAC/C,OAAO,CAAC,0BAA0B,CAAoC;IAEtE;;;;;;;;OAQG;YACW,0BAA0B;YAyC1B,oBAAoB;YA6CpB,2BAA2B;IAQzC;;;;OAIG;YACW,cAAc;IAQ5B,OAAO,CAAC,mBAAmB;IAo7C3B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAe3C;IAEF;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;YAmCpB,cAAc;IA4G5B,oEAAoE;IACpE,OAAO,CAAC,iBAAiB;IAoBzB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,eAAe;YAuCT,gBAAgB;IA+0C9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkZzB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA6UxB,GAAG;CAyCV"}
package/dist/server.js CHANGED
@@ -2209,7 +2209,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2209
2209
  },
2210
2210
  {
2211
2211
  name: 'wordpress_delete_post',
2212
- description: 'Delete a post. IMPORTANT: By default, this only works on Respira-created duplicates. The force parameter only works if "Allow Direct Editing" is enabled in Respira settings (disabled by default for safety).',
2212
+ description: 'Delete a post. IMPORTANT: By default, this only works on Respira-created duplicates. The force parameter only works if "Allow Direct Editing" is enabled in Respira settings (disabled by default for safety).\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
2213
2213
  inputSchema: {
2214
2214
  type: 'object',
2215
2215
  properties: {
@@ -2221,6 +2221,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2221
2221
  type: 'boolean',
2222
2222
  description: 'Force delete even if not a duplicate',
2223
2223
  },
2224
+ approval_token: {
2225
+ type: 'string',
2226
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2227
+ },
2224
2228
  },
2225
2229
  required: ['id'],
2226
2230
  },
@@ -2738,7 +2742,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2738
2742
  },
2739
2743
  {
2740
2744
  name: 'wordpress_create_site_pattern',
2741
- description: 'Stage creation of a native synced or unsynced user pattern. The write is approval-first, snapshot-backed, read-back verified, and server-render verified.',
2745
+ description: 'Stage creation of a native synced or unsynced user pattern. The write is approval-first, snapshot-backed, read-back verified, and server-render verified.\n\nApproval flow: the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the create.',
2742
2746
  inputSchema: {
2743
2747
  type: 'object',
2744
2748
  properties: {
@@ -2750,13 +2754,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2750
2754
  verification_marker: { type: 'string' },
2751
2755
  dry_run: { type: 'boolean' },
2752
2756
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2757
+ approval_token: {
2758
+ type: 'string',
2759
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2760
+ },
2753
2761
  },
2754
2762
  required: ['title'],
2755
2763
  },
2756
2764
  },
2757
2765
  {
2758
2766
  name: 'wordpress_update_site_pattern',
2759
- description: 'Stage targeted path-addressed operations on an editable user pattern with stale-write, wrong-target, snapshot, and render protection.',
2767
+ description: 'Stage targeted path-addressed operations on an editable user pattern with stale-write, wrong-target, snapshot, and render protection.\n\nApproval flow: the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the update.',
2760
2768
  inputSchema: {
2761
2769
  type: 'object',
2762
2770
  properties: {
@@ -2766,13 +2774,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2766
2774
  verification_marker: { type: 'string' },
2767
2775
  dry_run: { type: 'boolean' },
2768
2776
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2777
+ approval_token: {
2778
+ type: 'string',
2779
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2780
+ },
2769
2781
  },
2770
2782
  required: ['id', 'expected_fingerprint', 'operations'],
2771
2783
  },
2772
2784
  },
2773
2785
  {
2774
2786
  name: 'wordpress_delete_site_pattern',
2775
- description: 'Stage deletion of an editable user pattern. Referenced patterns are refused unless allow_referenced_delete is explicitly approved; a rollback snapshot is retained.',
2787
+ description: 'Stage deletion of an editable user pattern. Referenced patterns are refused unless allow_referenced_delete is explicitly approved; a rollback snapshot is retained.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
2776
2788
  inputSchema: {
2777
2789
  type: 'object',
2778
2790
  properties: {
@@ -2781,6 +2793,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2781
2793
  allow_referenced_delete: { type: 'boolean' },
2782
2794
  dry_run: { type: 'boolean' },
2783
2795
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2796
+ approval_token: {
2797
+ type: 'string',
2798
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2799
+ },
2784
2800
  },
2785
2801
  required: ['id', 'expected_fingerprint'],
2786
2802
  },
@@ -2811,7 +2827,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2811
2827
  },
2812
2828
  {
2813
2829
  name: 'wordpress_create_site_navigation',
2814
- description: 'Stage creation of a native block navigation entity. The write is approval-first, snapshot-backed, read-back verified, and server-render verified.',
2830
+ description: 'Stage creation of a native block navigation entity. The write is approval-first, snapshot-backed, read-back verified, and server-render verified.\n\nApproval flow: the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the create.',
2815
2831
  inputSchema: {
2816
2832
  type: 'object',
2817
2833
  properties: {
@@ -2822,13 +2838,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2822
2838
  verification_marker: { type: 'string' },
2823
2839
  dry_run: { type: 'boolean' },
2824
2840
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2841
+ approval_token: {
2842
+ type: 'string',
2843
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2844
+ },
2825
2845
  },
2826
2846
  required: ['title'],
2827
2847
  },
2828
2848
  },
2829
2849
  {
2830
2850
  name: 'wordpress_update_site_navigation',
2831
- description: 'Stage exact path-addressed operations on a native block navigation entity while preserving unknown extension blocks and rejecting stale or wrong targets.',
2851
+ description: 'Stage exact path-addressed operations on a native block navigation entity while preserving unknown extension blocks and rejecting stale or wrong targets.\n\nApproval flow: the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the update.',
2832
2852
  inputSchema: {
2833
2853
  type: 'object',
2834
2854
  properties: {
@@ -2838,13 +2858,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2838
2858
  verification_marker: { type: 'string' },
2839
2859
  dry_run: { type: 'boolean' },
2840
2860
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2861
+ approval_token: {
2862
+ type: 'string',
2863
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2864
+ },
2841
2865
  },
2842
2866
  required: ['id', 'expected_fingerprint', 'operations'],
2843
2867
  },
2844
2868
  },
2845
2869
  {
2846
2870
  name: 'wordpress_delete_site_navigation',
2847
- description: 'Stage deletion of a native block navigation entity. Referenced navigation is refused unless allow_referenced_delete is explicitly approved; a rollback snapshot is retained.',
2871
+ description: 'Stage deletion of a native block navigation entity. Referenced navigation is refused unless allow_referenced_delete is explicitly approved; a rollback snapshot is retained.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
2848
2872
  inputSchema: {
2849
2873
  type: 'object',
2850
2874
  properties: {
@@ -2853,6 +2877,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2853
2877
  allow_referenced_delete: { type: 'boolean' },
2854
2878
  dry_run: { type: 'boolean' },
2855
2879
  edit_target: { type: 'string', enum: ['approval', 'live'] },
2880
+ approval_token: {
2881
+ type: 'string',
2882
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
2883
+ },
2856
2884
  },
2857
2885
  required: ['id', 'expected_fingerprint'],
2858
2886
  },
@@ -3780,7 +3808,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3780
3808
  },
3781
3809
  {
3782
3810
  name: 'wordpress_delete_comment',
3783
- description: 'Delete a comment.',
3811
+ description: 'Delete a comment.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
3784
3812
  inputSchema: {
3785
3813
  type: 'object',
3786
3814
  properties: {
@@ -3788,6 +3816,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3788
3816
  type: 'number',
3789
3817
  description: 'Comment ID',
3790
3818
  },
3819
+ approval_token: {
3820
+ type: 'string',
3821
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
3822
+ },
3791
3823
  },
3792
3824
  required: ['id'],
3793
3825
  },
@@ -3931,7 +3963,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3931
3963
  },
3932
3964
  {
3933
3965
  name: 'wordpress_delete_term',
3934
- description: 'Delete a term.',
3966
+ description: 'Delete a term.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
3935
3967
  inputSchema: {
3936
3968
  type: 'object',
3937
3969
  properties: {
@@ -3943,6 +3975,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3943
3975
  type: 'number',
3944
3976
  description: 'Term ID',
3945
3977
  },
3978
+ approval_token: {
3979
+ type: 'string',
3980
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
3981
+ },
3946
3982
  },
3947
3983
  required: ['taxonomy', 'id'],
3948
3984
  },
@@ -4535,7 +4571,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4535
4571
  },
4536
4572
  {
4537
4573
  name: 'wordpress_delete_option',
4538
- description: 'Delete an option.',
4574
+ description: 'Delete an option.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the delete.',
4539
4575
  inputSchema: {
4540
4576
  type: 'object',
4541
4577
  properties: {
@@ -4543,6 +4579,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4543
4579
  type: 'string',
4544
4580
  description: 'Option name',
4545
4581
  },
4582
+ approval_token: {
4583
+ type: 'string',
4584
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
4585
+ },
4546
4586
  },
4547
4587
  required: ['option'],
4548
4588
  },
@@ -6923,7 +6963,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
6923
6963
  };
6924
6964
  }
6925
6965
  case 'wordpress_delete_post':
6926
- return await client.deletePost(args.id, args.force);
6966
+ return await client.deletePost(args.id, args.force, args.approval_token);
6927
6967
  case 'wordpress_list_media':
6928
6968
  return await client.listMedia(args);
6929
6969
  case 'wordpress_upload_media': {
@@ -7154,7 +7194,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
7154
7194
  case 'wordpress_update_comment':
7155
7195
  return await client.updateComment(args.id, args);
7156
7196
  case 'wordpress_delete_comment':
7157
- return await client.deleteComment(args.id);
7197
+ return await client.deleteComment(args.id, args.approval_token);
7158
7198
  // Taxonomies
7159
7199
  case 'wordpress_list_taxonomies':
7160
7200
  return await client.listTaxonomies();
@@ -7169,7 +7209,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
7169
7209
  case 'wordpress_update_term':
7170
7210
  return await client.updateTerm(args.taxonomy, args.id, args);
7171
7211
  case 'wordpress_delete_term':
7172
- return await client.deleteTerm(args.taxonomy, args.id);
7212
+ return await client.deleteTerm(args.taxonomy, args.id, args.approval_token);
7173
7213
  // Custom Post Types
7174
7214
  case 'wordpress_list_post_types':
7175
7215
  return await client.listPostTypes();
@@ -7244,7 +7284,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
7244
7284
  case 'wordpress_update_option':
7245
7285
  return await client.updateOption(args.option, args.value);
7246
7286
  case 'wordpress_delete_option':
7247
- return await client.deleteOption(args.option);
7287
+ return await client.deleteOption(args.option, args.approval_token);
7248
7288
  // Media enhancements
7249
7289
  case 'wordpress_get_media':
7250
7290
  return await client.getMedia(args.id);
@@ -7763,7 +7803,18 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
7763
7803
  hint: 'Install Advanced Custom Fields on the target site, then retry. ACF Pro is required for repeaters, flexible content, galleries, and options pages.'
7764
7804
  };
7765
7805
  }
7766
- return await this.currentSite.dispatchAcfTool(acfName, args);
7806
+ // Same class of bug as the mcp-v8.0.3 Bricks/Elementor fix: `client`
7807
+ // (resolved above via resolveClient(args), site_id-aware) is the
7808
+ // correct target — NOT `this.currentSite`, which is only the
7809
+ // globally active site. This ACF branch used `this.currentSite!`,
7810
+ // so respira_acf_update_option(s)/get_option(s) and every other ACF
7811
+ // tool silently read/wrote the active site while the response
7812
+ // envelope (built from args.site_id in getActiveSiteSummary/
7813
+ // withSiteContext) still named the pinned site_id, making the
7814
+ // wrong-site write invisible. Reported by john@localmarketingpros.com
7815
+ // (17-site DSO rollout): a population pass pinned to site B landed
7816
+ // on site A instead.
7817
+ return await client.dispatchAcfTool(acfName, args);
7767
7818
  }
7768
7819
  // Check if this is a widget shortcut (wordpress_add_*).
7769
7820
  if (name.startsWith('wordpress_add_')) {
@@ -8089,7 +8140,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
8089
8140
  },
8090
8141
  {
8091
8142
  name: 'wordpress_bulk_pages_operation',
8092
- description: 'Apply an operation across up to 100 pages. Supports strip_inline_styles, find_and_replace, and custom operations. Each page gets a snapshot for rollback. Rate limited to 3/hr.',
8143
+ description: 'Apply an operation across up to 100 pages. Supports strip_inline_styles, find_and_replace, and custom operations. Each page gets a snapshot for rollback. Rate limited to 3/hr.\n\nApproval flow: destructive — the first call may return `code: respira_approval_required` with `data.approval_request.approval_token`. Pass that token back via the `approval_token` param on the next call to complete the operation.',
8093
8144
  inputSchema: {
8094
8145
  type: 'object',
8095
8146
  properties: {
@@ -8106,6 +8157,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
8106
8157
  type: 'object',
8107
8158
  description: 'Execution options: {dry_run: boolean}',
8108
8159
  },
8160
+ approval_token: {
8161
+ type: 'string',
8162
+ description: 'One-time token from a prior respira_approval_required response. Omit on the first call; the tool will reject with a token if approval is required, then re-call with this parameter set to that token to confirm and execute.',
8163
+ },
8109
8164
  },
8110
8165
  required: ['page_ids', 'operation'],
8111
8166
  },