@respira/wordpress-mcp-server 8.1.4 → 8.1.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.
- package/CHANGELOG.md +16 -0
- package/README.md +5 -5
- package/dist/__tests__/oq-put-fallback-403.test.d.ts +2 -0
- package/dist/__tests__/oq-put-fallback-403.test.d.ts.map +1 -0
- package/dist/__tests__/oq-put-fallback-403.test.js +59 -0
- package/dist/__tests__/oq-put-fallback-403.test.js.map +1 -0
- package/dist/__tests__/v2-negotiation-failure.test.d.ts +2 -0
- package/dist/__tests__/v2-negotiation-failure.test.d.ts.map +1 -0
- package/dist/__tests__/v2-negotiation-failure.test.js +104 -0
- package/dist/__tests__/v2-negotiation-failure.test.js.map +1 -0
- package/dist/__tests__/v2-negotiation-through-real-client.test.d.ts +2 -0
- package/dist/__tests__/v2-negotiation-through-real-client.test.d.ts.map +1 -0
- package/dist/__tests__/v2-negotiation-through-real-client.test.js +104 -0
- package/dist/__tests__/v2-negotiation-through-real-client.test.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +20 -8
- package/dist/server.js.map +1 -1
- package/dist/wordpress-client.d.ts +63 -0
- package/dist/wordpress-client.d.ts.map +1 -1
- package/dist/wordpress-client.js +121 -15
- package/dist/wordpress-client.js.map +1 -1
- package/package.json +2 -2
- package/skills/migrate-elementor-to-breakdance/SKILL.md +1 -1
- package/skills/migrate-elementor-to-bricks/SKILL.md +1 -1
- package/skills/migrate-elementor-to-gutenberg/SKILL.md +1 -1
- package/skills/migrate-elementor-to-oxygen/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to Respira WordPress MCP Server will be documented in this f
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [8.1.6] - 2026-08-02
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **An authentication failure was reported as "respira/v2 is not available on this site".** The version probe swallowed its own error and fell back to v1, so every failure mode reached you as the same message followed by four suggested causes: a security plugin, Plain permalinks, a WAF or CDN, an outdated plugin. For M.O. all four were false. Permalinks were `/%postname%/`, there was no CDN, the plugin was already current, and `/wp-json/respira/v2/status` was answering `401` rather than `404`, which proves the opposite of what the message said: the route existed, WordPress routed to it, and it ran far enough to reach its auth check. That cost most of a day chasing firewall rules for a deactivated key. The probe now keeps the reason and reports it. A `401` or `403` says the key was rejected and points at reconnecting, a `5xx` reports the status and sends you to the debug log, a connection failure says so, and the original text is kept only for a real `404`, which is the one case it was ever true for.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`excerpt` on `update_page`, `update_post` and `update_custom_post`.** There was no way to ask for it, so callers put it in `meta`, where it became a post meta row that reads back perfectly and renders nowhere. The description says as much. Requires plugin 8.2.1 for the write to land in the native field. Reported by P.F.
|
|
17
|
+
|
|
18
|
+
## [8.1.5] - 2026-08-01
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Updates now succeed on hosts that block PUT at the edge.** Managed hosts and WAFs commonly allow `GET` and `POST` but reject `PUT`, `PATCH` and `DELETE` before the request ever reaches WordPress. The result was every update failing with "403 Forbidden from the web server (not WordPress)" while creating pages and reading them worked perfectly, which sends you looking at plugin whitelists, permalink settings, CDN exceptions and User-Agent rules when the only thing that actually mattered was the HTTP verb. Respira already shipped the fallback that fixes this, retrying the write as a `POST`, but it only fired on `404` (the old-plugin case), so it never ran for the hosts that needed it. It now also fires on `403` and `405`, at all five write paths. Reported, root-caused and verified live against a real write by O.Q., who also identified the exact shape of the fix.
|
|
23
|
+
|
|
8
24
|
## [8.1.4] - 2026-07-30
|
|
9
25
|
|
|
10
26
|
### Fixed
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<h1 align="center">Respira WordPress MCP Server</h1>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<strong>198 MCP tools, 303 with the WooCommerce add-on.
|
|
10
|
+
<strong>198 MCP tools, 303 with the WooCommerce add-on. 17 page builders and the native Site Editor. MCP 2026-07-28 with legacy-client support.</strong><br>
|
|
11
11
|
Element-level editing, full page creation, HTML-to-builder conversion with rendering fidelity validation, per-site tool governance, storefront design intelligence, stock images, bulk operations — all through one MCP server.
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<a href="https://www.npmjs.com/package/@respira/wordpress-mcp-server"><img src="https://img.shields.io/npm/v/@respira/wordpress-mcp-server.svg?style=flat-square&color=10b981" alt="npm version"></a>
|
|
16
16
|
<a href="https://www.npmjs.com/package/@respira/wordpress-mcp-server"><img src="https://img.shields.io/npm/dm/@respira/wordpress-mcp-server.svg?style=flat-square" alt="npm downloads"></a>
|
|
17
17
|
<img src="https://img.shields.io/badge/tools-303-10b981?style=flat-square" alt="303 Tools with WooCommerce">
|
|
18
|
-
<img src="https://img.shields.io/badge/builders-
|
|
18
|
+
<img src="https://img.shields.io/badge/builders-17-10b981?style=flat-square" alt="16 Page Builders">
|
|
19
19
|
<img src="https://img.shields.io/badge/TypeScript-100%25-blue?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript">
|
|
20
20
|
</p>
|
|
21
21
|
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
Other WordPress MCP servers wrap the REST API. They can create posts and pages, but **they can't touch your page builder content**.
|
|
35
35
|
|
|
36
|
-
Respira includes a WordPress plugin that gives AI **native access** to
|
|
36
|
+
Respira includes a WordPress plugin that gives AI **native access** to 17 page builders, plus element-level precision, full page creation from structure, HTML-to-builder conversion, storefront design intelligence, stock image search, and bulk operations across hundreds of pages.
|
|
37
37
|
|
|
38
38
|
### New in 8.1: MCP 2026-07-28, without breaking existing clients
|
|
39
39
|
|
|
@@ -129,7 +129,7 @@ The MCP server exposes 198 core tools and 103 WooCommerce add-on tools. It autom
|
|
|
129
129
|
|
|
130
130
|
| Capability | Respira | Typical WordPress MCP servers |
|
|
131
131
|
|---|---|---|
|
|
132
|
-
| Page builder support | **
|
|
132
|
+
| Page builder support | **17 builders** (incl. Spectra, Kadence, GenerateBlocks) | None, or a single builder |
|
|
133
133
|
| Element-level find/update/move/remove | **Yes** | No |
|
|
134
134
|
| Build full pages from structure | **Yes** | No |
|
|
135
135
|
| Convert HTML to native builder | **Yes** | No |
|
|
@@ -817,6 +817,6 @@ MIT © [Respira](https://respira.press)
|
|
|
817
817
|
---
|
|
818
818
|
|
|
819
819
|
<p align="center">
|
|
820
|
-
<strong>198 core tools, 303 with WooCommerce.
|
|
820
|
+
<strong>198 core tools, 303 with WooCommerce. 17 builders. The AI infrastructure layer for WordPress.</strong><br>
|
|
821
821
|
<a href="https://respira.press">respira.press</a>
|
|
822
822
|
</p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oq-put-fallback-403.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/oq-put-fallback-403.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { isRetryableAsPost } from '../wordpress-client.js';
|
|
4
|
+
/**
|
|
5
|
+
* O.Q. field report, 2026-07-31.
|
|
6
|
+
*
|
|
7
|
+
* Managed hosts and WAFs allow GET and POST but block PUT, PATCH and DELETE at
|
|
8
|
+
* the edge, before WordPress sees the request. Every update failed with
|
|
9
|
+
* "403 Forbidden from the web server (not WordPress)" while creation and reads
|
|
10
|
+
* worked, which sends the customer chasing plugin whitelists and CDN rules when
|
|
11
|
+
* the only thing that mattered was the HTTP verb.
|
|
12
|
+
*
|
|
13
|
+
* The client already had a PUT-to-POST fallback. It only fired on 404.
|
|
14
|
+
*
|
|
15
|
+
* THE PART THIS TEST EXISTS FOR: widening the status check alone does NOT fix
|
|
16
|
+
* it, and a build plus the existing suite passed while the bug was still live.
|
|
17
|
+
* The response interceptor rewrites a non-WordPress 403 into a coded Error via
|
|
18
|
+
* codedError(), which sets `name` and does NOT carry `response` across. So a
|
|
19
|
+
* predicate that only looks at `error.response.status` never fires on the exact
|
|
20
|
+
* case the fallback exists for. Proven against a real site with a shim
|
|
21
|
+
* returning 403 on PUT: the status-only version still failed the write, the
|
|
22
|
+
* shape-aware version completed it.
|
|
23
|
+
*/
|
|
24
|
+
describe('isRetryableAsPost', () => {
|
|
25
|
+
it('fires on the transformed non-WordPress 403, which has no response object', () => {
|
|
26
|
+
// Exactly what codedError() produces for a host-level block.
|
|
27
|
+
const coded = new Error('Site blocked: ... 403 Forbidden from the web server (not WordPress).');
|
|
28
|
+
coded.name = 'http_403_non_wordpress';
|
|
29
|
+
assert.equal(isRetryableAsPost(coded), true);
|
|
30
|
+
});
|
|
31
|
+
it('a status-only check would have missed that error', () => {
|
|
32
|
+
const coded = new Error('Site blocked');
|
|
33
|
+
coded.name = 'http_403_non_wordpress';
|
|
34
|
+
// Documents why the first attempt at this fix did nothing.
|
|
35
|
+
assert.equal(coded.response, undefined);
|
|
36
|
+
});
|
|
37
|
+
it('still fires on the original raw 404 case (old plugin, no v2 route)', () => {
|
|
38
|
+
assert.equal(isRetryableAsPost({ response: { status: 404 } }), true);
|
|
39
|
+
});
|
|
40
|
+
it('fires on a raw 403 and 405', () => {
|
|
41
|
+
assert.equal(isRetryableAsPost({ response: { status: 403 } }), true);
|
|
42
|
+
assert.equal(isRetryableAsPost({ response: { status: 405 } }), true);
|
|
43
|
+
});
|
|
44
|
+
it('does NOT retry a 403 raised by WordPress itself', () => {
|
|
45
|
+
// A real permission decision. Retrying as POST just fails again, slower,
|
|
46
|
+
// and would muddy the error the user needs to see.
|
|
47
|
+
const wpDenied = new Error('Sorry, you are not allowed to edit this post.');
|
|
48
|
+
wpDenied.name = 'rest_cannot_edit';
|
|
49
|
+
assert.equal(isRetryableAsPost(wpDenied), false);
|
|
50
|
+
});
|
|
51
|
+
it('does not retry unrelated failures', () => {
|
|
52
|
+
assert.equal(isRetryableAsPost({ response: { status: 500 } }), false);
|
|
53
|
+
assert.equal(isRetryableAsPost({ response: { status: 401 } }), false);
|
|
54
|
+
assert.equal(isRetryableAsPost(new Error('network down')), false);
|
|
55
|
+
assert.equal(isRetryableAsPost(undefined), false);
|
|
56
|
+
assert.equal(isRetryableAsPost(null), false);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=oq-put-fallback-403.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oq-put-fallback-403.test.js","sourceRoot":"","sources":["../../src/__tests__/oq-put-fallback-403.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,6DAA6D;QAC7D,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;QAChG,KAAK,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;QACxC,KAAK,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACtC,2DAA2D;QAC3D,MAAM,CAAC,KAAK,CAAE,KAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QACrE,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,yEAAyE;QACzE,mDAAmD;QACnD,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QAC5E,QAAQ,CAAC,IAAI,GAAG,kBAAkB,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QACtE,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QACtE,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAClE,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC;QAClD,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2-negotiation-failure.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/v2-negotiation-failure.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { classifyV2Failure, describeV2Failure } from '../wordpress-client.js';
|
|
4
|
+
/**
|
|
5
|
+
* The v2 probe used to swallow its own error:
|
|
6
|
+
*
|
|
7
|
+
* try { ...get('/wp-json/respira/v2/status')... } catch { }
|
|
8
|
+
* return 'v1'
|
|
9
|
+
*
|
|
10
|
+
* so every failure mode reached the user as one message: "respira/v2 is not
|
|
11
|
+
* available on this site", followed by four suggested causes (security plugin,
|
|
12
|
+
* Plain permalinks, WAF/CDN, outdated plugin).
|
|
13
|
+
*
|
|
14
|
+
* M.O. hit this with a 401. All four suggestions were false: permalinks were
|
|
15
|
+
* /%postname%/, there was no CDN, the plugin was on the newest version, and
|
|
16
|
+
* /wp-json/respira/v2/status answered 401 rather than 404 — meaning the route
|
|
17
|
+
* existed, WordPress routed to it, and it ran far enough to reach its auth
|
|
18
|
+
* check. The message described the one situation that provably was not
|
|
19
|
+
* happening, and cost most of a day.
|
|
20
|
+
*/
|
|
21
|
+
const SITE = 'https://example.com';
|
|
22
|
+
const err = (status, data) => ({ response: { status, data } });
|
|
23
|
+
describe('classifyV2Failure', () => {
|
|
24
|
+
it('401 is auth, not a missing namespace', () => {
|
|
25
|
+
const f = classifyV2Failure(err(401, { code: 'respira_invalid_site_token', message: 'token out of date' }));
|
|
26
|
+
assert.equal(f.kind, 'auth');
|
|
27
|
+
assert.equal(f.status, 401);
|
|
28
|
+
assert.equal(f.code, 'respira_invalid_site_token');
|
|
29
|
+
});
|
|
30
|
+
it('403 is auth too', () => {
|
|
31
|
+
assert.equal(classifyV2Failure(err(403)).kind, 'auth');
|
|
32
|
+
});
|
|
33
|
+
it('404 is the one case that really means unavailable', () => {
|
|
34
|
+
assert.equal(classifyV2Failure(err(404)).kind, 'missing');
|
|
35
|
+
});
|
|
36
|
+
it('500 is a server fault, distinct from both', () => {
|
|
37
|
+
assert.equal(classifyV2Failure(err(500)).kind, 'server');
|
|
38
|
+
});
|
|
39
|
+
it('no response at all is a network failure', () => {
|
|
40
|
+
assert.equal(classifyV2Failure({ message: 'ECONNREFUSED' }).kind, 'network');
|
|
41
|
+
});
|
|
42
|
+
it('an undefined error does not throw', () => {
|
|
43
|
+
assert.equal(classifyV2Failure(undefined).kind, 'network');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('describeV2Failure', () => {
|
|
47
|
+
it("the auth message does NOT repeat the four causes that misled the reporter", () => {
|
|
48
|
+
const msg = describeV2Failure(classifyV2Failure(err(401)), SITE);
|
|
49
|
+
assert.ok(!/Wordfence/.test(msg), msg);
|
|
50
|
+
assert.ok(!/permalinks/i.test(msg), msg);
|
|
51
|
+
assert.ok(!/Cloudflare/.test(msg), msg);
|
|
52
|
+
assert.ok(!/outdated/i.test(msg), msg);
|
|
53
|
+
});
|
|
54
|
+
it('the auth message states what a 401 actually proves', () => {
|
|
55
|
+
const msg = describeV2Failure(classifyV2Failure(err(401)), SITE);
|
|
56
|
+
assert.ok(/routes exist and are reachable/.test(msg), msg);
|
|
57
|
+
assert.ok(/authentication result, not a missing namespace/.test(msg), msg);
|
|
58
|
+
});
|
|
59
|
+
it('it names the deactivated-key cause, which is the one that actually happened', () => {
|
|
60
|
+
const msg = describeV2Failure(classifyV2Failure(err(401)), SITE);
|
|
61
|
+
assert.ok(/lapsed licence disables API keys/.test(msg), msg);
|
|
62
|
+
assert.ok(/reconnect the site/i.test(msg), msg);
|
|
63
|
+
});
|
|
64
|
+
it("it quotes the site's own words when they were given", () => {
|
|
65
|
+
const msg = describeV2Failure(classifyV2Failure(err(401, { message: 'The saved connection token for this site is out of date.' })), SITE);
|
|
66
|
+
assert.ok(msg.includes('The saved connection token for this site is out of date.'), msg);
|
|
67
|
+
});
|
|
68
|
+
it('a real 404 keeps the original guidance, which is correct for a 404', () => {
|
|
69
|
+
const msg = describeV2Failure(classifyV2Failure(err(404)), SITE);
|
|
70
|
+
assert.ok(/is not available on this site/.test(msg), msg);
|
|
71
|
+
assert.ok(/Wordfence/.test(msg), msg);
|
|
72
|
+
});
|
|
73
|
+
it('a null failure falls back to the 404 text rather than inventing a cause', () => {
|
|
74
|
+
const msg = describeV2Failure(null, SITE);
|
|
75
|
+
assert.ok(/is not available on this site/.test(msg), msg);
|
|
76
|
+
});
|
|
77
|
+
it('the server branch reports the status instead of blaming the namespace', () => {
|
|
78
|
+
const msg = describeV2Failure(classifyV2Failure(err(500, { message: 'Fatal error' })), SITE);
|
|
79
|
+
assert.ok(/HTTP 500/.test(msg), msg);
|
|
80
|
+
assert.ok(/debug\.log/.test(msg), msg);
|
|
81
|
+
assert.ok(!/Wordfence/.test(msg), msg);
|
|
82
|
+
});
|
|
83
|
+
it('the network branch says connectivity, not configuration', () => {
|
|
84
|
+
const msg = describeV2Failure(classifyV2Failure({ message: 'ETIMEDOUT' }), SITE);
|
|
85
|
+
assert.ok(/connectivity failure, not a plugin problem/.test(msg), msg);
|
|
86
|
+
assert.ok(msg.includes('ETIMEDOUT'), msg);
|
|
87
|
+
});
|
|
88
|
+
it('branches where probing helps point at the probe URL for the specific site', () => {
|
|
89
|
+
// Not the auth branch: /v2/status answers 401 to an unauthenticated browser
|
|
90
|
+
// whether or not the key is the problem, so sending someone there proves
|
|
91
|
+
// nothing. Its next step is reconnecting, asserted above.
|
|
92
|
+
for (const e of [err(404), err(500), { message: 'x' }]) {
|
|
93
|
+
const msg = describeV2Failure(classifyV2Failure(e), SITE);
|
|
94
|
+
assert.ok(msg.includes(`${SITE}/wp-json/respira/v2/status`), msg);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
it('no branch ever tells the reader the namespace is missing when it is not', () => {
|
|
98
|
+
for (const e of [err(401), err(403), err(500), { message: 'x' }]) {
|
|
99
|
+
const msg = describeV2Failure(classifyV2Failure(e), SITE);
|
|
100
|
+
assert.ok(!/is not available on this site/.test(msg), msg);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=v2-negotiation-failure.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2-negotiation-failure.test.js","sourceRoot":"","sources":["../../src/__tests__/v2-negotiation-failure.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2-negotiation-through-real-client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/v2-negotiation-through-real-client.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, it, after } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import http from 'node:http';
|
|
4
|
+
import { WordPressClient } from '../wordpress-client.js';
|
|
5
|
+
/**
|
|
6
|
+
* The check the pure-unit tests structurally cannot make.
|
|
7
|
+
*
|
|
8
|
+
* v2-negotiation-failure.test.ts feeds classifyV2Failure a hand-built
|
|
9
|
+
* `{response:{status:401}}`. In production no such object ever reaches it: the
|
|
10
|
+
* shared axios error interceptor funnels every non-2xx through handleError ->
|
|
11
|
+
* codedError, which returns `new Error(message)` with the status recorded only
|
|
12
|
+
* in `.name`. `.response` is gone.
|
|
13
|
+
*
|
|
14
|
+
* So an implementation that reads `error.response.status` passes every unit
|
|
15
|
+
* test and misclassifies *every real failure* as a network problem — telling a
|
|
16
|
+
* customer with a deactivated key that the site could not be reached. That is
|
|
17
|
+
* the same trap that made the first PUT->POST fallback fix a no-op (it needed
|
|
18
|
+
* isRetryableAsPost() matching on `.name` for exactly this reason).
|
|
19
|
+
*
|
|
20
|
+
* These tests run a real HTTP server, a real WordPressClient with its real
|
|
21
|
+
* interceptors, and assert on the message a user would actually see.
|
|
22
|
+
*/
|
|
23
|
+
const servers = [];
|
|
24
|
+
after(() => {
|
|
25
|
+
for (const s of servers)
|
|
26
|
+
s.close();
|
|
27
|
+
});
|
|
28
|
+
/** Start a server that answers the v2 status probe with a fixed status. */
|
|
29
|
+
async function clientAnswering(status, body) {
|
|
30
|
+
const server = http.createServer((_req, res) => {
|
|
31
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
32
|
+
res.end(JSON.stringify(body));
|
|
33
|
+
});
|
|
34
|
+
servers.push(server);
|
|
35
|
+
await new Promise((resolve) => server.listen(0, resolve));
|
|
36
|
+
const { port } = server.address();
|
|
37
|
+
return new WordPressClient({
|
|
38
|
+
id: 'probe',
|
|
39
|
+
name: 'probe',
|
|
40
|
+
url: `http://127.0.0.1:${port}`,
|
|
41
|
+
apiKey: 'respira_test_key',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/** Drive a v2-gated call and return the message the user would see. */
|
|
45
|
+
async function v2FailureMessage(client) {
|
|
46
|
+
try {
|
|
47
|
+
await client.listSnapshots({ post_id: 1 });
|
|
48
|
+
return '(no error thrown)';
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
return String(err?.message ?? err);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
describe('v2 negotiation through the real client', () => {
|
|
55
|
+
it('a real 401 is reported as an auth rejection, not a missing namespace', async () => {
|
|
56
|
+
const client = await clientAnswering(401, {
|
|
57
|
+
code: 'respira_invalid_site_token',
|
|
58
|
+
message: 'The saved connection token for this site is out of date.',
|
|
59
|
+
});
|
|
60
|
+
const msg = await v2FailureMessage(client);
|
|
61
|
+
assert.match(msg, /rejected this API key \(HTTP 401/);
|
|
62
|
+
assert.doesNotMatch(msg, /is not available on this site/);
|
|
63
|
+
// The four causes that sent the reporter chasing firewall rules for a day.
|
|
64
|
+
assert.doesNotMatch(msg, /Wordfence/);
|
|
65
|
+
assert.doesNotMatch(msg, /permalinks/i);
|
|
66
|
+
assert.doesNotMatch(msg, /Cloudflare/);
|
|
67
|
+
});
|
|
68
|
+
it("and quotes the site's own explanation", async () => {
|
|
69
|
+
const client = await clientAnswering(401, {
|
|
70
|
+
code: 'respira_invalid_site_token',
|
|
71
|
+
message: 'The saved connection token for this site is out of date.',
|
|
72
|
+
});
|
|
73
|
+
const msg = await v2FailureMessage(client);
|
|
74
|
+
assert.match(msg, /The saved connection token for this site is out of date\./);
|
|
75
|
+
assert.match(msg, /respira_invalid_site_token/);
|
|
76
|
+
});
|
|
77
|
+
it('a 403 is auth too', async () => {
|
|
78
|
+
const client = await clientAnswering(403, { code: 'respira_scope_denied', message: 'nope' });
|
|
79
|
+
const msg = await v2FailureMessage(client);
|
|
80
|
+
assert.match(msg, /rejected this API key \(HTTP 403/);
|
|
81
|
+
});
|
|
82
|
+
it('a real 404 keeps the original guidance, which is correct only for a 404', async () => {
|
|
83
|
+
const client = await clientAnswering(404, { code: 'rest_no_route', message: 'No route' });
|
|
84
|
+
const msg = await v2FailureMessage(client);
|
|
85
|
+
assert.match(msg, /is not available on this site/);
|
|
86
|
+
assert.match(msg, /Wordfence/);
|
|
87
|
+
});
|
|
88
|
+
it('a 500 names the status instead of blaming the namespace', async () => {
|
|
89
|
+
const client = await clientAnswering(500, { code: 'fatal', message: 'Fatal error' });
|
|
90
|
+
const msg = await v2FailureMessage(client);
|
|
91
|
+
assert.match(msg, /returned HTTP 500 on this site/);
|
|
92
|
+
assert.doesNotMatch(msg, /is not available on this site/);
|
|
93
|
+
});
|
|
94
|
+
it('a dead host is reported as connectivity, not configuration', async () => {
|
|
95
|
+
// Nothing listening: a genuine transport failure, where there IS no status.
|
|
96
|
+
const client = new WordPressClient({
|
|
97
|
+
id: 'probe', name: 'probe', url: 'http://127.0.0.1:1', apiKey: 'respira_test_key',
|
|
98
|
+
});
|
|
99
|
+
const msg = await v2FailureMessage(client);
|
|
100
|
+
assert.match(msg, /Could not reach respira\/v2|connectivity failure/);
|
|
101
|
+
assert.doesNotMatch(msg, /Wordfence/);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=v2-negotiation-through-real-client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2-negotiation-through-real-client.test.js","sourceRoot":"","sources":["../../src/__tests__/v2-negotiation-through-real-client.test.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgBH,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;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgBH,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;IA2yGtB;;;;;;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;CA+CV"}
|
package/dist/server.js
CHANGED
|
@@ -1836,7 +1836,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1836
1836
|
},
|
|
1837
1837
|
{
|
|
1838
1838
|
name: 'wordpress_update_page',
|
|
1839
|
-
description: 'Update PAGE-LEVEL fields (title, status, slug, full HTML content replacement, custom CSS). For editing SPECIFIC elements WITHIN a page (text widgets, images, buttons, headings), use respira_update_element instead — it is element-aware, builder-aware, snapshot-safe, and works across all
|
|
1839
|
+
description: 'Update PAGE-LEVEL fields (title, status, slug, full HTML content replacement, custom CSS). For editing SPECIFIC elements WITHIN a page (text widgets, images, buttons, headings), use respira_update_element instead — it is element-aware, builder-aware, snapshot-safe, and works across all 17 page builders. Use respira_update_page only when you need to replace the entire content body, change the page title/slug/status, or set page-level metadata. When direct editing is enabled and you target an original page, Respira returns a confirmation_required preflight by default so you can choose live/original or duplicate.',
|
|
1840
1840
|
inputSchema: {
|
|
1841
1841
|
type: 'object',
|
|
1842
1842
|
properties: {
|
|
@@ -1860,6 +1860,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1860
1860
|
type: 'string',
|
|
1861
1861
|
description: 'URL slug (post_name). Example: "about-us" produces /about-us/.',
|
|
1862
1862
|
},
|
|
1863
|
+
excerpt: {
|
|
1864
|
+
type: 'string',
|
|
1865
|
+
description: 'Post excerpt (wp_posts.post_excerpt) — the "Excerpt"/"Zajawka" panel, RSS summaries and archive teasers. Do NOT pass this inside meta: an excerpt written as post meta reads back fine and renders nowhere.',
|
|
1866
|
+
},
|
|
1863
1867
|
custom_css: {
|
|
1864
1868
|
type: 'string',
|
|
1865
1869
|
description: 'Custom CSS for the page. Auto-detects builder: Divi → _et_pb_custom_css, Elementor → _elementor_page_settings.custom_css.',
|
|
@@ -2137,6 +2141,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2137
2141
|
type: 'string',
|
|
2138
2142
|
description: 'URL slug (post_name). Example: "my-post" produces /my-post/.',
|
|
2139
2143
|
},
|
|
2144
|
+
excerpt: {
|
|
2145
|
+
type: 'string',
|
|
2146
|
+
description: 'Post excerpt (wp_posts.post_excerpt) — the "Excerpt"/"Zajawka" panel, RSS summaries and archive teasers. Do NOT pass this inside meta: an excerpt written as post meta reads back fine and renders nowhere.',
|
|
2147
|
+
},
|
|
2140
2148
|
author: {
|
|
2141
2149
|
description: 'Author (user id or user login). Requires edit_others_posts capability when different from the acting user.',
|
|
2142
2150
|
oneOf: [
|
|
@@ -2293,7 +2301,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2293
2301
|
properties: {
|
|
2294
2302
|
builder: {
|
|
2295
2303
|
type: 'string',
|
|
2296
|
-
description: 'Optional. Builder name. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery. Omit to auto-detect the active site builder.',
|
|
2304
|
+
description: 'Optional. Builder name. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery, flatsome, spectra, kadence, generateblocks, greenshift. Omit to auto-detect the active site builder.',
|
|
2297
2305
|
},
|
|
2298
2306
|
page_id: {
|
|
2299
2307
|
type: 'number',
|
|
@@ -2312,7 +2320,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2312
2320
|
properties: {
|
|
2313
2321
|
builder: {
|
|
2314
2322
|
type: 'string',
|
|
2315
|
-
description: 'Builder name. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery. Use get_builder_info to discover the active builder first.',
|
|
2323
|
+
description: 'Builder name. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery, flatsome, spectra, kadence, generateblocks, greenshift. Use get_builder_info to discover the active builder first.',
|
|
2316
2324
|
},
|
|
2317
2325
|
page_id: {
|
|
2318
2326
|
type: 'number',
|
|
@@ -2376,13 +2384,13 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2376
2384
|
},
|
|
2377
2385
|
{
|
|
2378
2386
|
name: 'wordpress_inject_builder_content',
|
|
2379
|
-
description: 'REPLACE (or append to) the entire page builder layout. WARNING: By default this REPLACES all existing content (use mode:"append" to add content without destroying existing elements). For editing a single module, use wordpress_update_module instead. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery. For Divi, divi_version is required ("4" or "5"). Replacing a page that already has content with mode="replace" requires confirm_replace=true, else returns 409 respira_replace_confirmation_required. Alternatives: mode="append" to add to existing content, or edit_target="live" to overwrite the live page directly. The gate prevents silent data loss.\n\nCanonical Divi 5 nested shape: `content: [{ type: "section", children: [{ type: "row", children: [{ type: "column", children: [{ type: "heading", settings: {...} }] }] }] }]` (rows/cols/columns/modules/elements/innerBlocks also accepted as `children` aliases). Each Divi module needs its render-critical attr in `settings` (e.g. heading→title, text→content, button→button_text); the plugin names any missing one via `respira_divi_required_attrs_missing`.',
|
|
2387
|
+
description: 'REPLACE (or append to) the entire page builder layout. WARNING: By default this REPLACES all existing content (use mode:"append" to add content without destroying existing elements). For editing a single module, use wordpress_update_module instead. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery, flatsome, spectra, kadence, generateblocks, greenshift. For Divi, divi_version is required ("4" or "5"). Replacing a page that already has content with mode="replace" requires confirm_replace=true, else returns 409 respira_replace_confirmation_required. Alternatives: mode="append" to add to existing content, or edit_target="live" to overwrite the live page directly. The gate prevents silent data loss.\n\nCanonical Divi 5 nested shape: `content: [{ type: "section", children: [{ type: "row", children: [{ type: "column", children: [{ type: "heading", settings: {...} }] }] }] }]` (rows/cols/columns/modules/elements/innerBlocks also accepted as `children` aliases). Each Divi module needs its render-critical attr in `settings` (e.g. heading→title, text→content, button→button_text); the plugin names any missing one via `respira_divi_required_attrs_missing`.',
|
|
2380
2388
|
inputSchema: {
|
|
2381
2389
|
type: 'object',
|
|
2382
2390
|
properties: {
|
|
2383
2391
|
builder: {
|
|
2384
2392
|
type: 'string',
|
|
2385
|
-
description: 'Builder identifier. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery.',
|
|
2393
|
+
description: 'Builder identifier. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery, flatsome, spectra, kadence, generateblocks, greenshift.',
|
|
2386
2394
|
},
|
|
2387
2395
|
page_id: {
|
|
2388
2396
|
type: 'number',
|
|
@@ -2442,7 +2450,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2442
2450
|
properties: {
|
|
2443
2451
|
builder: {
|
|
2444
2452
|
type: 'string',
|
|
2445
|
-
description: 'Builder name. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery.',
|
|
2453
|
+
description: 'Builder name. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery, flatsome, spectra, kadence, generateblocks, greenshift.',
|
|
2446
2454
|
},
|
|
2447
2455
|
page_id: {
|
|
2448
2456
|
type: 'number',
|
|
@@ -4424,6 +4432,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4424
4432
|
type: 'string',
|
|
4425
4433
|
description: 'URL slug (post_name).',
|
|
4426
4434
|
},
|
|
4435
|
+
excerpt: {
|
|
4436
|
+
type: 'string',
|
|
4437
|
+
description: 'Post excerpt (wp_posts.post_excerpt) — the "Excerpt"/"Zajawka" panel, RSS summaries and archive teasers. Do NOT pass this inside meta: an excerpt written as post meta reads back fine and renders nowhere.',
|
|
4438
|
+
},
|
|
4427
4439
|
author: {
|
|
4428
4440
|
description: 'Author (user id or user login). Requires edit_others_posts capability when different from the acting user.',
|
|
4429
4441
|
oneOf: [
|
|
@@ -7843,7 +7855,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
7843
7855
|
// --- Element Operations ---
|
|
7844
7856
|
{
|
|
7845
7857
|
name: 'wordpress_find_element',
|
|
7846
|
-
description: 'Find an element in a page by ID, type, CSS class, text content, or uncode_shortcode_id. This is the PRIMARY tool for locating content to edit — use it instead of searching the database or reading PHP files. Works with all
|
|
7858
|
+
description: 'Find an element in a page by ID, type, CSS class, text content, or uncode_shortcode_id. This is the PRIMARY tool for locating content to edit — use it instead of searching the database or reading PHP files. Works with all 17 page builders. Use identifier_type "content" to search by visible text (e.g. find a heading containing "2025" to update a year). On WPBakery + Uncode pages, prefer identifier_type "uncode_shortcode_id" — the id is stable across saves and theme updates while class lists and admin labels drift. Returns matching element(s) with their position, settings, and element ID for use with update_element.',
|
|
7847
7859
|
inputSchema: {
|
|
7848
7860
|
type: 'object',
|
|
7849
7861
|
properties: {
|
|
@@ -7865,7 +7877,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
7865
7877
|
},
|
|
7866
7878
|
{
|
|
7867
7879
|
name: 'wordpress_update_element',
|
|
7868
|
-
description: 'Update settings or content on a specific element in a page. This is the PRIMARY tool for making content changes (text edits, style changes, image swaps, link updates, etc.). Works with all
|
|
7880
|
+
description: 'Update settings or content on a specific element in a page. This is the PRIMARY tool for making content changes (text edits, style changes, image swaps, link updates, etc.). Works with all 17 page builders. First use find_element to locate the element, then pass the same identifier here with the updates object containing the new values. On WPBakery + Uncode pages, prefer identifier_type "uncode_shortcode_id" for stable round-trip matching. Response includes target_id, original_id, edit_target ("live" or "duplicate"), is_duplicate, duplicate_created and post_status so the caller can never mistake a duplicate-routed write for a live-page change.\n\nLive-edit confirmation: when respira_allow_direct_edit=1 AND the post is a published original, the first call returns `status: "confirmation_required"` with a `next_call_examples` payload. Re-call with the suggested `edit_target` (and `confirm_live_edit: true` for the live path) to acknowledge. Both params are exposed at the top level here so autonomous flows can complete the handshake through MCP without polluting the `updates` object.',
|
|
7869
7881
|
inputSchema: {
|
|
7870
7882
|
type: 'object',
|
|
7871
7883
|
properties: {
|