@respira/wordpress-mcp-server 8.3.24 → 8.3.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +97 -20
  3. package/TOOL_CATALOG.md +445 -307
  4. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.js +3 -2
  5. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.js.map +1 -1
  6. package/dist/__tests__/81c478df-create-page-duplicate-id-aliases.test.d.ts +11 -0
  7. package/dist/__tests__/81c478df-create-page-duplicate-id-aliases.test.d.ts.map +1 -0
  8. package/dist/__tests__/81c478df-create-page-duplicate-id-aliases.test.js +80 -0
  9. package/dist/__tests__/81c478df-create-page-duplicate-id-aliases.test.js.map +1 -0
  10. package/dist/__tests__/plugin-slug-path-encoding.test.d.ts +2 -0
  11. package/dist/__tests__/plugin-slug-path-encoding.test.d.ts.map +1 -0
  12. package/dist/__tests__/plugin-slug-path-encoding.test.js +41 -0
  13. package/dist/__tests__/plugin-slug-path-encoding.test.js.map +1 -0
  14. package/dist/__tests__/release-9-surface.test.d.ts +2 -0
  15. package/dist/__tests__/release-9-surface.test.d.ts.map +1 -0
  16. package/dist/__tests__/release-9-surface.test.js +197 -0
  17. package/dist/__tests__/release-9-surface.test.js.map +1 -0
  18. package/dist/install-skills.d.ts +4 -5
  19. package/dist/install-skills.d.ts.map +1 -1
  20. package/dist/install-skills.js +4 -5
  21. package/dist/install-skills.js.map +1 -1
  22. package/dist/server.d.ts.map +1 -1
  23. package/dist/server.js +252 -73
  24. package/dist/server.js.map +1 -1
  25. package/dist/skill-prompts.d.ts +43 -0
  26. package/dist/skill-prompts.d.ts.map +1 -0
  27. package/dist/skill-prompts.js +135 -0
  28. package/dist/skill-prompts.js.map +1 -0
  29. package/dist/wordpress-client.d.ts +40 -11
  30. package/dist/wordpress-client.d.ts.map +1 -1
  31. package/dist/wordpress-client.js +74 -22
  32. package/dist/wordpress-client.js.map +1 -1
  33. package/package.json +2 -2
  34. package/skills/activity-report-composer/SKILL.md +17 -5
  35. package/skills/activity-report-composer/metadata.json +2 -2
  36. package/skills/art-direction/SKILL.md +9 -3
  37. package/skills/art-direction/metadata.json +7 -3
  38. package/skills/brand-voice-synthesizer/SKILL.md +36 -16
  39. package/skills/brand-voice-synthesizer/metadata.json +8 -4
  40. package/skills/build-oxygen6-page/SKILL.md +17 -2
  41. package/skills/conversion-audit/SKILL.md +12 -0
  42. package/skills/custom-post-type-architect/SKILL.md +12 -0
  43. package/skills/figma-to-bricks/SKILL.md +4 -2
  44. package/skills/figma-to-divi/SKILL.md +4 -2
  45. package/skills/figma-to-elementor/SKILL.md +8 -1
  46. package/skills/figma-to-gutenberg/SKILL.md +4 -2
  47. package/skills/html-to-breakdance/SKILL.md +7 -3
  48. package/skills/html-to-breakdance/metadata.json +7 -3
  49. package/skills/html-to-bricks/SKILL.md +7 -3
  50. package/skills/html-to-bricks/metadata.json +7 -3
  51. package/skills/internal-link-builder/SKILL.md +12 -0
  52. package/skills/migrate-beaver-builder-to-bricks/SKILL.md +19 -3
  53. package/skills/migrate-beaver-builder-to-gutenberg/SKILL.md +19 -3
  54. package/skills/migrate-brizy-to-gutenberg/SKILL.md +19 -3
  55. package/skills/migrate-divi-to-breakdance/SKILL.md +19 -3
  56. package/skills/migrate-divi-to-bricks/SKILL.md +19 -3
  57. package/skills/migrate-divi-to-gutenberg/SKILL.md +19 -3
  58. package/skills/migrate-elementor-to-breakdance/SKILL.md +20 -4
  59. package/skills/migrate-elementor-to-bricks/SKILL.md +20 -4
  60. package/skills/migrate-elementor-to-gutenberg/SKILL.md +20 -4
  61. package/skills/migrate-elementor-to-oxygen/SKILL.md +20 -4
  62. package/skills/migrate-oxygen-to-breakdance/SKILL.md +19 -3
  63. package/skills/migrate-oxygen-to-bricks/SKILL.md +19 -3
  64. package/skills/migrate-thrive-architect-to-gutenberg/SKILL.md +19 -3
  65. package/skills/migrate-visual-composer-to-gutenberg/SKILL.md +19 -3
  66. package/skills/migrate-wpbakery-to-bricks/SKILL.md +19 -3
  67. package/skills/migrate-wpbakery-to-gutenberg/SKILL.md +19 -3
  68. package/skills/mobile-experience-report/SKILL.md +12 -0
  69. package/skills/murmur-review-loop/SKILL.md +142 -0
  70. package/skills/page-template-library/SKILL.md +12 -0
  71. package/skills/respira-builder-edits/SKILL.md +1 -0
  72. package/skills/respira-setup-assistant/SKILL.md +12 -0
  73. package/skills/respira-site-audit/SKILL.md +18 -6
  74. package/skills/respira-woocommerce/SKILL.md +1 -0
  75. package/skills/seo-aeo-amplifier/SKILL.md +14 -2
  76. package/skills/seo-aeo-amplifier/metadata.json +2 -2
  77. package/skills/stale-content-detector/SKILL.md +12 -0
  78. package/skills/technical-debt-audit/SKILL.md +14 -2
  79. package/skills/woo-marketing-campaigns/SKILL.md +12 -9
  80. package/skills/woo-pricing-promotions/SKILL.md +9 -9
  81. package/skills/woocommerce-health-check/SKILL.md +22 -8
  82. package/skills/wordpress-ai-image-optimizer/SKILL.md +14 -2
  83. package/skills/wordpress-ai-image-optimizer/metadata.json +2 -2
  84. package/skills/wordpress-security-review/SKILL.md +154 -0
  85. package/skills/wordpress-site-dna/README.md +1 -1
  86. package/skills/wordpress-site-dna/SKILL.md +52 -19
  87. package/tool-capabilities.json +3171 -370
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Provides tools for AI coding assistants to interact with WordPress sites
5
5
  */
