@respira/wordpress-mcp-server 6.18.6 → 6.19.0
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/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +403 -39
- package/dist/server.js.map +1 -1
- package/dist/wordpress-client.d.ts +90 -6
- package/dist/wordpress-client.d.ts.map +1 -1
- package/dist/wordpress-client.js +136 -17
- package/dist/wordpress-client.js.map +1 -1
- package/package.json +7 -4
- package/skills/respira-setup/SKILL.md +54 -0
package/dist/server.js
CHANGED
|
@@ -7,8 +7,9 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
7
7
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
9
9
|
import { execSync } from 'child_process';
|
|
10
|
-
import { readFileSync } from 'fs';
|
|
11
|
-
import {
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
import { dirname, join, resolve } from 'path';
|
|
12
13
|
import { fileURLToPath } from 'url';
|
|
13
14
|
import { WordPressClient } from './wordpress-client.js';
|
|
14
15
|
import { RespiraVersionChecker } from './version-checker.js';
|
|
@@ -25,6 +26,49 @@ import { getUsageEmitter } from './usage-emitter.js';
|
|
|
25
26
|
* 6.11.4 release and never tracked subsequent npm publishes. Mirrors
|
|
26
27
|
* the existing MCP_CLIENT_VERSION helper in wordpress-client.ts.
|
|
27
28
|
*/
|
|
29
|
+
/**
|
|
30
|
+
* B-19 (v6.18.7): persist the last active site to disk so the MCP process
|
|
31
|
+
* restoring across restarts (e.g. `claude_desktop_config.json` reload,
|
|
32
|
+
* `npx -y @respira/wordpress-mcp-server@latest` cache rebuild, OS reboot)
|
|
33
|
+
* doesn't always snap back to the default site. Reported by A.D. as the
|
|
34
|
+
* single biggest day-to-day friction across sessions on 2026-05-23.
|
|
35
|
+
*
|
|
36
|
+
* State lives in `~/.respira/state.json`. Single small object so we can
|
|
37
|
+
* extend it without a schema migration; only `last_active_site_id` is
|
|
38
|
+
* read today. Best-effort: any IO failure logs to stderr and falls back
|
|
39
|
+
* to the configured default site (the pre-v6.18.7 behaviour).
|
|
40
|
+
*/
|
|
41
|
+
const STATE_DIR_PATH = join(homedir(), '.respira');
|
|
42
|
+
const STATE_FILE_PATH = join(STATE_DIR_PATH, 'state.json');
|
|
43
|
+
function loadRespiraMcpState() {
|
|
44
|
+
try {
|
|
45
|
+
if (!existsSync(STATE_FILE_PATH)) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
const raw = readFileSync(STATE_FILE_PATH, 'utf8');
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
if (parsed && typeof parsed === 'object') {
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
console.error('[respira-mcp] could not read', STATE_FILE_PATH, '-', err?.message);
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
function saveRespiraMcpState(patch) {
|
|
60
|
+
try {
|
|
61
|
+
if (!existsSync(STATE_DIR_PATH)) {
|
|
62
|
+
mkdirSync(STATE_DIR_PATH, { recursive: true, mode: 0o700 });
|
|
63
|
+
}
|
|
64
|
+
const current = loadRespiraMcpState();
|
|
65
|
+
const next = { ...current, ...patch, schema_version: 1 };
|
|
66
|
+
writeFileSync(STATE_FILE_PATH, JSON.stringify(next, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.error('[respira-mcp] could not write', STATE_FILE_PATH, '-', err?.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
28
72
|
const MCP_SERVER_VERSION = (() => {
|
|
29
73
|
try {
|
|
30
74
|
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -487,12 +531,42 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
487
531
|
}
|
|
488
532
|
}
|
|
489
533
|
});
|
|
534
|
+
// B-19 (v6.18.7): restore the persisted last_active_site_id if present
|
|
535
|
+
// and the site is still known + allowed. Falls back silently to the
|
|
536
|
+
// default site picked above when the persisted id is stale (the
|
|
537
|
+
// customer renamed sites between client versions, hit by A.D.).
|
|
538
|
+
const persisted = loadRespiraMcpState();
|
|
539
|
+
if (persisted.last_active_site_id) {
|
|
540
|
+
const restored = this.sites.get(persisted.last_active_site_id);
|
|
541
|
+
if (restored && this.isSiteAllowed(restored)) {
|
|
542
|
+
this.currentSite = restored;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
490
545
|
this.setupHandlers();
|
|
491
546
|
}
|
|
492
547
|
getSiteSummary(site) {
|
|
493
548
|
return site.getSiteSummary(site.getSiteId() === this.defaultSiteId);
|
|
494
549
|
}
|
|
495
|
-
|
|
550
|
+
/**
|
|
551
|
+
* N6 fix (v6.19.0): the response envelope `site` field must reflect the
|
|
552
|
+
* client that ACTUALLY serviced the call, not the global default. Pre-fix,
|
|
553
|
+
* every tool wrapped its response with `site: this.currentSite.summary` —
|
|
554
|
+
* so a call with `site_id: "mihai-love"` against a default of `cekt-ro`
|
|
555
|
+
* ran correctly but reported `site: cekt-ro` in the envelope, breaking
|
|
556
|
+
* Cowork multi-tab parallel sessions that branched on the response site
|
|
557
|
+
* field. Now: if args carries `site_id` and it resolves to a known site,
|
|
558
|
+
* return THAT site's summary. Otherwise fall back to global `currentSite`.
|
|
559
|
+
*/
|
|
560
|
+
getActiveSiteSummary(args) {
|
|
561
|
+
const overrideId = args && typeof args === 'object' && args !== null && typeof args.site_id === 'string'
|
|
562
|
+
? args.site_id
|
|
563
|
+
: null;
|
|
564
|
+
if (overrideId) {
|
|
565
|
+
const overrideSite = this.sites.get(overrideId);
|
|
566
|
+
if (overrideSite) {
|
|
567
|
+
return this.getSiteSummary(overrideSite);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
496
570
|
if (!this.currentSite) {
|
|
497
571
|
return null;
|
|
498
572
|
}
|
|
@@ -854,8 +928,8 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
854
928
|
message: `Connected ${sites.length} site${sites.length === 1 ? '' : 's'} via the Respira Cowork token. The config was written to ~/.respira/config.json. Restart this Cowork chat (or open a new one) so the MCP server picks up the new sites.`,
|
|
855
929
|
};
|
|
856
930
|
}
|
|
857
|
-
withSiteContext(result) {
|
|
858
|
-
const site = this.getActiveSiteSummary();
|
|
931
|
+
withSiteContext(result, args) {
|
|
932
|
+
const site = this.getActiveSiteSummary(args);
|
|
859
933
|
if (!site) {
|
|
860
934
|
return result;
|
|
861
935
|
}
|
|
@@ -887,7 +961,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
887
961
|
// respira_* alias to wordpress_* before dispatch.
|
|
888
962
|
const canonical = this.normalizeToolName(name).canonical;
|
|
889
963
|
if (canonical === 'wordpress_redeem_token') {
|
|
890
|
-
return this.withSiteContext(await this.redeemInstallToken(String(args?.token || '')));
|
|
964
|
+
return this.withSiteContext(await this.redeemInstallToken(String(args?.token || '')), args);
|
|
891
965
|
}
|
|
892
966
|
if (!this.currentSite) {
|
|
893
967
|
throw new Error('No WordPress site configured');
|
|
@@ -915,7 +989,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
915
989
|
// Handle soft errors (e.g. unknown tool) — return isError without throwing.
|
|
916
990
|
if (result && result.__respira_is_error) {
|
|
917
991
|
const { __respira_is_error: _, ...errorPayload } = result;
|
|
918
|
-
const activeSite = this.getActiveSiteSummary();
|
|
992
|
+
const activeSite = this.getActiveSiteSummary(args);
|
|
919
993
|
return {
|
|
920
994
|
content: [
|
|
921
995
|
{
|
|
@@ -926,7 +1000,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
926
1000
|
isError: true,
|
|
927
1001
|
};
|
|
928
1002
|
}
|
|
929
|
-
const resultWithSite = this.withSiteContext(result);
|
|
1003
|
+
const resultWithSite = this.withSiteContext(result, args);
|
|
930
1004
|
const resultWithNotice = await this.attachUpdateNotice(resultWithSite);
|
|
931
1005
|
const resultWithVersionWarning = this.attachVersionWarning(resultWithNotice);
|
|
932
1006
|
return {
|
|
@@ -946,7 +1020,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
946
1020
|
}
|
|
947
1021
|
this.currentSite?.setCurrentToolName(null);
|
|
948
1022
|
if (error instanceof ToolTimeoutError) {
|
|
949
|
-
const activeSite = this.getActiveSiteSummary();
|
|
1023
|
+
const activeSite = this.getActiveSiteSummary(args);
|
|
950
1024
|
return {
|
|
951
1025
|
content: [
|
|
952
1026
|
{
|
|
@@ -1037,7 +1111,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1037
1111
|
}
|
|
1038
1112
|
}
|
|
1039
1113
|
const errorWithUpdateNotice = await this.appendUpdateNoticeToError(errorMessage);
|
|
1040
|
-
const activeSite = this.getActiveSiteSummary();
|
|
1114
|
+
const activeSite = this.getActiveSiteSummary(args);
|
|
1115
|
+
// B-17 (v6.18.7): gate the JS stack trace behind RESPIRA_DEBUG.
|
|
1116
|
+
// Pre-v6.18.7 every error response carried error.stack with full
|
|
1117
|
+
// `file:///opt/homebrew/lib/node_modules/.../wordpress-client.js:NNN`
|
|
1118
|
+
// paths, leaking the operator's local filesystem layout to whoever
|
|
1119
|
+
// collects the MCP response. The stack is only useful when actively
|
|
1120
|
+
// debugging the MCP server itself; production should not see it.
|
|
1121
|
+
const debugEnabled = process.env.RESPIRA_DEBUG === '1' ||
|
|
1122
|
+
process.env.RESPIRA_DEBUG === 'true' ||
|
|
1123
|
+
process.env.NODE_ENV === 'development';
|
|
1124
|
+
const stack = debugEnabled && error?.stack ? error.stack : undefined;
|
|
1041
1125
|
return {
|
|
1042
1126
|
content: [
|
|
1043
1127
|
{
|
|
@@ -1045,7 +1129,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1045
1129
|
text: JSON.stringify({
|
|
1046
1130
|
error: errorWithUpdateNotice,
|
|
1047
1131
|
site: activeSite,
|
|
1048
|
-
stack
|
|
1132
|
+
stack,
|
|
1049
1133
|
// v6.17: every error response carries the report path so the
|
|
1050
1134
|
// agent has a clear next step when retries + diagnose haven't
|
|
1051
1135
|
// resolved the issue. The agent should ask the user before
|
|
@@ -1144,6 +1228,90 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1144
1228
|
},
|
|
1145
1229
|
readOnlyHint: true,
|
|
1146
1230
|
},
|
|
1231
|
+
{
|
|
1232
|
+
name: 'wordpress_get_divi_migration_readiness',
|
|
1233
|
+
description: 'Generate a Divi 5 migration readiness report for the active WordPress site. ' +
|
|
1234
|
+
'Counts Divi 4 vs Divi 5 vs mixed pages, flags high-risk pages (compatibility-mode pages, deprecated shortcodes), surfaces migration candidates, and tallies unknown shortcodes that may need manual handling. ' +
|
|
1235
|
+
'Call this when the user asks "is my site ready for Divi 5", "should i migrate to Divi 5", "what would break if i migrated", or any variant. ' +
|
|
1236
|
+
'Returns structured JSON (not prose) — the agent shapes it into a readable answer for the user. ' +
|
|
1237
|
+
'Includes a disclaimer line that must be surfaced verbatim: Respira plans + validates; Divi\'s own Migrator performs the conversion. ' +
|
|
1238
|
+
'Report is cached per-user for 24h.',
|
|
1239
|
+
inputSchema: {
|
|
1240
|
+
type: 'object',
|
|
1241
|
+
properties: {},
|
|
1242
|
+
},
|
|
1243
|
+
readOnlyHint: true,
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
name: 'wordpress_abilities_gap_report',
|
|
1247
|
+
description: 'Compare the active plugins on this WordPress site against the curated list of plugins known to expose abilities via the WordPress Abilities API (wp_register_ability). ' +
|
|
1248
|
+
'Returns two lists: (1) installed plugins that have shipped Abilities API support — Respira can wrap them as MCP tools through the Inhale gateway; (2) installed plugins that have not yet adopted the standard — each with a wp.org support URL where the user can file a feature request. ' +
|
|
1249
|
+
'Call this when the user asks "what abilities could my site expose", "which of my plugins support AI", "what AI surface does this site have", "what plugins should i ask to adopt the Abilities API". ' +
|
|
1250
|
+
'Returns structured JSON; the agent shapes it into a readable summary for the user.',
|
|
1251
|
+
inputSchema: {
|
|
1252
|
+
type: 'object',
|
|
1253
|
+
properties: {},
|
|
1254
|
+
},
|
|
1255
|
+
readOnlyHint: true,
|
|
1256
|
+
},
|
|
1257
|
+
{
|
|
1258
|
+
name: 'wordpress_search_abilities',
|
|
1259
|
+
description: 'SEARCH the curated WordPress abilities directory across all known plugins (Elementor, Yoast SEO, WooCommerce, Jetpack, ACF, Akismet, plus Respira\'s own 151 tools — 163 total today, growing weekly via auto-pull). ' +
|
|
1260
|
+
'Use this whenever the user asks "is there an ability that does X", "can WordPress do Y", "what plugins expose Z as MCP", "how do i automate W on my site". ' +
|
|
1261
|
+
'Each result is enriched with per-site context: is_installed (is the plugin active on THIS site), is_inhaled (has the admin opted the ability into Respira\'s MCP surface), install_url (wp.org page when not installed), inhale_admin_url (Respira admin tab when installed but not inhaled), and how_to_invoke (the exact tool call to make when ready). ' +
|
|
1262
|
+
'After finding a matching ability, if is_inhaled is true you can call it via wordpress_invoke_ability. Otherwise surface install_url or inhale_admin_url to the user so they can opt in. ' +
|
|
1263
|
+
'No auth gates: this surface is open to every agent connected to Respira. The directory itself is global; the enrichment is per-site.',
|
|
1264
|
+
inputSchema: {
|
|
1265
|
+
type: 'object',
|
|
1266
|
+
properties: {
|
|
1267
|
+
q: {
|
|
1268
|
+
type: 'string',
|
|
1269
|
+
description: 'Free-text query — matches against ability_name, label, description, plugin_name, category. Optional; an empty query returns the top of the directory.',
|
|
1270
|
+
},
|
|
1271
|
+
plugin: {
|
|
1272
|
+
type: 'string',
|
|
1273
|
+
description: 'Restrict to a single plugin by wp.org slug (e.g. "wordpress-seo") or display name ("Yoast SEO"). Optional.',
|
|
1274
|
+
},
|
|
1275
|
+
category: {
|
|
1276
|
+
type: 'string',
|
|
1277
|
+
description: 'Restrict to a single category (e.g. "SEO", "E-commerce", "Page Builder", "Custom Fields"). Optional.',
|
|
1278
|
+
},
|
|
1279
|
+
kind: {
|
|
1280
|
+
type: 'string',
|
|
1281
|
+
enum: ['read', 'write'],
|
|
1282
|
+
description: 'Restrict to read-only or write abilities. Optional.',
|
|
1283
|
+
},
|
|
1284
|
+
limit: {
|
|
1285
|
+
type: 'integer',
|
|
1286
|
+
description: 'Max results to return (default 20, max 100).',
|
|
1287
|
+
default: 20,
|
|
1288
|
+
},
|
|
1289
|
+
},
|
|
1290
|
+
},
|
|
1291
|
+
readOnlyHint: true,
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
name: 'wordpress_invoke_ability',
|
|
1295
|
+
description: 'Invoke an inhaled WordPress Abilities API ability through the Respira safety wrapper. ' +
|
|
1296
|
+
'The wrapper snapshots the target post if the call looks like a write, runs the underlying ability, logs the call to the audit trail, and returns a structured envelope with a rollback URL when a snapshot was taken. ' +
|
|
1297
|
+
'The ability must (a) be registered on the site via wp_register_ability, AND (b) be inhaled (the admin toggled it on at Respira > MCP Abilities). Otherwise the call returns a structured 403/404 with instructions for the user. ' +
|
|
1298
|
+
'Use this to call any Yoast / Elementor / WooCommerce / ACF / Jetpack ability — anything the site has inhaled — with Respira\'s snapshot-before-write protection layered on top. ' +
|
|
1299
|
+
'Pass ability="vendor/ability-name" and args={…}. The args shape comes from the underlying ability\'s own schema; consult its documentation.',
|
|
1300
|
+
inputSchema: {
|
|
1301
|
+
type: 'object',
|
|
1302
|
+
properties: {
|
|
1303
|
+
ability: {
|
|
1304
|
+
type: 'string',
|
|
1305
|
+
description: 'Full ability name as registered, e.g. "yoast-seo/analyze-post" or "elementor/get-widget".',
|
|
1306
|
+
},
|
|
1307
|
+
args: {
|
|
1308
|
+
type: 'object',
|
|
1309
|
+
description: 'Arguments to forward to the ability\'s execute handler. Shape varies per ability.',
|
|
1310
|
+
},
|
|
1311
|
+
},
|
|
1312
|
+
required: ['ability'],
|
|
1313
|
+
},
|
|
1314
|
+
},
|
|
1147
1315
|
{
|
|
1148
1316
|
name: 'wordpress_get_active_site',
|
|
1149
1317
|
description: 'Return the currently active site in the Respira multi-site configuration.',
|
|
@@ -1297,7 +1465,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1297
1465
|
},
|
|
1298
1466
|
{
|
|
1299
1467
|
name: 'wordpress_delete_page',
|
|
1300
|
-
description: 'Delete a page. IMPORTANT: By default, this only works on Respira-created duplicates.
|
|
1468
|
+
description: 'Delete a page. IMPORTANT: By default, this only works on Respira-created duplicates. To delete an original: pass `force=true` AND `confirm_live_edit=true`. The "Allow Direct Editing" setting in Respira must also be enabled (disabled by default for safety).\n\nApproval flow (added v6.14.2): the first call returns `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. Pre-v6.14.2 the schema did not expose `approval_token` so agents could see the token in the response but had no way to pass it back — leaving every agent-driven cleanup of its own duplicates stuck.',
|
|
1301
1469
|
inputSchema: {
|
|
1302
1470
|
type: 'object',
|
|
1303
1471
|
properties: {
|
|
@@ -1307,7 +1475,11 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1307
1475
|
},
|
|
1308
1476
|
force: {
|
|
1309
1477
|
type: 'boolean',
|
|
1310
|
-
description: 'Force delete even if not a duplicate',
|
|
1478
|
+
description: 'Force delete even if not a duplicate. Requires confirm_live_edit=true.',
|
|
1479
|
+
},
|
|
1480
|
+
confirm_live_edit: {
|
|
1481
|
+
type: 'boolean',
|
|
1482
|
+
description: 'N37 fix (v6.19.0): operator confirmation that a force-delete on an original is intended. The server has always required this alongside force=true; pre-v6.19.0 it was undocumented and the surfaced approval response leaked it as a "surreptitious" field. Now in the schema.',
|
|
1311
1483
|
},
|
|
1312
1484
|
approval_token: {
|
|
1313
1485
|
type: 'string',
|
|
@@ -1385,6 +1557,33 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1385
1557
|
required: ['original_id'],
|
|
1386
1558
|
},
|
|
1387
1559
|
},
|
|
1560
|
+
{
|
|
1561
|
+
name: 'respira_duplicate_with_translations',
|
|
1562
|
+
description: 'Duplicate a WPML-linked page or post together with all its language translations into a new independent translation group. Use this when a site uses WPML and you need a safe copy of a multilingual page (e.g. an EN+RO report page) to edit without touching the original. Each language copy is duplicated, WPML metadata is cleared, and all duplicates are re-linked under a new translation group. Returns a map of language_code → {original_id, duplicate_id, url}. On non-WPML sites falls back to a single-language duplicate. Call respira_get_site_context first to check whether WPML is active.',
|
|
1563
|
+
inputSchema: {
|
|
1564
|
+
type: 'object',
|
|
1565
|
+
properties: {
|
|
1566
|
+
post_id: {
|
|
1567
|
+
type: 'number',
|
|
1568
|
+
description: 'ID of any page/post in the translation group (can be any language version).',
|
|
1569
|
+
},
|
|
1570
|
+
new_titles: {
|
|
1571
|
+
type: 'object',
|
|
1572
|
+
description: 'Optional map of language_code to title string for the new duplicates. Example: {"en": "Reports - Draft", "ro": "Rapoarte - Ciornă"}. When omitted, the original titles are kept.',
|
|
1573
|
+
additionalProperties: { type: 'string' },
|
|
1574
|
+
},
|
|
1575
|
+
suffix: {
|
|
1576
|
+
type: 'string',
|
|
1577
|
+
description: 'Optional suffix used internally to label the duplicate (not appended to the title unless new_titles is also omitted). Defaults to a timestamp.',
|
|
1578
|
+
},
|
|
1579
|
+
type: {
|
|
1580
|
+
type: 'string',
|
|
1581
|
+
description: 'Post type slug: "page" (default), "post", or a custom post type.',
|
|
1582
|
+
},
|
|
1583
|
+
},
|
|
1584
|
+
required: ['post_id'],
|
|
1585
|
+
},
|
|
1586
|
+
},
|
|
1388
1587
|
{
|
|
1389
1588
|
name: 'wordpress_update_post',
|
|
1390
1589
|
description: 'Update a post. Supports author reassignment, taxonomy terms (categories/tags/custom), and featured media. When direct editing is enabled and you target an original post, Respira returns a confirmation_required preflight by default so you can choose live/original or duplicate.',
|
|
@@ -1782,7 +1981,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1782
1981
|
},
|
|
1783
1982
|
{
|
|
1784
1983
|
name: 'wordpress_get_snapshot',
|
|
1785
|
-
description: 'Get one Respira v2 snapshot by UUID.',
|
|
1984
|
+
description: 'Get one Respira v2 snapshot by UUID. Defaults to metadata-only (kind, label, hashes, timestamps, actor). Pass `include=content` to fetch the full builder payload — that adds ~50-100KB per snapshot, so opt in only when you actually need the bytes (e.g. building a content-level diff, restoring partial data).',
|
|
1786
1985
|
inputSchema: {
|
|
1787
1986
|
type: 'object',
|
|
1788
1987
|
properties: {
|
|
@@ -1790,6 +1989,12 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1790
1989
|
type: 'string',
|
|
1791
1990
|
description: 'Snapshot UUID',
|
|
1792
1991
|
},
|
|
1992
|
+
// N26 fix (v6.19.0): `include` opt-in for the heavy fields.
|
|
1993
|
+
// Defaults to metadata-only to keep agent context tight.
|
|
1994
|
+
include: {
|
|
1995
|
+
type: 'string',
|
|
1996
|
+
description: 'CSV of optional sections to include in the response. Currently supports "content" (adds the full builder payload). Default: metadata-only.',
|
|
1997
|
+
},
|
|
1793
1998
|
},
|
|
1794
1999
|
required: ['snapshot_uuid'],
|
|
1795
2000
|
},
|
|
@@ -1831,7 +2036,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1831
2036
|
},
|
|
1832
2037
|
{
|
|
1833
2038
|
name: 'wordpress_apply_builder_patch',
|
|
1834
|
-
description: 'Apply
|
|
2039
|
+
description: 'Apply a list of targeted builder patch operations. Each operation is { identifier: { id|admin_label|path|type [+match_content] }, updates: { content?, attributes?, ...flat_settings? } }. The identifier block follows the same shape as wordpress_find_element\'s identifier; the updates block follows the same shape as wordpress_update_module\'s updates. Returns 400 respira_patch_invalid_operation when an entry is missing either field. This is NOT a JSON-Patch document — do not pass { op, path, value } shapes (those will be rejected). Example: { operations: [{ identifier: { admin_label: "Hero" }, updates: { admin_label: "Hero Content" } }] }.',
|
|
1835
2040
|
inputSchema: {
|
|
1836
2041
|
type: 'object',
|
|
1837
2042
|
properties: {
|
|
@@ -1849,8 +2054,28 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1849
2054
|
},
|
|
1850
2055
|
operations: {
|
|
1851
2056
|
type: 'array',
|
|
1852
|
-
description: '
|
|
1853
|
-
items: {
|
|
2057
|
+
description: 'List of patch operations applied in order.',
|
|
2058
|
+
items: {
|
|
2059
|
+
type: 'object',
|
|
2060
|
+
required: ['identifier', 'updates'],
|
|
2061
|
+
properties: {
|
|
2062
|
+
identifier: {
|
|
2063
|
+
type: 'object',
|
|
2064
|
+
description: 'Element identifier. Provide exactly one of id / admin_label / path / type. match_content disambiguates duplicates of the same type.',
|
|
2065
|
+
properties: {
|
|
2066
|
+
id: { type: 'string', description: 'Builder element ID (Divi 5 _nodeId, Elementor element id, Bricks id, etc).' },
|
|
2067
|
+
admin_label: { type: 'string', description: 'Admin label / navigator label.' },
|
|
2068
|
+
path: { description: 'Index path as either a dot-string "0.1.2" or an array [0,1,2].' },
|
|
2069
|
+
type: { type: 'string', description: 'Module type (e.g. "divi/heading", "et_pb_text", "core/paragraph").' },
|
|
2070
|
+
match_content: { type: 'string', description: 'Optional content substring for disambiguation.' },
|
|
2071
|
+
},
|
|
2072
|
+
},
|
|
2073
|
+
updates: {
|
|
2074
|
+
type: 'object',
|
|
2075
|
+
description: 'Patch to merge into the matched element. Same shape as wordpress_update_module.updates: { content?: string, attributes?: object, ...flat_settings_keys? }. Flat keys (admin_label, background_color, font_size, ...) route through the adapter\'s deep-set helper.',
|
|
2076
|
+
},
|
|
2077
|
+
},
|
|
2078
|
+
},
|
|
1854
2079
|
},
|
|
1855
2080
|
edit_target: {
|
|
1856
2081
|
type: 'string',
|
|
@@ -2059,7 +2284,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2059
2284
|
},
|
|
2060
2285
|
{
|
|
2061
2286
|
name: 'wordpress_get_core_web_vitals',
|
|
2062
|
-
description: 'Get Core Web Vitals metrics (LCP, FID, CLS) for a page.',
|
|
2287
|
+
description: 'Get Core Web Vitals metrics (LCP, FID, CLS) for a page.\n\n**Deprecated path** (v6.19.0+, Phase E Tier 1): the current implementation returns a HEURISTIC estimate from static page analysis, NOT real Lighthouse / CrUX data. The response includes `data_source: "respira_heuristic_v1"` and a `_deprecation` field. For real measurements use `respira_run_pagespeed_audit` (Phase E Tier 2).',
|
|
2063
2288
|
inputSchema: {
|
|
2064
2289
|
type: 'object',
|
|
2065
2290
|
properties: {
|
|
@@ -2072,6 +2297,44 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2072
2297
|
},
|
|
2073
2298
|
readOnlyHint: true,
|
|
2074
2299
|
},
|
|
2300
|
+
{
|
|
2301
|
+
// Phase E Tier 2 (v6.19.0): real PageSpeed Insights v5 audit. The
|
|
2302
|
+
// honest replacement for `get_core_web_vitals` heuristic. Returns
|
|
2303
|
+
// Lighthouse lab data + CrUX field data (when available) + the
|
|
2304
|
+
// opportunity list ranked by metric savings.
|
|
2305
|
+
name: 'wordpress_run_pagespeed_audit',
|
|
2306
|
+
description: 'Run a real PageSpeed Insights v5 audit against a public URL. Returns Lighthouse lab data (scores for performance/accessibility/best-practices/seo, lab metrics FCP/LCP/TBT/CLS/SI/TTI, opportunities ranked by savings_ms, diagnostics) plus CrUX field data (real-user p75 metrics over the trailing ~28 days, when Google has enough traffic to publish them for the URL). Cached for 1 hour per (url, strategy). Pass `fresh=true` to bypass the cache. `strategy="both"` runs mobile + desktop in series. Set the `respira_pagespeed_api_key` WordPress option to lift the 1 req/sec free-tier rate limit to 200 req/min.',
|
|
2307
|
+
inputSchema: {
|
|
2308
|
+
type: 'object',
|
|
2309
|
+
properties: {
|
|
2310
|
+
page_id: { type: 'number', description: 'Page ID to audit. Resolved to the public permalink server-side before the PSI call.' },
|
|
2311
|
+
url: { type: 'string', description: 'Optional explicit URL override. When provided, skips the page_id → permalink resolution. Use for staging / preview / partner-site URLs.' },
|
|
2312
|
+
strategy: { type: 'string', enum: ['mobile', 'desktop', 'both'], default: 'mobile', description: 'Audit profile. Default mobile.' },
|
|
2313
|
+
fresh: { type: 'boolean', default: false, description: 'Bypass the 1h transient cache.' },
|
|
2314
|
+
site_id: { type: 'string', description: 'Per-call site override.' },
|
|
2315
|
+
},
|
|
2316
|
+
},
|
|
2317
|
+
readOnlyHint: true,
|
|
2318
|
+
},
|
|
2319
|
+
{
|
|
2320
|
+
// Phase E Tier 2 (v6.19.0): standard-analyzer-envelope wrapper around
|
|
2321
|
+
// PSI. Feeds the Health tab composite via Respira_Site_Health's new
|
|
2322
|
+
// `pagespeed` slot. Use for periodic site-wide health passes; use
|
|
2323
|
+
// `respira_run_pagespeed_audit` for the raw lab+field data.
|
|
2324
|
+
name: 'wordpress_analyze_pagespeed',
|
|
2325
|
+
description: 'Analyzer-envelope wrapper around the PSI audit. Returns the standard `{success, score, grade, issues, recommendations, metrics, data_source, measured_at}` shape that the Reports → Health tab consumes. Score = Lighthouse performance score (0-100). Grade A≥90, B≥75, C≥60, D≥40, F<40. Issues derived from Lighthouse opportunities, ranked by savings_ms. CrUX p75 field-data surfaces as an info-priority recommendation when available.',
|
|
2326
|
+
inputSchema: {
|
|
2327
|
+
type: 'object',
|
|
2328
|
+
properties: {
|
|
2329
|
+
page_id: { type: 'number', description: 'Page ID to audit.' },
|
|
2330
|
+
url: { type: 'string', description: 'Optional explicit URL override.' },
|
|
2331
|
+
strategy: { type: 'string', enum: ['mobile', 'desktop'], default: 'mobile' },
|
|
2332
|
+
fresh: { type: 'boolean', default: false, description: 'Bypass the 1h cache.' },
|
|
2333
|
+
site_id: { type: 'string', description: 'Per-call site override.' },
|
|
2334
|
+
},
|
|
2335
|
+
},
|
|
2336
|
+
readOnlyHint: true,
|
|
2337
|
+
},
|
|
2075
2338
|
{
|
|
2076
2339
|
name: 'wordpress_analyze_images',
|
|
2077
2340
|
description: 'Analyze image optimization opportunities including missing alt text, large files, and unoptimized formats.',
|
|
@@ -2182,10 +2445,14 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2182
2445
|
// Accessibility tools
|
|
2183
2446
|
{
|
|
2184
2447
|
name: 'wordpress_list_accessibility_scans',
|
|
2185
|
-
description: 'List previous accessibility scans with scores and violation counts.',
|
|
2448
|
+
description: 'List previous accessibility scans with scores and violation counts. Pass `summary_only=true` (default) for a compact response that omits violations[].nodes and the per-violation AI fix prompts — those add up to ~30KB per scan and blow past the MCP response cap with 5+ entries. Use `summary_only=false` only when you genuinely need the full payload.',
|
|
2186
2449
|
inputSchema: {
|
|
2187
2450
|
type: 'object',
|
|
2188
|
-
properties: {
|
|
2451
|
+
properties: {
|
|
2452
|
+
limit: { type: 'number', description: 'Max scans to return (default 20).' },
|
|
2453
|
+
offset: { type: 'number', description: 'Pagination offset (default 0).' },
|
|
2454
|
+
summary_only: { type: 'boolean', description: 'When true (default), strips violations[].nodes and prompts[] from each entry. The full payload remains available via respira_get_accessibility_scan.' },
|
|
2455
|
+
},
|
|
2189
2456
|
},
|
|
2190
2457
|
readOnlyHint: true,
|
|
2191
2458
|
},
|
|
@@ -2195,7 +2462,8 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2195
2462
|
inputSchema: {
|
|
2196
2463
|
type: 'object',
|
|
2197
2464
|
properties: {
|
|
2198
|
-
|
|
2465
|
+
// N15 fix (v6.19.0): scan_id is a UUID STRING (matches what list_accessibility_scans returns), not a number. Pre-fix the schema declared number and callers couldn't pair the two tools.
|
|
2466
|
+
scan_id: { type: 'string', description: 'Scan ID (UUID) to retrieve. Returned in id field of list_accessibility_scans entries.' },
|
|
2199
2467
|
},
|
|
2200
2468
|
required: ['scan_id'],
|
|
2201
2469
|
},
|
|
@@ -2377,7 +2645,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2377
2645
|
},
|
|
2378
2646
|
{
|
|
2379
2647
|
name: 'wordpress_create_user',
|
|
2380
|
-
description: 'Create a new user.',
|
|
2648
|
+
description: 'Create a new user.\n\nApproval flow (N9 fix, v6.19.0): destructive — the first call returns `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. Pre-v6.19.0 the schema did not expose `approval_token` so agents could see the token in the response but had no way to complete the flow.',
|
|
2381
2649
|
inputSchema: {
|
|
2382
2650
|
type: 'object',
|
|
2383
2651
|
properties: {
|
|
@@ -2397,13 +2665,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2397
2665
|
type: 'string',
|
|
2398
2666
|
description: 'User role (default: subscriber)',
|
|
2399
2667
|
},
|
|
2668
|
+
approval_token: {
|
|
2669
|
+
type: 'string',
|
|
2670
|
+
description: 'Single-use approval token from a prior respira_approval_required response. The plugin returns a fresh token on every blocked call (data.approval_request.approval_token); pass that exact string here on the retry to complete the create.',
|
|
2671
|
+
},
|
|
2400
2672
|
},
|
|
2401
2673
|
required: ['username', 'email', 'password'],
|
|
2402
2674
|
},
|
|
2403
2675
|
},
|
|
2404
2676
|
{
|
|
2405
2677
|
name: 'wordpress_update_user',
|
|
2406
|
-
description: 'Update user information.',
|
|
2678
|
+
description: 'Update user information.\n\nApproval flow (N9 fix, v6.19.0): destructive — first call returns `respira_approval_required` with `approval_token`; pass it back to complete the update.',
|
|
2407
2679
|
inputSchema: {
|
|
2408
2680
|
type: 'object',
|
|
2409
2681
|
properties: {
|
|
@@ -2427,6 +2699,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2427
2699
|
type: 'string',
|
|
2428
2700
|
description: 'User role',
|
|
2429
2701
|
},
|
|
2702
|
+
approval_token: {
|
|
2703
|
+
type: 'string',
|
|
2704
|
+
description: 'Single-use approval token from a prior respira_approval_required response. Pass the token from data.approval_request.approval_token to complete the update.',
|
|
2705
|
+
},
|
|
2430
2706
|
},
|
|
2431
2707
|
required: ['id'],
|
|
2432
2708
|
},
|
|
@@ -2434,7 +2710,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2434
2710
|
},
|
|
2435
2711
|
{
|
|
2436
2712
|
name: 'wordpress_delete_user',
|
|
2437
|
-
description: 'Delete a user.',
|
|
2713
|
+
description: 'Delete a user.\n\nApproval flow (N9 fix, v6.19.0): destructive — first call returns `respira_approval_required` with `approval_token`; pass it back to complete the delete.',
|
|
2438
2714
|
inputSchema: {
|
|
2439
2715
|
type: 'object',
|
|
2440
2716
|
properties: {
|
|
@@ -2446,6 +2722,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2446
2722
|
type: 'number',
|
|
2447
2723
|
description: 'User ID to reassign posts to (optional)',
|
|
2448
2724
|
},
|
|
2725
|
+
approval_token: {
|
|
2726
|
+
type: 'string',
|
|
2727
|
+
description: 'Single-use approval token from a prior respira_approval_required response. Pass the token from data.approval_request.approval_token to complete the delete.',
|
|
2728
|
+
},
|
|
2449
2729
|
},
|
|
2450
2730
|
required: ['id'],
|
|
2451
2731
|
},
|
|
@@ -2977,7 +3257,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2977
3257
|
},
|
|
2978
3258
|
{
|
|
2979
3259
|
name: 'wordpress_delete_custom_post',
|
|
2980
|
-
description: 'Delete a custom post.',
|
|
3260
|
+
description: 'Delete a custom post.\n\nSafety flow (N9 fix, v6.19.0): destructive. By default this only works on Respira-created duplicates. To delete an original: pass `force=true` AND `confirm_live_edit=true` (the latter requires "Allow Direct Editing" enabled in Respira settings). Approval gate may also fire — if response is `respira_approval_required`, pass the returned `approval_token` back to complete the delete. Pre-v6.19.0 the schema lacked `force`, `confirm_live_edit`, and `approval_token` so agents could not complete the flow at all.',
|
|
2981
3261
|
inputSchema: {
|
|
2982
3262
|
type: 'object',
|
|
2983
3263
|
properties: {
|
|
@@ -2989,6 +3269,18 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
2989
3269
|
type: 'number',
|
|
2990
3270
|
description: 'Post ID',
|
|
2991
3271
|
},
|
|
3272
|
+
force: {
|
|
3273
|
+
type: 'boolean',
|
|
3274
|
+
description: 'Force delete even if the post is an original (not a Respira-created duplicate). Requires confirm_live_edit=true as well.',
|
|
3275
|
+
},
|
|
3276
|
+
confirm_live_edit: {
|
|
3277
|
+
type: 'boolean',
|
|
3278
|
+
description: 'Operator confirmation that a live-edit on an original is intended. Required alongside force=true. The "Allow Direct Editing" setting in Respira must also be enabled.',
|
|
3279
|
+
},
|
|
3280
|
+
approval_token: {
|
|
3281
|
+
type: 'string',
|
|
3282
|
+
description: 'Single-use approval token from a prior respira_approval_required response. Pass the token from data.approval_request.approval_token to complete the delete.',
|
|
3283
|
+
},
|
|
2992
3284
|
},
|
|
2993
3285
|
required: ['type', 'id'],
|
|
2994
3286
|
},
|
|
@@ -3207,7 +3499,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
3207
3499
|
},
|
|
3208
3500
|
{
|
|
3209
3501
|
name: 'wordpress_delete_menu',
|
|
3210
|
-
description: 'Delete a navigation menu.',
|
|
3502
|
+
description: 'Delete a navigation menu.\n\nApproval flow (N9 fix, v6.19.0): destructive — first call returns `respira_approval_required` with `approval_token`; pass it back to complete the delete.',
|
|
3211
3503
|
inputSchema: {
|
|
3212
3504
|
type: 'object',
|
|
3213
3505
|
properties: {
|
|
@@ -3215,6 +3507,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
3215
3507
|
type: 'number',
|
|
3216
3508
|
description: 'Menu ID',
|
|
3217
3509
|
},
|
|
3510
|
+
approval_token: {
|
|
3511
|
+
type: 'string',
|
|
3512
|
+
description: 'Single-use approval token from a prior respira_approval_required response. Pass the token from data.approval_request.approval_token to complete the delete.',
|
|
3513
|
+
},
|
|
3218
3514
|
},
|
|
3219
3515
|
required: ['id'],
|
|
3220
3516
|
},
|
|
@@ -3925,8 +4221,18 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
3925
4221
|
const errorCode = dispatchError instanceof Error
|
|
3926
4222
|
? dispatchError.name.slice(0, 80)
|
|
3927
4223
|
: dispatchError != null ? 'unknown_error' : null;
|
|
4224
|
+
// v7.1: when an agent calls the universal ability proxy
|
|
4225
|
+
// (wordpress_invoke_ability), record the call under the underlying
|
|
4226
|
+
// ability name (`inhale_ability:<vendor>/<name>`) so the public
|
|
4227
|
+
// /abilities marketplace page can group quality metrics per
|
|
4228
|
+
// ability, not per proxy. The proxy itself stays a single MCP tool
|
|
4229
|
+
// from the agent's perspective.
|
|
4230
|
+
let telemetryToolName = canonical;
|
|
4231
|
+
if (canonical === 'wordpress_invoke_ability' && typeof args?.ability === 'string' && args.ability.includes('/')) {
|
|
4232
|
+
telemetryToolName = `inhale_ability:${args.ability}`;
|
|
4233
|
+
}
|
|
3928
4234
|
emitter.record({
|
|
3929
|
-
toolName:
|
|
4235
|
+
toolName: telemetryToolName,
|
|
3930
4236
|
siteUrl,
|
|
3931
4237
|
durationMs: Date.now() - startedAt,
|
|
3932
4238
|
success: dispatchError === null,
|
|
@@ -4040,6 +4346,20 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4040
4346
|
return await client.getThemeDocs();
|
|
4041
4347
|
case 'wordpress_get_builder_info':
|
|
4042
4348
|
return await client.getBuilderInfo({ debug: Boolean(args?.debug) });
|
|
4349
|
+
case 'wordpress_get_divi_migration_readiness':
|
|
4350
|
+
return await client.getDiviMigrationReadiness();
|
|
4351
|
+
case 'wordpress_abilities_gap_report':
|
|
4352
|
+
return await client.getAbilitiesGapReport();
|
|
4353
|
+
case 'wordpress_search_abilities':
|
|
4354
|
+
return await client.searchAbilities({
|
|
4355
|
+
q: args?.q,
|
|
4356
|
+
plugin: args?.plugin,
|
|
4357
|
+
category: args?.category,
|
|
4358
|
+
kind: args?.kind,
|
|
4359
|
+
limit: args?.limit,
|
|
4360
|
+
});
|
|
4361
|
+
case 'wordpress_invoke_ability':
|
|
4362
|
+
return await client.invokeInhaledAbility(args?.ability, args?.args || {});
|
|
4043
4363
|
case 'wordpress_list_pages':
|
|
4044
4364
|
return await client.listPages(args);
|
|
4045
4365
|
case 'wordpress_read_page':
|
|
@@ -4082,6 +4402,13 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4082
4402
|
...(await client.duplicatePost(args.original_id, args.suffix, args.include)),
|
|
4083
4403
|
respira_approvals_url: client.getApprovalsUrl(),
|
|
4084
4404
|
};
|
|
4405
|
+
case 'respira_duplicate_with_translations':
|
|
4406
|
+
return await client.duplicateWithTranslations({
|
|
4407
|
+
post_id: args.post_id,
|
|
4408
|
+
new_titles: args.new_titles ?? null,
|
|
4409
|
+
suffix: args.suffix ?? null,
|
|
4410
|
+
type: args.type ?? 'page',
|
|
4411
|
+
});
|
|
4085
4412
|
case 'wordpress_update_post': {
|
|
4086
4413
|
const approvalsUrl = client.getApprovalsUrl();
|
|
4087
4414
|
const post = await client.updatePost(args.id, args);
|
|
@@ -4152,6 +4479,9 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4152
4479
|
}
|
|
4153
4480
|
this.currentSite = newSite;
|
|
4154
4481
|
this.cachedFilterContext = null; // Invalidate tool filter cache on site switch.
|
|
4482
|
+
// B-19 (v6.18.7): persist so the next process restart restores
|
|
4483
|
+
// this site instead of falling back to the configured default.
|
|
4484
|
+
saveRespiraMcpState({ last_active_site_id: String(args.site_id) });
|
|
4155
4485
|
return {
|
|
4156
4486
|
success: true,
|
|
4157
4487
|
message: `Switched to site: ${newSite.getSiteName()}`,
|
|
@@ -4206,6 +4536,20 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4206
4536
|
return await client.analyzePerformance(args.page_id);
|
|
4207
4537
|
case 'wordpress_get_core_web_vitals':
|
|
4208
4538
|
return await client.getCoreWebVitals(args.page_id);
|
|
4539
|
+
case 'wordpress_run_pagespeed_audit':
|
|
4540
|
+
return await client.runPageSpeedAudit({
|
|
4541
|
+
page_id: args.page_id,
|
|
4542
|
+
url: args.url,
|
|
4543
|
+
strategy: args.strategy,
|
|
4544
|
+
fresh: args.fresh,
|
|
4545
|
+
});
|
|
4546
|
+
case 'wordpress_analyze_pagespeed':
|
|
4547
|
+
return await client.analyzePagespeed({
|
|
4548
|
+
page_id: args.page_id,
|
|
4549
|
+
url: args.url,
|
|
4550
|
+
strategy: args.strategy,
|
|
4551
|
+
fresh: args.fresh,
|
|
4552
|
+
});
|
|
4209
4553
|
case 'wordpress_analyze_images':
|
|
4210
4554
|
return await client.analyzeImages(args.page_id);
|
|
4211
4555
|
// SEO Analysis
|
|
@@ -4224,7 +4568,11 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4224
4568
|
return await client.checkStructuredData(args.page_id);
|
|
4225
4569
|
// Accessibility
|
|
4226
4570
|
case 'wordpress_list_accessibility_scans':
|
|
4227
|
-
return await client.listAccessibilityScans(
|
|
4571
|
+
return await client.listAccessibilityScans({
|
|
4572
|
+
limit: args.limit,
|
|
4573
|
+
offset: args.offset,
|
|
4574
|
+
summary_only: args.summary_only,
|
|
4575
|
+
});
|
|
4228
4576
|
case 'wordpress_get_accessibility_scan':
|
|
4229
4577
|
return await client.getAccessibilityScan(args.scan_id);
|
|
4230
4578
|
case 'wordpress_scan_page_accessibility':
|
|
@@ -4254,7 +4602,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4254
4602
|
case 'wordpress_update_user':
|
|
4255
4603
|
return await client.updateUser(args.id, args);
|
|
4256
4604
|
case 'wordpress_delete_user':
|
|
4257
|
-
return await client.deleteUser(args.id, args.reassign);
|
|
4605
|
+
return await client.deleteUser(args.id, args.reassign, args.approval_token);
|
|
4258
4606
|
// Comments
|
|
4259
4607
|
case 'wordpress_list_comments':
|
|
4260
4608
|
return await client.listComments(args);
|
|
@@ -4295,7 +4643,11 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4295
4643
|
case 'wordpress_update_custom_post':
|
|
4296
4644
|
return await client.updateCustomPost(args.type, args.id, args);
|
|
4297
4645
|
case 'wordpress_delete_custom_post':
|
|
4298
|
-
return await client.deleteCustomPost(args.type, args.id
|
|
4646
|
+
return await client.deleteCustomPost(args.type, args.id, {
|
|
4647
|
+
force: args.force,
|
|
4648
|
+
confirm_live_edit: args.confirm_live_edit,
|
|
4649
|
+
approval_token: args.approval_token,
|
|
4650
|
+
});
|
|
4299
4651
|
// Options
|
|
4300
4652
|
case 'wordpress_list_options':
|
|
4301
4653
|
return await client.listOptions(args.search);
|
|
@@ -4324,7 +4676,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4324
4676
|
case 'wordpress_update_menu':
|
|
4325
4677
|
return await client.updateMenu(args.id, args);
|
|
4326
4678
|
case 'wordpress_delete_menu':
|
|
4327
|
-
return await client.deleteMenu(args.id);
|
|
4679
|
+
return await client.deleteMenu(args.id, args.approval_token);
|
|
4328
4680
|
// Menu Locations
|
|
4329
4681
|
case 'wordpress_list_menu_locations':
|
|
4330
4682
|
return await client.listMenuLocations();
|
|
@@ -4344,7 +4696,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4344
4696
|
case 'wordpress_list_snapshots':
|
|
4345
4697
|
return await client.listSnapshots(args);
|
|
4346
4698
|
case 'wordpress_get_snapshot':
|
|
4347
|
-
return await client.getSnapshot(args.snapshot_uuid);
|
|
4699
|
+
return await client.getSnapshot(args.snapshot_uuid, args.include);
|
|
4348
4700
|
case 'wordpress_diff_snapshots':
|
|
4349
4701
|
return await client.diffSnapshots(args.snapshot_uuid_a, args.snapshot_uuid_b);
|
|
4350
4702
|
case 'wordpress_restore_snapshot':
|
|
@@ -4493,7 +4845,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4493
4845
|
// --- Element Operations ---
|
|
4494
4846
|
{
|
|
4495
4847
|
name: 'wordpress_find_element',
|
|
4496
|
-
description: 'Find an element in a page by ID, type, CSS class,
|
|
4848
|
+
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 12 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.',
|
|
4497
4849
|
inputSchema: {
|
|
4498
4850
|
type: 'object',
|
|
4499
4851
|
properties: {
|
|
@@ -4501,7 +4853,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4501
4853
|
identifier_type: {
|
|
4502
4854
|
type: 'string',
|
|
4503
4855
|
description: 'How to find the element',
|
|
4504
|
-
enum: ['id', 'type', 'class', 'content'],
|
|
4856
|
+
enum: ['id', 'type', 'class', 'content', 'uncode_shortcode_id'],
|
|
4505
4857
|
},
|
|
4506
4858
|
identifier_value: { type: 'string', description: 'Value to search for' },
|
|
4507
4859
|
match_content: {
|
|
@@ -4515,14 +4867,14 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4515
4867
|
},
|
|
4516
4868
|
{
|
|
4517
4869
|
name: 'wordpress_update_element',
|
|
4518
|
-
description: 'Update settings or content on a specific element in a page. This is the PRIMARY tool for making content changes — use it for text edits, style changes, image swaps, link updates, etc. Works with all 12 page builders. First use find_element to locate the element, then pass the same identifier here with the updates object containing the new values. 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.',
|
|
4870
|
+
description: 'Update settings or content on a specific element in a page. This is the PRIMARY tool for making content changes — use it for text edits, style changes, image swaps, link updates, etc. Works with all 12 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.',
|
|
4519
4871
|
inputSchema: {
|
|
4520
4872
|
type: 'object',
|
|
4521
4873
|
properties: {
|
|
4522
4874
|
post_id: { type: 'number', description: 'Page/post ID' },
|
|
4523
4875
|
identifier_type: {
|
|
4524
4876
|
type: 'string',
|
|
4525
|
-
enum: ['id', 'type', 'class', 'content'],
|
|
4877
|
+
enum: ['id', 'type', 'class', 'content', 'uncode_shortcode_id'],
|
|
4526
4878
|
},
|
|
4527
4879
|
identifier_value: { type: 'string', description: 'Value to match' },
|
|
4528
4880
|
updates: {
|
|
@@ -4593,15 +4945,27 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4593
4945
|
},
|
|
4594
4946
|
{
|
|
4595
4947
|
name: 'wordpress_batch_update',
|
|
4596
|
-
description: 'Apply multiple element operations to a page in a single atomic transaction. Extracts content once, applies all operations, and injects once.',
|
|
4948
|
+
description: 'Apply multiple element operations to a page in a single atomic transaction. Extracts content once, applies all operations, and injects once. Per-operation identifier shape: {identifier_type, identifier_value} (NOT {identifier: {...}} — that\'s respira_apply_builder_patch\'s shape). Use this when you have a list of updates to apply against existing elements; use respira_apply_builder_patch when you have a patch-document with a richer identifier block.',
|
|
4597
4949
|
inputSchema: {
|
|
4598
4950
|
type: 'object',
|
|
4599
4951
|
properties: {
|
|
4600
4952
|
post_id: { type: 'number', description: 'Page/post ID' },
|
|
4601
4953
|
operations: {
|
|
4602
4954
|
type: 'array',
|
|
4603
|
-
description: 'Array of operations:
|
|
4604
|
-
items: {
|
|
4955
|
+
description: 'Array of operations applied in order. Each op: {action: "update"|"remove"|"move"|"duplicate", identifier_type: "id"|"admin_label"|"type"|"path", identifier_value: string, updates?: object, target_container_path?: string, position?: number, match_content?: string}.',
|
|
4956
|
+
items: {
|
|
4957
|
+
type: 'object',
|
|
4958
|
+
required: ['action', 'identifier_type', 'identifier_value'],
|
|
4959
|
+
properties: {
|
|
4960
|
+
action: { type: 'string', enum: ['update', 'remove', 'move', 'duplicate'] },
|
|
4961
|
+
identifier_type: { type: 'string', enum: ['id', 'admin_label', 'type', 'widget_type', 'path', 'content'] },
|
|
4962
|
+
identifier_value: { type: 'string' },
|
|
4963
|
+
updates: { type: 'object', description: 'For action=update: flat settings patch OR nested attrs bucket. See respira_update_element for shape.' },
|
|
4964
|
+
target_container_path: { type: 'string', description: 'For action=move: destination container path.' },
|
|
4965
|
+
position: { type: 'number', description: 'For action=move: index inside the target container.' },
|
|
4966
|
+
match_content: { type: 'string', description: 'Optional disambiguator for type-based identifiers when the same type appears multiple times on the page.' },
|
|
4967
|
+
},
|
|
4968
|
+
},
|
|
4605
4969
|
},
|
|
4606
4970
|
},
|
|
4607
4971
|
required: ['post_id', 'operations'],
|