6
- import { Server } from '@modelcontextprotocol/server';
6
+ import { Server, ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';
7
7
  import { setMcpClientInfo } from './agent-signature.js';
8
8
  import { serveStdio } from '@modelcontextprotocol/server/stdio';
9
9
  import { execSync } from 'child_process';
@@ -17,6 +17,7 @@ import { RespiraVersionChecker } from './version-checker.js';
17
17
  import { getBricksTools, dispatchBricksTool } from './bricks-tools.js';
18
18
  import { getElementorTools, dispatchElementorTool } from './elementor-tools.js';
19
19
  import { getAcfTools, resolveAcfToolName } from './acf-tools.js';
20
+ import { listSkillPrompts, getSkillPrompt } from './skill-prompts.js';
20
21
  import { getUsageEmitter, deriveToolKind } from './usage-emitter.js';
21
22
  import { FSE_OPERATION_ITEM_SCHEMA } from './fse-operation-schema.js';
22
23
  import { collapseLauncherProcesses, validateApiKeyShape, describeConfigOrigin, configOriginKey } from './config.js';
@@ -133,6 +134,19 @@ function saveRespiraMcpState(patch) {
133
134
  console.error('[respira-mcp] could not write', STATE_FILE_PATH, '-', err?.message);
134
135
  }
135
136
  }
137
+ /**
138
+ * Tools removed in a major release, keyed by the name a caller would still
139
+ * send. dispatchToolCall answers them with the replacement instead of a bare
140
+ * "Unknown tool". Not advertised and not dispatched, so the parity gate never
141
+ * sees them.
142
+ */
143
+ const RETIRED_TOOLS = {
144
+ woocommerce_sales_report: {
145
+ replacement: 'woocommerce_revenue_summary',
146
+ removedIn: '9.0',
147
+ note: 'It takes date_start and date_end (ISO, in the store timezone) instead of period, date_min and date_max, and states exactly which order statuses and amounts it counts.',
148
+ },
149
+ };
136
150
  const MCP_SERVER_VERSION = (() => {
137
151
  try {
138
152
  const currentDir = dirname(fileURLToPath(import.meta.url));
@@ -566,6 +580,8 @@ export class RespiraWordPressServer {
566
580
  }, {
567
581
  capabilities: {
568
582
  tools: {},
583
+ // The bundled skills catalog, one prompt per skill (skill-prompts.ts).
584
+ prompts: {},
569
585
  },
570
586
  instructions: `${SOUL_MD_CONTENT}
571
587
 
@@ -592,7 +608,7 @@ Follow these on every site. They are what separates a great result from a frustr
592
608
 
593
609
  ## Tool naming
594
610
 
595
- Always use respira_* tool names (e.g. respira_update_page, respira_find_element). The wordpress_* names are deprecated aliases and will be removed in v6.0.
611
+ Always use respira_* tool names (e.g. respira_update_page, respira_find_element). The older wordpress_* names still answer, so a saved call keeps working, but new calls should use respira_*.
596
612
 
597
613
  ## Step 1: Understand the site
598
614
 
@@ -1826,6 +1842,20 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
1826
1842
  tools: await this.getTools(),
1827
1843
  };
1828
1844
  });
1845
+ // Skills as prompts. Served from the skills folder bundled into the
1846
+ // package, so listing them needs no site and no network: a client can
1847
+ // offer every Respira skill as a slash command before a site is set up.
1848
+ this.server.setRequestHandler('prompts/list', async () => {
1849
+ return { prompts: listSkillPrompts() };
1850
+ });
1851
+ this.server.setRequestHandler('prompts/get', async (request) => {
1852
+ const { name, arguments: promptArgs } = request.params;
1853
+ const prompt = getSkillPrompt(String(name || ''), promptArgs);
1854
+ if (!prompt) {
1855
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Unknown prompt: ${name}. Call prompts/list for the Respira skills this server offers.`);
1856
+ }
1857
+ return prompt;
1858
+ });
1829
1859
  // Handle tool calls
1830
1860
  this.server.setRequestHandler('tools/call', async (request) => {
1831
1861
  const { name, arguments: args } = request.params;
@@ -2566,12 +2596,20 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2566
2596
  type: 'number',
2567
2597
  description: 'ID of the original page to duplicate',
2568
2598
  },
2599
+ page_id: {
2600
+ type: 'number',
2601
+ description: 'Alias for original_id, matching the builder page tools',
2602
+ },
2603
+ post_id: {
2604
+ type: 'number',
2605
+ description: 'Alias for original_id, matching tools that operate on any post type',
2606
+ },
2569
2607
  suffix: {
2570
2608
  type: 'string',
2571
2609
  description: 'Optional suffix for the duplicated page title',
2572
2610
  },
2573
2611
  },
2574
- required: ['original_id'],
2612
+ required: [],
2575
2613
  },
2576
2614
  annotations: {
2577
2615
  title: "Duplicate Page",
@@ -2649,7 +2687,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2649
2687
  },
2650
2688
  {
2651
2689
  name: 'wordpress_delete_page',
2652
- 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.',
2690
+ 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: 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.',
2653
2691
  inputSchema: {
2654
2692
  type: 'object',
2655
2693
  properties: {
@@ -2663,7 +2701,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2663
2701
  },
2664
2702
  confirm_live_edit: {
2665
2703
  type: 'boolean',
2666
- 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.',
2704
+ description: 'Operator confirmation that a force-delete on an original is intended. Required alongside force=true, and honoured only when "Allow Direct Editing" is enabled in Respira settings.',
2667
2705
  },
2668
2706
  approval_token: {
2669
2707
  type: 'string',
@@ -3081,7 +3119,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3081
3119
  },
3082
3120
  {
3083
3121
  name: 'wordpress_delete_post',
3084
- description: 'Delete a post. 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: destructive — the FIRST call returns `code: respira_approval_required` with `data.approval_request.approval_token`, and deletes nothing. Pass that exact token back via the `approval_token` param on the next call to complete the delete. Deleting a Respira-created duplicate still takes both calls; the approval gate runs before the duplicate check.\n\nFixed in 8.3.7 (ticket 429299b5): this tool used to discard the response body, so the approval token, the refusal reason and the success flag were all invisible and every call looked like an empty response. It now always returns the site\'s answer.',
3122
+ description: 'Delete a post. 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: destructive — the FIRST call returns `code: respira_approval_required` with `data.approval_request.approval_token`, and deletes nothing. Pass that exact token back via the `approval_token` param on the next call to complete the delete. Deleting a Respira-created duplicate still takes both calls; the approval gate runs before the duplicate check.\n\nThe response is always the site\'s answer: the approval token, the refusal reason, or the success flag.',
3085
3123
  inputSchema: {
3086
3124
  type: 'object',
3087
3125
  properties: {
@@ -3324,7 +3362,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3324
3362
  },
3325
3363
  mode: {
3326
3364
  type: 'string',
3327
- description: 'replace (default): OVERWRITES all existing page content. append: adds new content after existing elements, preserving the current layout. As of plugin v7.0.16, replace against a page with existing content requires confirm_replace=true.',
3365
+ description: 'replace (default): OVERWRITES all existing page content. append: adds new content after existing elements, preserving the current layout. Replacing a page that already has content requires confirm_replace=true.',
3328
3366
  enum: ['replace', 'append'],
3329
3367
  },
3330
3368
  confirm_replace: {
@@ -3660,6 +3698,57 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3660
3698
  openWorldHint: false,
3661
3699
  },
3662
3700
  },
3701
+ {
3702
+ // The client report. Before 9.0 it existed only as the ability
3703
+ // respira/generate-activity-report, which invoke_ability cannot reach
3704
+ // (the inhale gate refuses it), so every skill that asked for it
3705
+ // failed at step 1.
3706
+ name: 'wordpress_generate_activity_report',
3707
+ description: 'Generate the client report for a period from this site\'s Respira activity log: what changed, through which tools and builders, concrete before and after examples, notable moments, and time saved against per-tool manual baselines. Returns structured data for you to write up in the chosen framing, such as an agency client report or a personal recap. Reads the log and changes nothing. Defaults to the last 30 days. Needs an administrator key.',
3708
+ inputSchema: {
3709
+ type: 'object',
3710
+ properties: {
3711
+ window_days: { type: 'integer', minimum: 1, maximum: 365, description: 'Days back from now. Default 30. Ignored when start_date and end_date are given.' },
3712
+ start_date: { type: 'string', description: 'ISO 8601 start date. Use with end_date.' },
3713
+ end_date: { type: 'string', description: 'ISO 8601 end date. Use with start_date.' },
3714
+ content_types: {
3715
+ type: 'array',
3716
+ items: { type: 'string', enum: ['pages', 'posts', 'products', 'variables', 'snapshots', 'migrations', 'audits', 'skills'] },
3717
+ description: 'Limit the report to these content types. Default all.',
3718
+ },
3719
+ scope: {
3720
+ type: 'object',
3721
+ properties: {
3722
+ page_ids: { type: 'array', items: { type: 'integer' } },
3723
+ post_ids: { type: 'array', items: { type: 'integer' } },
3724
+ product_ids: { type: 'array', items: { type: 'integer' } },
3725
+ builder: { type: 'string' },
3726
+ },
3727
+ description: 'Focus on specific page, post or product IDs, or on one page builder by slug.',
3728
+ },
3729
+ framing: {
3730
+ type: 'string',
3731
+ enum: ['personal_recap', 'agency_client_report', 'agency_case_study', 'team_report', 'testimonial_draft', 'build_in_public'],
3732
+ description: 'Who the write-up is for. Default personal_recap; use agency_client_report for a client.',
3733
+ },
3734
+ output_mode: { type: 'string', enum: ['structured', 'client_report', 'ops_report'], description: 'structured returns the data; client_report and ops_report add ready-made sections.' },
3735
+ report_tone: { type: 'string', enum: ['client_update', 'retainer_proof', 'internal_ops'], description: 'Tone for the ready-made sections.' },
3736
+ client_name: { type: 'string', description: 'Who an agency report is addressed to.' },
3737
+ include_examples: { type: 'boolean', description: 'Up to 10 edits with 200-character before and after excerpts. Default true.' },
3738
+ include_time_estimates: { type: 'boolean', description: 'Estimate time saved from per-tool baselines. Default true.' },
3739
+ anonymize: { type: 'boolean', description: 'Redact page titles, URLs and excerpts, for a report that will be published. Default false.' },
3740
+ currency: { type: 'string', description: 'ISO 4217 code for the cost estimate. Default EUR.' },
3741
+ hourly_rate: { type: 'number', description: 'When set, time savings include a cost estimate at this hourly rate.' },
3742
+ },
3743
+ },
3744
+ annotations: {
3745
+ title: "Generate Activity Report",
3746
+ readOnlyHint: true,
3747
+ destructiveHint: false,
3748
+ idempotentHint: true,
3749
+ openWorldHint: false,
3750
+ },
3751
+ },
3663
3752
  {
3664
3753
  name: 'wordpress_get_site_template',
3665
3754
  description: 'Read one FSE template or template part as normalized blocks. Returns its source, render route, supported targeted operations, and expected_fingerprint for safe updates.',
@@ -4650,7 +4739,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4650
4739
  },
4651
4740
  probe_timeout_ms: {
4652
4741
  type: 'number',
4653
- description: 'Optional per-probe timeout in milliseconds. Default 30000 (was 15000 pre-v6.17). Clamped to [5000, 60000]. Bump this when probing sites behind slow edge layers (some Cloudflare custom rules add 8+ seconds before forwarding to origin).',
4742
+ description: 'Optional per-probe timeout in milliseconds. Default 30000. Clamped to [5000, 60000]. Bump this when probing sites behind slow edge layers (some Cloudflare custom rules add 8+ seconds before forwarding to origin).',
4654
4743
  minimum: 5000,
4655
4744
  maximum: 60000,
4656
4745
  },
@@ -4786,31 +4875,35 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4786
4875
  },
4787
4876
  },
4788
4877
  {
4878
+ // Until 9.0 this returned a heuristic estimate from static page
4879
+ // analysis. AER's "what a visitor notices first" shortcut and three
4880
+ // skills call it by name expecting field data, so it now runs the real
4881
+ // PageSpeed Insights audit instead of being removed.
4789
4882
  name: 'wordpress_get_core_web_vitals',
4790
- 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).',
4883
+ description: 'Core Web Vitals for a page, measured by Google PageSpeed Insights: LCP, CLS and INP from real Chrome users over the last 28 days when Google publishes field data for the URL, plus Lighthouse lab metrics. Runs the same audit as respira_run_pagespeed_audit with one strategy, so the page must be publicly reachable; drafts and local sites return an error that says so. Cached for 1 hour per URL and strategy.',
4791
4884
  inputSchema: {
4792
4885
  type: 'object',
4793
4886
  properties: {
4794
- page_id: {
4795
- type: 'number',
4796
- description: 'Page ID to analyze',
4797
- },
4887
+ page_id: { type: 'number', description: 'Page ID to measure. Resolved to its public permalink.' },
4888
+ url: { type: 'string', description: 'Explicit public URL to measure instead of a page ID.' },
4889
+ strategy: { type: 'string', enum: ['mobile', 'desktop'], default: 'mobile', description: 'Device profile. Default mobile.' },
4798
4890
  },
4799
- required: ['page_id'],
4891
+ anyOf: [
4892
+ { required: ['page_id'] },
4893
+ { required: ['url'] },
4894
+ ],
4800
4895
  },
4801
4896
  annotations: {
4802
4897
  title: "Core Web Vitals",
4803
4898
  readOnlyHint: true,
4804
4899
  destructiveHint: false,
4805
4900
  idempotentHint: true,
4806
- openWorldHint: false,
4901
+ openWorldHint: true,
4807
4902
  },
4808
4903
  },
4809
4904
  {
4810
- // Phase E Tier 2 (v6.19.0): real PageSpeed Insights v5 audit. The
4811
- // honest replacement for `get_core_web_vitals` heuristic. Returns
4812
- // Lighthouse lab data + CrUX field data (when available) + the
4813
- // opportunity list ranked by metric savings.
4905
+ // Real PageSpeed Insights v5 audit: Lighthouse lab data, CrUX field
4906
+ // data when available, and opportunities ranked by metric savings.
4814
4907
  name: 'wordpress_run_pagespeed_audit',
4815
4908
  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.',
4816
4909
  inputSchema: {
@@ -4836,9 +4929,8 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4836
4929
  },
4837
4930
  },
4838
4931
  {
4839
- // Phase E Tier 2 (v6.19.0): standard-analyzer-envelope wrapper around
4840
- // PSI. Feeds the Health tab composite via Respira_Site_Health's new
4841
- // `pagespeed` slot. Use for periodic site-wide health passes; use
4932
+ // Standard-analyzer-envelope wrapper around PSI. Feeds the Health tab
4933
+ // composite via Respira_Site_Health's `pagespeed` slot. Use for periodic site-wide health passes; use
4842
4934
  // `respira_run_pagespeed_audit` for the raw lab+field data.
4843
4935
  name: 'wordpress_analyze_pagespeed',
4844
4936
  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.',
@@ -5016,7 +5108,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5016
5108
  // Safety & Security center
5017
5109
  {
5018
5110
  name: 'wordpress_run_security_audit',
5019
- description: 'Run a bounded, read-only WordPress security evidence pass. Returns authoritative WordPress/WooCommerce versions, official core checksum mismatches, administrators, standard and must-use plugins, WordPress cron hooks, known advisory indicators, scan coverage and explicit host-level limitations. Set deep_scan=true to inspect uploads and must-use plugin paths for executable files. This tool never deletes or rewrites anything.',
5111
+ description: 'Run a bounded WordPress security audit. Returns authoritative WordPress/WooCommerce versions, official core checksum mismatches, administrators and account hygiene, standard and must-use plugins, themes, WordPress cron hooks, advisory indicators, scan coverage and explicit host-level limitations. The audit reads and never changes the site, deep_scan included. One flag writes: probe_uploads_execution, off by default, puts one inert PHP file in uploads, requests it to learn whether PHP runs there, then deletes it. The result includes known_vulnerabilities: installed core, plugins and themes matched against the Respira vulnerability database for a licensed site, with status ok or unavailable. An unavailable status never means the site is clean. known_vulnerabilities=false skips that match. On plugins older than 9.0, deep_scan also runs the uploads probe.',
5020
5112
  inputSchema: {
5021
5113
  type: 'object',
5022
5114
  properties: {
@@ -5028,7 +5120,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5028
5120
  deep_scan: {
5029
5121
  type: 'boolean',
5030
5122
  default: false,
5031
- description: 'Inspect uploads and must-use plugin paths with strict file, time, symlink and result limits.',
5123
+ description: 'Inspect uploads and must-use plugin paths for executable files, with strict file, time, symlink and result limits. Reads only on plugin 9.0 and newer.',
5124
+ },
5125
+ probe_uploads_execution: {
5126
+ type: 'boolean',
5127
+ default: false,
5128
+ description: 'Writes one inert PHP file into uploads, requests it to test whether PHP executes there, then deletes it. Off by default. File-change monitors such as Wordfence may report the file. Needs plugin 9.0 or newer; older plugins ignore it.',
5129
+ },
5130
+ known_vulnerabilities: {
5131
+ type: 'boolean',
5132
+ default: true,
5133
+ description: 'Match installed core, plugins and themes against the Respira vulnerability database. On by default; false skips that round trip. Needs plugin 9.0 or newer.',
5032
5134
  },
5033
5135
  },
5034
5136
  },
@@ -5042,17 +5144,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5042
5144
  },
5043
5145
  {
5044
5146
  name: 'wordpress_update_core_security',
5045
- description: 'Update WordPress core to an exact, catalog-approved security release on the current release branch. Requires a recent recoverable backup, then explicit two-step approval. The first call returns respira_approval_required and an approval_token; repeat the same arguments with that token. The result verifies the running version, REST/frontend boot and official checksums. WordPress core has no automatic rollback, and the receipt says so.',
5147
+ description: 'Update WordPress core to the security release WordPress.org offers on the site\'s current release branch. Requires a recent recoverable backup, then explicit two-step approval. The first call returns respira_approval_required and an approval_token; repeat the same arguments with that token. The result verifies the running version, REST/frontend boot and official checksums. WordPress core has no automatic rollback, and the receipt says so. Plugins older than 9.0 accept only the releases in their own catalog.',
5046
5148
  inputSchema: {
5047
5149
  type: 'object',
5048
5150
  properties: {
5049
5151
  advisory_id: {
5050
5152
  type: 'string',
5051
- description: 'Reviewed security advisory ID. Do not guess it: run respira_run_security_audit first and use the advisory it reports. The approved list lives in the plugin catalog and grows with each WordPress security release, so any advisory named here would go stale.',
5153
+ description: 'Security advisory ID from respira_run_security_audit, recorded on the receipt. Do not guess it: run the audit first and use the advisory it reports.',
5052
5154
  },
5053
5155
  target_version: {
5054
5156
  type: 'string',
5055
- description: 'Exact approved patch version for that advisory on this site\'s own release branch, as reported by respira_run_security_audit. The plugin rejects anything outside its catalog, and it will not move a site across branches.',
5157
+ description: 'The exact version WordPress.org offers as the update on this site\'s own release branch, as reported by respira_run_security_audit. The plugin refuses any other version and never moves a site across branches.',
5056
5158
  },
5057
5159
  backup_confirmed: {
5058
5160
  type: 'boolean',
@@ -5073,6 +5175,60 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5073
5175
  openWorldHint: true,
5074
5176
  },
5075
5177
  },
5178
+ {
5179
+ name: 'wordpress_update_theme',
5180
+ description: 'Update a theme to the latest version offered to this site, then check that the site still answers (REST API, front page, fatal-error marker). Requires plugin management to be enabled in Respira settings. Approval-gated: the first call returns `respira_approval_required` with an `approval_token`; call again with the same `stylesheet` plus that token. If the result carries `stop_batch: true`, stop updating and check the site. There is no automatic rollback.',
5181
+ inputSchema: {
5182
+ type: 'object',
5183
+ properties: {
5184
+ stylesheet: {
5185
+ type: 'string',
5186
+ description: 'Theme directory name (its stylesheet), as respira_run_security_audit lists it under checks.themes.',
5187
+ },
5188
+ approval_token: {
5189
+ type: 'string',
5190
+ description: 'Second-step approval token returned by the first call. Echo it back unchanged with the same stylesheet.',
5191
+ },
5192
+ },
5193
+ required: ['stylesheet'],
5194
+ },
5195
+ annotations: {
5196
+ title: "Update Theme",
5197
+ readOnlyHint: false,
5198
+ destructiveHint: true,
5199
+ idempotentHint: true,
5200
+ openWorldHint: true,
5201
+ },
5202
+ },
5203
+ {
5204
+ name: 'wordpress_revoke_application_password',
5205
+ description: 'Revoke one WordPress application password, by user id and uuid as respira_run_security_audit reports them under checks.account_hygiene. Anything that signs in with it stops working and it cannot be restored. Approval-gated: the first call returns `respira_approval_required` with an `approval_token`; call again with the same arguments plus that token. The password this request itself signed in with is refused.',
5206
+ inputSchema: {
5207
+ type: 'object',
5208
+ properties: {
5209
+ user_id: {
5210
+ type: 'integer',
5211
+ description: 'The user the application password belongs to.',
5212
+ },
5213
+ uuid: {
5214
+ type: 'string',
5215
+ description: 'The application password uuid from the security audit. Never the password itself.',
5216
+ },
5217
+ approval_token: {
5218
+ type: 'string',
5219
+ description: 'Second-step approval token returned by the first call. Echo it back unchanged with the same user_id and uuid.',
5220
+ },
5221
+ },
5222
+ required: ['user_id', 'uuid'],
5223
+ },
5224
+ annotations: {
5225
+ title: "Revoke Application Password",
5226
+ readOnlyHint: false,
5227
+ destructiveHint: true,
5228
+ idempotentHint: true,
5229
+ openWorldHint: false,
5230
+ },
5231
+ },
5076
5232
  // Accessibility tools
5077
5233
  {
5078
5234
  name: 'wordpress_list_accessibility_scans',
@@ -5155,10 +5311,12 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5155
5311
  openWorldHint: false,
5156
5312
  },
5157
5313
  },
5158
- // Plugin Management tools (EXPERIMENTAL)
5314
+ // Plugin Management tools. The EXPERIMENTAL label came off in 9.0: every
5315
+ // mutation is approval-gated, and activation has probed the site and
5316
+ // rolled itself back on failure since plugin 8.5.5.
5159
5317
  {
5160
5318
  name: 'wordpress_list_plugins',
5161
- description: 'EXPERIMENTAL: List all installed plugins with their status, version, and update availability. Requires plugin management to be enabled in Respira settings.',
5319
+ description: 'List all installed plugins with their status, version, and update availability. Requires plugin management to be enabled in Respira settings.',
5162
5320
  inputSchema: {
5163
5321
  type: 'object',
5164
5322
  properties: {},
@@ -5173,7 +5331,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5173
5331
  },
5174
5332
  {
5175
5333
  name: 'wordpress_install_plugin',
5176
- description: 'EXPERIMENTAL: Install a plugin from WordPress.org or a ZIP URL. Requires plugin management to be enabled in Respira settings. Approval-gated: first call returns `respira_approval_required` with an `approval_token`; call again with that token to complete the install. Use with caution.',
5334
+ description: 'Install a plugin from WordPress.org or a ZIP URL. Requires plugin management to be enabled in Respira settings. Approval-gated: first call returns `respira_approval_required` with an `approval_token`; call again with that token to complete the install. Install only what the site owner asked for.',
5177
5335
  inputSchema: {
5178
5336
  type: 'object',
5179
5337
  properties: {
@@ -5203,7 +5361,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5203
5361
  },
5204
5362
  {
5205
5363
  name: 'wordpress_activate_plugin',
5206
- description: 'EXPERIMENTAL: Safely activate a plugin. Respira checks declared PHP, WordPress, dependency and companion-plugin compatibility, activates in a fresh loopback request, probes REST and frontend boot, and automatically deactivates the plugin if the site fails. Approval-gated. Use force_without_probe only after explicit operator approval on a host that blocks loopback requests.',
5364
+ description: 'Safely activate a plugin. Respira checks declared PHP, WordPress, dependency and companion-plugin compatibility, activates in a fresh loopback request, probes REST and frontend boot, and automatically deactivates the plugin if the site fails. Approval-gated. Use force_without_probe only after explicit operator approval on a host that blocks loopback requests.',
5207
5365
  inputSchema: {
5208
5366
  type: 'object',
5209
5367
  properties: {
@@ -5232,7 +5390,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5232
5390
  },
5233
5391
  {
5234
5392
  name: 'wordpress_deactivate_plugin',
5235
- description: 'EXPERIMENTAL: Deactivate a plugin. Requires plugin management to be enabled in Respira settings. Approval-gated: first call returns `respira_approval_required` with an `approval_token`; call again with the same `slug` plus that token to complete deactivation.',
5393
+ description: 'Deactivate a plugin. Requires plugin management to be enabled in Respira settings. Approval-gated: first call returns `respira_approval_required` with an `approval_token`; call again with the same `slug` plus that token to complete deactivation.',
5236
5394
  inputSchema: {
5237
5395
  type: 'object',
5238
5396
  properties: {
@@ -5257,7 +5415,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5257
5415
  },
5258
5416
  {
5259
5417
  name: 'wordpress_update_plugin',
5260
- description: 'EXPERIMENTAL: Update a plugin to the latest version. Requires plugin management to be enabled in Respira settings. Approval-gated: first call returns `respira_approval_required` with an `approval_token`; call again with the same `slug` plus that token to complete the update. Make sure you have backups before updating.',
5418
+ description: 'Update a plugin to the latest version. Requires plugin management to be enabled in Respira settings. Approval-gated: first call returns `respira_approval_required` with an `approval_token`; call again with the same `slug` plus that token to complete the update. Make sure a recent backup exists before updating, and load the front page afterwards to confirm the site still renders.',
5261
5419
  inputSchema: {
5262
5420
  type: 'object',
5263
5421
  properties: {
@@ -5282,7 +5440,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5282
5440
  },
5283
5441
  {
5284
5442
  name: 'wordpress_delete_plugin',
5285
- description: 'EXPERIMENTAL: Permanently delete a plugin. Requires plugin management to be enabled in Respira settings. The plugin must be deactivated first. Approval-gated: first call returns `respira_approval_required` with an `approval_token`; call again with the same `slug` plus that token to confirm deletion.',
5443
+ description: 'Permanently delete a plugin. Requires plugin management to be enabled in Respira settings. The plugin must be deactivated first. Approval-gated: first call returns `respira_approval_required` with an `approval_token`; call again with the same `slug` plus that token to confirm deletion.',
5286
5444
  inputSchema: {
5287
5445
  type: 'object',
5288
5446
  properties: {
@@ -5357,7 +5515,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5357
5515
  },
5358
5516
  {
5359
5517
  name: 'wordpress_create_user',
5360
- 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.',
5518
+ description: 'Create a new user.\n\nApproval flow: 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.',
5361
5519
  inputSchema: {
5362
5520
  type: 'object',
5363
5521
  properties: {
@@ -5394,7 +5552,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5394
5552
  },
5395
5553
  {
5396
5554
  name: 'wordpress_update_user',
5397
- 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.',
5555
+ description: 'Update user information.\n\nApproval flow: the first call returns `respira_approval_required` with `approval_token`; pass it back to complete the update.',
5398
5556
  inputSchema: {
5399
5557
  type: 'object',
5400
5558
  properties: {
@@ -5435,7 +5593,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5435
5593
  },
5436
5594
  {
5437
5595
  name: 'wordpress_delete_user',
5438
- 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.',
5596
+ description: 'Delete a user.\n\nApproval flow: the first call returns `respira_approval_required` with `approval_token`; pass it back to complete the delete.',
5439
5597
  inputSchema: {
5440
5598
  type: 'object',
5441
5599
  properties: {
@@ -6531,7 +6689,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
6531
6689
  },
6532
6690
  {
6533
6691
  name: 'wordpress_delete_custom_post',
6534
- 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.',
6692
+ description: 'Delete a custom post.\n\nSafety flow: 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). The approval gate may also fire: if the response is `respira_approval_required`, pass the returned `approval_token` back to complete the delete.',
6535
6693
  inputSchema: {
6536
6694
  type: 'object',
6537
6695
  properties: {
@@ -6610,7 +6768,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
6610
6768
  },
6611
6769
  {
6612
6770
  name: 'wordpress_update_option',
6613
- description: 'Update an option value.',
6771
+ description: 'Update an option value. Send the value in the same type get_option returns. A string stays a string, even when it contains JSON, because many plugins store JSON as text and decode it themselves. Send an object or array only for options that already hold one, such as theme_mods. Writing an object or array over an option that holds text is refused unless allow_type_change is true.',
6614
6772
  inputSchema: {
6615
6773
  type: 'object',
6616
6774
  properties: {
@@ -6619,7 +6777,15 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
6619
6777
  description: 'Option name',
6620
6778
  },
6621
6779
  value: {
6622
- description: 'Option value (string, number, boolean, object, or array). For complex options like theme_mods, pass as object not JSON string.',
6780
+ description: 'Option value, in the type get_option returns for this option. Text stays text (JSON-encode it if needed). Objects or arrays only for options that already hold them, such as theme_mods.',
6781
+ },
6782
+ allow_type_change: {
6783
+ type: 'boolean',
6784
+ description: 'Set true only to really replace an option that holds text with an object or array. Without it that write is refused, because a plugin that decodes its own text option can crash on every page load.',
6785
+ },
6786
+ allow_object_overwrite: {
6787
+ type: 'boolean',
6788
+ description: 'Set true only to replace an option that stores a PHP object, and only when the new value keeps the same shape. Without it that write is refused.',
6623
6789
  },
6624
6790
  },
6625
6791
  required: ['option', 'value'],
@@ -6771,7 +6937,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
6771
6937
  },
6772
6938
  {
6773
6939
  name: 'wordpress_delete_media',
6774
- description: 'Delete a media file. Approval-gated since v7.1.0-beta.1 — the first call returns `code: respira_approval_required` with an `approval_token`; pass that token in the second call to confirm. Same flow as `delete_page` / `delete_user` / `delete_plugin`.',
6940
+ description: 'Delete a media file. Approval-gated: the first call returns `code: respira_approval_required` with an `approval_token`; pass that token in the second call to confirm. Same flow as `delete_page` / `delete_user` / `delete_plugin`.',
6775
6941
  inputSchema: {
6776
6942
  type: 'object',
6777
6943
  properties: {
@@ -6891,7 +7057,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
6891
7057
  },
6892
7058
  {
6893
7059
  name: 'wordpress_delete_menu',
6894
- 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.',
7060
+ description: 'Delete a navigation menu.\n\nApproval flow: the first call returns `respira_approval_required` with `approval_token`; pass it back to complete the delete.',
6895
7061
  inputSchema: {
6896
7062
  type: 'object',
6897
7063
  properties: {
@@ -7156,9 +7322,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
7156
7322
  }
7157
7323
  // Generate respira_* aliases for all wordpress_* tools.
7158
7324
  const allTools = this.generateDualTools(tools);
7159
- // Context-aware tool filtering: expose only relevant tools based on
7160
- // detected builder and active plugins. Reduces tool list from ~170 to
7161
- // ~30-60, saving significant tokens in AI agent context windows.
7325
+ // Context-aware tool filtering: builder-specific tools appear only on the
7326
+ // builder they serve, and WooCommerce tools only where WooCommerce runs.
7327
+ // When the site context cannot be read, everything is shown (fail-open).
7328
+ // The README's "Tools on one site" table has the measured counts.
7162
7329
  return this.filterToolsByContext(allTools);
7163
7330
  }
7164
7331
  /**
@@ -7719,25 +7886,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
7719
7886
  openWorldHint: false,
7720
7887
  },
7721
7888
  },
7722
- {
7723
- name: 'woocommerce_sales_report',
7724
- description: 'Deprecated compatibility alias for woocommerce_revenue_summary. Retained for two minor releases.',
7725
- inputSchema: {
7726
- type: 'object',
7727
- properties: {
7728
- period: { type: 'string', description: 'week, month, year, custom' },
7729
- date_min: { type: 'string', description: 'Start date for custom period' },
7730
- date_max: { type: 'string', description: 'End date for custom period' },
7731
- },
7732
- },
7733
- annotations: {
7734
- title: "Sales Report",
7735
- readOnlyHint: true,
7736
- destructiveHint: false,
7737
- idempotentHint: true,
7738
- openWorldHint: false,
7739
- },
7740
- },
7741
7889
  {
7742
7890
  name: 'woocommerce_revenue_summary',
7743
7891
  description: 'Bounded HPOS-compatible revenue summary with currency, store timezone, exact date boundaries, included statuses, pagination coverage, and explicit gross/discount/refund/tax/shipping/net definitions.',
@@ -9784,11 +9932,16 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
9784
9932
  return await client.listPages(args);
9785
9933
  case 'wordpress_read_page':
9786
9934
  return await client.getPage(args.id, args.include);
9787
- case 'wordpress_create_page_duplicate':
9935
+ case 'wordpress_create_page_duplicate': {
9936
+ const originalId = args.original_id ?? args.page_id ?? args.post_id;
9937
+ if (originalId === undefined || originalId === null) {
9938
+ throw new Error('create_page_duplicate needs original_id, page_id, or post_id.');
9939
+ }
9788
9940
  return {
9789
- ...(await client.duplicatePage(args.original_id, args.suffix, args.include)),
9941
+ ...(await client.duplicatePage(originalId, args.suffix, args.include)),
9790
9942
  respira_approvals_url: client.getApprovalsUrl(),
9791
9943
  };
9944
+ }
9792
9945
  case 'wordpress_update_page': {
9793
9946
  const approvalsUrl = client.getApprovalsUrl();
9794
9947
  const page = await client.updatePage(args.id, args);
@@ -10092,7 +10245,11 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
10092
10245
  case 'wordpress_analyze_performance':
10093
10246
  return await client.analyzePerformance(args.page_id);
10094
10247
  case 'wordpress_get_core_web_vitals':
10095
- return await client.getCoreWebVitals(args.page_id);
10248
+ return await client.getCoreWebVitals({
10249
+ page_id: args.page_id,
10250
+ url: args.url,
10251
+ strategy: args.strategy,
10252
+ });
10096
10253
  case 'wordpress_run_pagespeed_audit':
10097
10254
  return await client.runPageSpeedAudit({
10098
10255
  page_id: args.page_id,
@@ -10124,7 +10281,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
10124
10281
  case 'wordpress_check_structured_data':
10125
10282
  return await client.checkStructuredData(args.page_id);
10126
10283
  case 'wordpress_run_security_audit':
10127
- return await client.runSecurityAudit(args.advisory_ids || [], args.deep_scan === true);
10284
+ return await client.runSecurityAudit(args.advisory_ids || [], args.deep_scan === true, args.probe_uploads_execution === true, args.known_vulnerabilities !== false);
10128
10285
  case 'wordpress_update_core_security':
10129
10286
  return await client.updateCoreSecurity({
10130
10287
  advisoryId: args.advisory_id,
@@ -10132,6 +10289,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
10132
10289
  backupConfirmed: args.backup_confirmed === true,
10133
10290
  approvalToken: args.approval_token,
10134
10291
  });
10292
+ case 'wordpress_update_theme':
10293
+ return await client.updateTheme(args.stylesheet, args.approval_token);
10294
+ case 'wordpress_revoke_application_password':
10295
+ return await client.revokeApplicationPassword(Number(args.user_id), String(args.uuid), args.approval_token);
10135
10296
  // Accessibility
10136
10297
  case 'wordpress_list_accessibility_scans':
10137
10298
  return await client.listAccessibilityScans({
@@ -10145,7 +10306,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
10145
10306
  return await client.scanPageAccessibility(args.page_id, args.standard, args.url);
10146
10307
  case 'wordpress_apply_accessibility_fixes':
10147
10308
  return await client.applyAccessibilityFixes(args.scan_id, args.rule_ids);
10148
- // Plugin Management (EXPERIMENTAL)
10309
+ // Plugin Management
10149
10310
  case 'wordpress_list_plugins':
10150
10311
  return await client.listPlugins();
10151
10312
  case 'wordpress_install_plugin':
@@ -10274,7 +10435,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
10274
10435
  case 'wordpress_get_option':
10275
10436
  return await client.getOption(args.option);
10276
10437
  case 'wordpress_update_option':
10277
- return await client.updateOption(args.option, args.value);
10438
+ return await client.updateOption(args.option, args.value, {
10439
+ allow_type_change: args.allow_type_change,
10440
+ allow_object_overwrite: args.allow_object_overwrite,
10441
+ });
10278
10442
  case 'wordpress_delete_option':
10279
10443
  return await client.deleteOption(args.option, args.approval_token);
10280
10444
  case 'wordpress_purge_cache':
@@ -10341,6 +10505,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
10341
10505
  return await client.callRestV1('GET', '/activity', args);
10342
10506
  case 'wordpress_get_activity':
10343
10507
  return await client.callRestV1('GET', `/activity/${args.id}`);
10508
+ case 'wordpress_generate_activity_report': {
10509
+ const { site_id: _siteId, ...reportInput } = args || {};
10510
+ return await client.generateActivityReport(reportInput);
10511
+ }
10344
10512
  case 'wordpress_get_site_template':
10345
10513
  return await client.callRestV2('GET', '/fse/template', args);
10346
10514
  case 'wordpress_create_site_template':
@@ -10478,8 +10646,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
10478
10646
  const { id, ...payload } = args;
10479
10647
  return await client.woocommerceUpdateStock(id, payload);
10480
10648
  }
10481
- case 'woocommerce_sales_report':
10482
- return await client.woocommerceSalesReport(args);
10483
10649
  case 'woocommerce_revenue_summary':
10484
10650
  return await client.callRestV1('GET', '/woocommerce/reports/revenue-summary', args);
10485
10651
  case 'woocommerce_sales_timeseries':
@@ -10891,6 +11057,19 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
10891
11057
  }
10892
11058
  // Return isError result instead of throwing — unknown tool is an execution
10893
11059
  // error, not a protocol error. This lets LLMs self-correct gracefully.
11060
+ // A tool removed in a major release answers with its replacement, so a
11061
+ // saved workflow or an older skill learns the new name in one turn
11062
+ // instead of guessing from "Unknown tool".
11063
+ const retired = RETIRED_TOOLS[name];
11064
+ if (retired) {
11065
+ return {
11066
+ __respira_is_error: true,
11067
+ error: `Retired tool: ${name}`,
11068
+ code: 'respira_tool_retired',
11069
+ replacement: retired.replacement,
11070
+ hint: `${name} was removed in ${retired.removedIn}. Call ${retired.replacement} instead. ${retired.note}`,
11071
+ };
11072
+ }
10894
11073
  return {
10895
11074
  __respira_is_error: true,
10896
11075
  error: `Unknown tool: ${name}`,
@@ -10969,7 +11148,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
10969
11148
  editTarget: {
10970
11149
  type: 'string',
10971
11150
  enum: ['live', 'duplicate'],
10972
- description: 'Deprecated camelCase alias of edit_target. Prefer edit_target. Kept for back-compat with pre-v6.19.5 callers.',
11151
+ description: 'Deprecated camelCase alias of edit_target. Prefer edit_target. Kept so older callers keep working.',
10973
11152
  },
10974
11153
  confirm_live_edit: {
10975
11154
  type: 'boolean',