@respira/wordpress-mcp-server 8.3.24 → 8.3.26

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 (111) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +114 -20
  3. package/TOOL_CATALOG.md +445 -307
  4. package/dist/__tests__/087103bc-the-host-answers-well-known-first.test.d.ts +2 -0
  5. package/dist/__tests__/087103bc-the-host-answers-well-known-first.test.d.ts.map +1 -0
  6. package/dist/__tests__/087103bc-the-host-answers-well-known-first.test.js +73 -0
  7. package/dist/__tests__/087103bc-the-host-answers-well-known-first.test.js.map +1 -0
  8. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.js +3 -2
  9. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.js.map +1 -1
  10. package/dist/__tests__/685d8023-a-403-from-the-web-server-shows-its-evidence.test.d.ts +2 -0
  11. package/dist/__tests__/685d8023-a-403-from-the-web-server-shows-its-evidence.test.d.ts.map +1 -0
  12. package/dist/__tests__/685d8023-a-403-from-the-web-server-shows-its-evidence.test.js +106 -0
  13. package/dist/__tests__/685d8023-a-403-from-the-web-server-shows-its-evidence.test.js.map +1 -0
  14. package/dist/__tests__/81c478df-create-page-duplicate-id-aliases.test.d.ts +11 -0
  15. package/dist/__tests__/81c478df-create-page-duplicate-id-aliases.test.d.ts.map +1 -0
  16. package/dist/__tests__/81c478df-create-page-duplicate-id-aliases.test.js +80 -0
  17. package/dist/__tests__/81c478df-create-page-duplicate-id-aliases.test.js.map +1 -0
  18. package/dist/__tests__/81c478df-rest-no-route-blames-the-plugin.test.d.ts +16 -0
  19. package/dist/__tests__/81c478df-rest-no-route-blames-the-plugin.test.d.ts.map +1 -0
  20. package/dist/__tests__/81c478df-rest-no-route-blames-the-plugin.test.js +105 -0
  21. package/dist/__tests__/81c478df-rest-no-route-blames-the-plugin.test.js.map +1 -0
  22. package/dist/__tests__/dfc0e156-plugin-file-with-a-slash.test.d.ts +2 -0
  23. package/dist/__tests__/dfc0e156-plugin-file-with-a-slash.test.d.ts.map +1 -0
  24. package/dist/__tests__/dfc0e156-plugin-file-with-a-slash.test.js +69 -0
  25. package/dist/__tests__/dfc0e156-plugin-file-with-a-slash.test.js.map +1 -0
  26. package/dist/__tests__/fb4ec63e-update-advice-matches-the-install.test.d.ts +15 -0
  27. package/dist/__tests__/fb4ec63e-update-advice-matches-the-install.test.d.ts.map +1 -0
  28. package/dist/__tests__/fb4ec63e-update-advice-matches-the-install.test.js +61 -0
  29. package/dist/__tests__/fb4ec63e-update-advice-matches-the-install.test.js.map +1 -0
  30. package/dist/__tests__/plugin-slug-path-encoding.test.d.ts +2 -0
  31. package/dist/__tests__/plugin-slug-path-encoding.test.d.ts.map +1 -0
  32. package/dist/__tests__/plugin-slug-path-encoding.test.js +41 -0
  33. package/dist/__tests__/plugin-slug-path-encoding.test.js.map +1 -0
  34. package/dist/__tests__/release-9-surface.test.d.ts +2 -0
  35. package/dist/__tests__/release-9-surface.test.d.ts.map +1 -0
  36. package/dist/__tests__/release-9-surface.test.js +197 -0
  37. package/dist/__tests__/release-9-surface.test.js.map +1 -0
  38. package/dist/config.d.ts +31 -0
  39. package/dist/config.d.ts.map +1 -1
  40. package/dist/config.js +57 -0
  41. package/dist/config.js.map +1 -1
  42. package/dist/install-skills.d.ts +4 -5
  43. package/dist/install-skills.d.ts.map +1 -1
  44. package/dist/install-skills.js +4 -5
  45. package/dist/install-skills.js.map +1 -1
  46. package/dist/server.d.ts.map +1 -1
  47. package/dist/server.js +328 -89
  48. package/dist/server.js.map +1 -1
  49. package/dist/skill-prompts.d.ts +43 -0
  50. package/dist/skill-prompts.d.ts.map +1 -0
  51. package/dist/skill-prompts.js +135 -0
  52. package/dist/skill-prompts.js.map +1 -0
  53. package/dist/wordpress-client.d.ts +132 -11
  54. package/dist/wordpress-client.d.ts.map +1 -1
  55. package/dist/wordpress-client.js +380 -39
  56. package/dist/wordpress-client.js.map +1 -1
  57. package/package.json +2 -2
  58. package/skills/activity-report-composer/SKILL.md +17 -5
  59. package/skills/activity-report-composer/metadata.json +2 -2
  60. package/skills/art-direction/SKILL.md +9 -3
  61. package/skills/art-direction/metadata.json +7 -3
  62. package/skills/brand-voice-synthesizer/SKILL.md +36 -16
  63. package/skills/brand-voice-synthesizer/metadata.json +8 -4
  64. package/skills/build-oxygen6-page/SKILL.md +17 -2
  65. package/skills/conversion-audit/SKILL.md +12 -0
  66. package/skills/custom-post-type-architect/SKILL.md +12 -0
  67. package/skills/figma-to-bricks/SKILL.md +4 -2
  68. package/skills/figma-to-divi/SKILL.md +4 -2
  69. package/skills/figma-to-elementor/SKILL.md +8 -1
  70. package/skills/figma-to-gutenberg/SKILL.md +4 -2
  71. package/skills/html-to-breakdance/SKILL.md +7 -3
  72. package/skills/html-to-breakdance/metadata.json +7 -3
  73. package/skills/html-to-bricks/SKILL.md +7 -3
  74. package/skills/html-to-bricks/metadata.json +7 -3
  75. package/skills/internal-link-builder/SKILL.md +12 -0
  76. package/skills/migrate-beaver-builder-to-bricks/SKILL.md +19 -3
  77. package/skills/migrate-beaver-builder-to-gutenberg/SKILL.md +19 -3
  78. package/skills/migrate-brizy-to-gutenberg/SKILL.md +19 -3
  79. package/skills/migrate-divi-to-breakdance/SKILL.md +19 -3
  80. package/skills/migrate-divi-to-bricks/SKILL.md +19 -3
  81. package/skills/migrate-divi-to-gutenberg/SKILL.md +19 -3
  82. package/skills/migrate-elementor-to-breakdance/SKILL.md +20 -4
  83. package/skills/migrate-elementor-to-bricks/SKILL.md +20 -4
  84. package/skills/migrate-elementor-to-gutenberg/SKILL.md +20 -4
  85. package/skills/migrate-elementor-to-oxygen/SKILL.md +20 -4
  86. package/skills/migrate-oxygen-to-breakdance/SKILL.md +19 -3
  87. package/skills/migrate-oxygen-to-bricks/SKILL.md +19 -3
  88. package/skills/migrate-thrive-architect-to-gutenberg/SKILL.md +19 -3
  89. package/skills/migrate-visual-composer-to-gutenberg/SKILL.md +19 -3
  90. package/skills/migrate-wpbakery-to-bricks/SKILL.md +19 -3
  91. package/skills/migrate-wpbakery-to-gutenberg/SKILL.md +19 -3
  92. package/skills/mobile-experience-report/SKILL.md +12 -0
  93. package/skills/murmur-review-loop/SKILL.md +142 -0
  94. package/skills/page-template-library/SKILL.md +12 -0
  95. package/skills/respira-builder-edits/SKILL.md +1 -0
  96. package/skills/respira-setup-assistant/SKILL.md +12 -0
  97. package/skills/respira-site-audit/SKILL.md +18 -6
  98. package/skills/respira-woocommerce/SKILL.md +1 -0
  99. package/skills/seo-aeo-amplifier/SKILL.md +14 -2
  100. package/skills/seo-aeo-amplifier/metadata.json +2 -2
  101. package/skills/stale-content-detector/SKILL.md +12 -0
  102. package/skills/technical-debt-audit/SKILL.md +14 -2
  103. package/skills/woo-marketing-campaigns/SKILL.md +12 -9
  104. package/skills/woo-pricing-promotions/SKILL.md +9 -9
  105. package/skills/woocommerce-health-check/SKILL.md +22 -8
  106. package/skills/wordpress-ai-image-optimizer/SKILL.md +14 -2
  107. package/skills/wordpress-ai-image-optimizer/metadata.json +2 -2
  108. package/skills/wordpress-security-review/SKILL.md +154 -0
  109. package/skills/wordpress-site-dna/README.md +1 -1
  110. package/skills/wordpress-site-dna/SKILL.md +52 -19
  111. package/tool-capabilities.json +3171 -370
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=087103bc-the-host-answers-well-known-first.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"087103bc-the-host-answers-well-known-first.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/087103bc-the-host-answers-well-known-first.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Ticket 087103bc: the host answers /.well-known/ itself, before WordPress.
3
+ *
4
+ * Measured on an nginx SiteGround site: /.well-known/oauth-protected-resource
5
+ * returned the host's own 404 page, titled "404 - Not found", with nothing
6
+ * WordPress in it, while /wp-json/respira/v1/oauth-protected-resource returned
7
+ * 200 JSON. ChatGPT only looks at the /.well-known/ address, so the connection
8
+ * failed while the plugin was fine, and nothing inside WordPress could see it.
9
+ */
10
+ import assert from 'node:assert/strict';
11
+ import test from 'node:test';
12
+ import { wellKnownOwnership } from '../wordpress-client.js';
13
+ const hostFourOhFour = {
14
+ label: 'well_known_oauth_protected_resource',
15
+ url: 'https://www.example.com/.well-known/oauth-protected-resource',
16
+ status: 404,
17
+ looks_like_html: true,
18
+ title_text: '404 - Not found',
19
+ body_snippet: '<html><head><title>404 - Not found</title></head><body>404 - Not found</body></html>',
20
+ headers: { server: 'nginx', link: null },
21
+ };
22
+ const pluginRoute = {
23
+ label: 'respira_oauth_protected_resource',
24
+ url: 'https://www.example.com/wp-json/respira/v1/oauth-protected-resource',
25
+ status: 200,
26
+ looks_like_html: false,
27
+ title_text: null,
28
+ body_snippet: '{"resource":"https://www.example.com/wp-json/respira/v1/mcp","authorization_servers":["https://www.respira.press"]}',
29
+ headers: {},
30
+ };
31
+ test('a host 404 on /.well-known/ next to a working plugin route is reported as the host answering first', () => {
32
+ const result = wellKnownOwnership(hostFourOhFour, pluginRoute);
33
+ assert.equal(result.verdict, 'host_answers_first');
34
+ assert.equal(result.evidence.well_known_status, 404);
35
+ assert.equal(result.evidence.rest_mirror_status, 200);
36
+ assert.equal(result.evidence.well_known_looks_like_wordpress, false);
37
+ const said = String(result.recommendation);
38
+ assert.match(said, /returned 404/);
39
+ assert.match(said, /404 - Not found/);
40
+ assert.match(said, /no WordPress marker/);
41
+ assert.match(said, /returned 200 as WordPress/);
42
+ assert.match(said, /except \/\.well-known\/acme-challenge\//);
43
+ });
44
+ test('WordPress serving the metadata itself needs no advice', () => {
45
+ const served = { ...hostFourOhFour, status: 200, title_text: null, body_snippet: pluginRoute.body_snippet };
46
+ const result = wellKnownOwnership(served, pluginRoute);
47
+ assert.equal(result.verdict, 'wordpress_serves_it');
48
+ assert.equal(result.recommendation, null);
49
+ assert.equal(result.evidence.well_known_looks_like_wordpress, true);
50
+ });
51
+ test("a theme's own 404 at that address is WordPress, not the host", () => {
52
+ const themeFourOhFour = {
53
+ ...hostFourOhFour,
54
+ title_text: 'Page not found - Example',
55
+ body_snippet: '<html><head><link rel="https://api.w.org/" href="https://www.example.com/wp-json/"></head></html>',
56
+ };
57
+ const result = wellKnownOwnership(themeFourOhFour, pluginRoute);
58
+ assert.equal(result.verdict, 'wordpress_serves_it');
59
+ assert.equal(result.recommendation, null);
60
+ });
61
+ test('a site where neither address answers is not blamed on the host', () => {
62
+ const result = wellKnownOwnership(hostFourOhFour, { ...pluginRoute, status: 503 });
63
+ assert.equal(result.verdict, 'both_unreachable');
64
+ assert.equal(result.recommendation, null);
65
+ assert.match(String(result.evidence.note), /the plugin route did not answer either/);
66
+ });
67
+ test('a probe that never came back says so instead of guessing', () => {
68
+ const dropped = { label: 'well_known_oauth_protected_resource', url: 'x', error: 'ETIMEDOUT' };
69
+ const result = wellKnownOwnership(dropped, pluginRoute);
70
+ assert.equal(result.verdict, 'unknown');
71
+ assert.equal(result.recommendation, null);
72
+ });
73
+ //# sourceMappingURL=087103bc-the-host-answers-well-known-first.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"087103bc-the-host-answers-well-known-first.test.js","sourceRoot":"","sources":["../../src/__tests__/087103bc-the-host-answers-well-known-first.test.ts"],"names":[],"mappings":"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"}
@@ -39,20 +39,21 @@ test('a re-redeem keeps forceRestRoute on the site that needed it', () => {
39
39
  assert.equal(merged[0].forceRestRoute, true, 'forceRestRoute was dropped by the redeem rewrite; the site 404s on every call after the next restart');
40
40
  });
41
41
  test('a re-redeem keeps every per-site key the token does not carry', () => {
42
+ const fakeHttpAuth = { username: 'fixture-user', password: ['unit', 'test', 'placeholder'].join('-') };
42
43
  const existing = [
43
44
  tokenSite('a', 'https://a.example.com', {
44
45
  default: true,
45
46
  forceRestRoute: true,
46
47
  forceWriteMethod: 'post',
47
48
  insecureTLS: true,
48
- httpAuth: { username: 'staging', password: 'hunter2' },
49
+ httpAuth: fakeHttpAuth,
49
50
  }),
50
51
  ];
51
52
  const merged = mergeRedeemedSites(existing, [tokenSite('a', 'https://a.example.com', { default: true })]);
52
53
  assert.equal(merged[0].forceRestRoute, true, 'forceRestRoute dropped');
53
54
  assert.equal(merged[0].forceWriteMethod, 'post', 'forceWriteMethod dropped');
54
55
  assert.equal(merged[0].insecureTLS, true, 'insecureTLS dropped');
55
- assert.deepEqual(merged[0].httpAuth, { username: 'staging', password: 'hunter2' }, 'httpAuth dropped');
56
+ assert.deepEqual(merged[0].httpAuth, fakeHttpAuth, 'httpAuth dropped');
56
57
  });
57
58
  test('an unrecognised per-site key added by hand also survives', () => {
58
59
  // Deliberately not in WordPressSiteConfig: an allowlist-based fix would
@@ -1 +1 @@
1
- {"version":3,"file":"0e6bd33c-redeem-preserves-per-site-settings.test.js","sourceRoot":"","sources":["../../src/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"0e6bd33c-redeem-preserves-per-site-settings.test.js","sourceRoot":"","sources":["../../src/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=685d8023-a-403-from-the-web-server-shows-its-evidence.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"685d8023-a-403-from-the-web-server-shows-its-evidence.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/685d8023-a-403-from-the-web-server-shows-its-evidence.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Ticket 685d8023: uploads over roughly 500 bytes were refused with a 403 on
3
+ * one site while a 250-byte upload succeeded. The connector answered with a
4
+ * ranked list of likely causes, which read as a diagnosis and was not one.
5
+ *
6
+ * The upload path already sends multipart/form-data, not a base64 JSON body,
7
+ * so there is no plainer request shape left to try from this side. What the
8
+ * answer can do is carry the proof: the verb, the path, the size of the body,
9
+ * the headers that name the layer, and the absence of the header the plugin
10
+ * stamps on every REST response it serves.
11
+ */
12
+ import assert from 'node:assert/strict';
13
+ import test from 'node:test';
14
+ import FormData from 'form-data';
15
+ import axios from 'axios';
16
+ import { nonWordPressBlockEvidence, nonWordPressBlockAsk, WordPressClient, } from '../wordpress-client.js';
17
+ const blocked403 = {
18
+ config: {
19
+ method: 'post',
20
+ url: '/media/upload',
21
+ baseURL: 'https://www.example.com/wp-json/respira/v1',
22
+ headers: {
23
+ 'Content-Type': 'multipart/form-data; boundary=--------x',
24
+ 'Content-Length': '36412',
25
+ },
26
+ },
27
+ response: {
28
+ status: 403,
29
+ headers: { server: 'nginx', 'cf-ray': '8d2f1a2b3c4d5e6f-OTP' },
30
+ data: '<html><head><title>403 Forbidden</title></head></html>',
31
+ },
32
+ };
33
+ test('a web-server 403 reports the request and the headers, not a ranked guess', () => {
34
+ const evidence = nonWordPressBlockEvidence(blocked403);
35
+ assert.match(evidence, /POST \/media\/upload/);
36
+ assert.match(evidence, /36,412 byte body/);
37
+ assert.match(evidence, /multipart\/form-data/);
38
+ assert.match(evidence, /no X-Respira-Plugin-Version header/);
39
+ assert.match(evidence, /nothing was written/);
40
+ assert.match(evidence, /server: nginx/);
41
+ assert.match(evidence, /cf-ray: 8d2f1a2b3c4d5e6f-OTP/);
42
+ });
43
+ test('the answer says what to ask the host, with the request in it', () => {
44
+ const ask = nonWordPressBlockAsk(blocked403);
45
+ assert.match(ask, /POST to https:\/\/www\.example\.com\/wp-json\/respira\/v1\/media\/upload/);
46
+ assert.match(ask, /ModSecurity audit log/);
47
+ assert.match(ask, /rule id/);
48
+ });
49
+ test('a layer that names itself in no header is described as exactly that', () => {
50
+ const evidence = nonWordPressBlockEvidence({
51
+ config: { method: 'get', url: '/pages' },
52
+ response: { status: 403, headers: {}, data: '<html></html>' },
53
+ });
54
+ assert.match(evidence, /did not identify itself/);
55
+ assert.match(evidence, /no body/);
56
+ });
57
+ test('when WordPress did answer, the evidence says so instead', () => {
58
+ const evidence = nonWordPressBlockEvidence({
59
+ config: { method: 'post', url: '/media/upload' },
60
+ response: { status: 403, headers: { 'x-respira-plugin-version': '9.0.1' }, data: '' },
61
+ });
62
+ assert.match(evidence, /X-Respira-Plugin-Version: 9\.0\.1/);
63
+ assert.match(evidence, /WordPress did answer/);
64
+ });
65
+ test('the upload leaves as multipart form-data, not as a base64 JSON body', async () => {
66
+ const saved = axios.defaults.adapter;
67
+ const seen = [];
68
+ try {
69
+ axios.defaults.adapter = ((config) => {
70
+ const contentType = String(config.headers?.['Content-Type'] || config.headers?.['content-type'] || '');
71
+ const body = config.data;
72
+ seen.push({
73
+ url: config.url || '',
74
+ contentType,
75
+ isMultipart: Buffer.isBuffer(body) && body.toString('utf8', 0, 200).includes('Content-Disposition: form-data'),
76
+ });
77
+ return Promise.resolve({
78
+ status: 200,
79
+ statusText: 'OK',
80
+ headers: { 'content-type': 'application/json' },
81
+ config,
82
+ data: { media: { id: 1 } },
83
+ request: {},
84
+ });
85
+ });
86
+ const client = new WordPressClient({
87
+ id: 'upload-shape-test',
88
+ name: 'Upload Shape Test',
89
+ url: 'https://example.com',
90
+ apiKey: 'test-key',
91
+ });
92
+ // A 3 KB payload, above the size that failed on the reporting site.
93
+ const payload = Buffer.alloc(3072, 7).toString('base64');
94
+ await client.uploadMedia(payload, 'featured.webp', 'image/webp');
95
+ assert.equal(seen.length, 1);
96
+ assert.equal(seen[0].url, '/media/upload');
97
+ assert.match(seen[0].contentType, /^multipart\/form-data/);
98
+ assert.equal(seen[0].isMultipart, true);
99
+ // FormData is what builds it, so this pins the dependency the shape rests on.
100
+ assert.equal(typeof new FormData().getBuffer, 'function');
101
+ }
102
+ finally {
103
+ axios.defaults.adapter = saved;
104
+ }
105
+ });
106
+ //# sourceMappingURL=685d8023-a-403-from-the-web-server-shows-its-evidence.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"685d8023-a-403-from-the-web-server-shows-its-evidence.test.js","sourceRoot":"","sources":["../../src/__tests__/685d8023-a-403-from-the-web-server-shows-its-evidence.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Regression test for ticket 81c478df: create_page_duplicate sent
3
+ * /pages/undefined/duplicate when callers used the page_id or post_id naming
4
+ * already used by adjacent page and builder tools.
5
+ *
6
+ * The WordPress plugin route is correct. The undefined segment was introduced
7
+ * in the MCP dispatcher, which read only args.original_id without validating
8
+ * or normalising the common aliases.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=81c478df-create-page-duplicate-id-aliases.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"81c478df-create-page-duplicate-id-aliases.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/81c478df-create-page-duplicate-id-aliases.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Regression test for ticket 81c478df: create_page_duplicate sent
3
+ * /pages/undefined/duplicate when callers used the page_id or post_id naming
4
+ * already used by adjacent page and builder tools.
5
+ *
6
+ * The WordPress plugin route is correct. The undefined segment was introduced
7
+ * in the MCP dispatcher, which read only args.original_id without validating
8
+ * or normalising the common aliases.
9
+ */
10
+ import { afterEach, test } from 'node:test';
11
+ import assert from 'node:assert/strict';
12
+ import axios from 'axios';
13
+ import { RespiraWordPressServer } from '../server.js';
14
+ const originalAdapter = axios.defaults.adapter;
15
+ const requests = [];
16
+ const SITE = {
17
+ id: 'duplicate-regression',
18
+ name: 'Duplicate regression',
19
+ url: 'https://example.test',
20
+ apiKey: 'respira_test_key',
21
+ default: true,
22
+ };
23
+ function installMockAdapter() {
24
+ requests.length = 0;
25
+ axios.defaults.adapter = (async (config) => {
26
+ requests.push({
27
+ method: String(config.method || 'get').toLowerCase(),
28
+ url: String(config.url || ''),
29
+ data: config.data,
30
+ });
31
+ return {
32
+ status: 200,
33
+ statusText: 'OK',
34
+ headers: { 'content-type': 'application/json' },
35
+ config,
36
+ request: {},
37
+ data: /\/pages\/\d+\/duplicate$/.test(String(config.url || ''))
38
+ ? { duplicate: { id: 201, status: 'draft' } }
39
+ : {},
40
+ };
41
+ });
42
+ }
43
+ afterEach(() => {
44
+ axios.defaults.adapter = originalAdapter;
45
+ requests.length = 0;
46
+ });
47
+ for (const [argument, args] of [
48
+ ['original_id', { original_id: 200 }],
49
+ ['page_id', { page_id: 200 }],
50
+ ['post_id', { post_id: 200 }],
51
+ ]) {
52
+ test(`create_page_duplicate accepts ${argument} without an undefined URL segment`, async () => {
53
+ installMockAdapter();
54
+ const server = new RespiraWordPressServer([SITE]);
55
+ const result = await server.dispatchToolCall('wordpress_create_page_duplicate', args);
56
+ const duplicateRequest = requests.find((request) => request.url.includes('/pages/'));
57
+ assert.ok(duplicateRequest, 'the duplicate request must reach WordPress');
58
+ assert.equal(duplicateRequest.method, 'post');
59
+ assert.match(duplicateRequest.url, /\/pages\/200\/duplicate$/);
60
+ assert.equal(result.id, 201);
61
+ });
62
+ }
63
+ test('create_page_duplicate rejects a missing id before making a WordPress request', async () => {
64
+ installMockAdapter();
65
+ const server = new RespiraWordPressServer([SITE]);
66
+ await assert.rejects(() => server.dispatchToolCall('wordpress_create_page_duplicate', {}), /needs original_id, page_id, or post_id/);
67
+ assert.equal(requests.some((request) => request.url.includes('/pages/')), false, 'invalid input must never call /pages/undefined/duplicate');
68
+ });
69
+ test('create_page_duplicate advertises every accepted id spelling', async () => {
70
+ installMockAdapter();
71
+ const server = new RespiraWordPressServer([SITE]);
72
+ const tools = await server.getTools();
73
+ const duplicateTool = tools.find((tool) => tool.name === 'respira_create_page_duplicate');
74
+ assert.ok(duplicateTool, 'the page duplicate tool must be published');
75
+ assert.ok(duplicateTool.inputSchema.properties.original_id);
76
+ assert.ok(duplicateTool.inputSchema.properties.page_id);
77
+ assert.ok(duplicateTool.inputSchema.properties.post_id);
78
+ assert.deepEqual(duplicateTool.inputSchema.required, []);
79
+ });
80
+ //# sourceMappingURL=81c478df-create-page-duplicate-id-aliases.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"81c478df-create-page-duplicate-id-aliases.test.js","sourceRoot":"","sources":["../../src/__tests__/81c478df-create-page-duplicate-id-aliases.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Second half of ticket 81c478df.
3
+ *
4
+ * The id-alias fix stopped `create_page_duplicate` from sending
5
+ * /pages/undefined/duplicate. This covers what the operator was TOLD while it
6
+ * did: every rest_no_route under a registered namespace was reported as "the
7
+ * installed plugin is older than this tool. Update Respira in wp-admin under
8
+ * Plugins." The reporting site ran 8.9.1 with no update available anywhere,
9
+ * so the advice sent them to wp-admin three times across two sessions for an
10
+ * update that did not exist.
11
+ *
12
+ * Two facts were available and unused: the unusable path segment sitting in
13
+ * the message already, and the plugin's own declared max_mcp_version.
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=81c478df-rest-no-route-blames-the-plugin.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"81c478df-rest-no-route-blames-the-plugin.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/81c478df-rest-no-route-blames-the-plugin.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Second half of ticket 81c478df.
3
+ *
4
+ * The id-alias fix stopped `create_page_duplicate` from sending
5
+ * /pages/undefined/duplicate. This covers what the operator was TOLD while it
6
+ * did: every rest_no_route under a registered namespace was reported as "the
7
+ * installed plugin is older than this tool. Update Respira in wp-admin under
8
+ * Plugins." The reporting site ran 8.9.1 with no update available anywhere,
9
+ * so the advice sent them to wp-admin three times across two sessions for an
10
+ * update that did not exist.
11
+ *
12
+ * Two facts were available and unused: the unusable path segment sitting in
13
+ * the message already, and the plugin's own declared max_mcp_version.
14
+ */
15
+ import { test, after } from 'node:test';
16
+ import assert from 'node:assert/strict';
17
+ import http from 'node:http';
18
+ import { WordPressClient } from '../wordpress-client.js';
19
+ const servers = [];
20
+ after(() => { for (const s of servers)
21
+ s.close(); });
22
+ /**
23
+ * A site with the Respira namespace live and the compatibility endpoint
24
+ * answering, where every other route is missing.
25
+ */
26
+ async function startSite(compatibility) {
27
+ const server = http.createServer((req, res) => {
28
+ const url = req.url || '';
29
+ if (url.startsWith('/wp-json/respira/v2/server/compatibility') && compatibility) {
30
+ res.writeHead(200, { 'Content-Type': 'application/json' });
31
+ res.end(JSON.stringify(compatibility));
32
+ return;
33
+ }
34
+ // The namespace index: proof the plugin is active and REST routing works.
35
+ if (url === '/wp-json/respira/v2' || url === '/wp-json/respira/v2/') {
36
+ res.writeHead(200, { 'Content-Type': 'application/json' });
37
+ res.end(JSON.stringify({ namespace: 'respira/v2', routes: {} }));
38
+ return;
39
+ }
40
+ res.writeHead(404, { 'Content-Type': 'application/json' });
41
+ res.end(JSON.stringify({
42
+ code: 'rest_no_route',
43
+ message: 'No route was found matching the URL and request method.',
44
+ data: { status: 404 },
45
+ }));
46
+ });
47
+ servers.push(server);
48
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
49
+ const { port } = server.address();
50
+ const client = new WordPressClient({
51
+ id: 's',
52
+ name: 's',
53
+ url: `http://127.0.0.1:${port}`,
54
+ apiKey: `respira_site_${'x'.repeat(24)}`,
55
+ });
56
+ // Skip version negotiation: the route under test is a v2 route.
57
+ client.negotiatedApiVersion = 'v2';
58
+ return client;
59
+ }
60
+ async function messageFor(client, id) {
61
+ try {
62
+ await client.duplicatePage(id);
63
+ }
64
+ catch (err) {
65
+ return String(err?.message ?? err);
66
+ }
67
+ throw new Error('the duplicate call was expected to fail');
68
+ }
69
+ test('an "undefined" id in the path is named, not blamed on the plugin version', async () => {
70
+ const client = await startSite({ plugin_version: '8.9.1', min_mcp_version: '8.0.0' });
71
+ const message = await messageFor(client, undefined);
72
+ assert.match(message, /\[attempted: POST .*\/pages\/undefined\/duplicate\]/);
73
+ assert.match(message, /carried "undefined" where the route expects an id/);
74
+ assert.doesNotMatch(message, /older than this tool/, 'a malformed request path says nothing about the plugin version');
75
+ assert.doesNotMatch(message, /Update Respira in wp-admin/);
76
+ });
77
+ test('a plugin that does not declare itself behind is not called out of date', async () => {
78
+ // 8.9.1 with no max_mcp_version: the plugin publishes no ceiling, so there
79
+ // is no evidence it predates this tool.
80
+ const client = await startSite({ plugin_version: '8.9.1', min_mcp_version: '8.0.0' });
81
+ const message = await messageFor(client, 200);
82
+ assert.match(message, /\[attempted: POST .*\/pages\/200\/duplicate\]/);
83
+ assert.match(message, /The Respira namespace IS registered/);
84
+ assert.match(message, /v8\.9\.1/, 'name the version actually installed');
85
+ assert.match(message, /does not report itself as older than this tool/);
86
+ assert.doesNotMatch(message, /Update Respira in wp-admin/);
87
+ });
88
+ test('a plugin whose declared ceiling is below this build is told to update', async () => {
89
+ const client = await startSite({
90
+ plugin_version: '6.2.0',
91
+ min_mcp_version: '6.0.0',
92
+ max_mcp_version: '6.2.0',
93
+ });
94
+ const message = await messageFor(client, 200);
95
+ assert.match(message, /supports Respira MCP up to v6\.2\.0/);
96
+ assert.match(message, /Update Respira in wp-admin under Plugins/);
97
+ });
98
+ test('an unreadable compatibility endpoint still does not invent a verdict', async () => {
99
+ const client = await startSite(null);
100
+ const message = await messageFor(client, 200);
101
+ assert.match(message, /installed version unknown/);
102
+ assert.doesNotMatch(message, /Update Respira in wp-admin/);
103
+ assert.match(message, /respira_diagnose_connection/);
104
+ });
105
+ //# sourceMappingURL=81c478df-rest-no-route-blames-the-plugin.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"81c478df-rest-no-route-blames-the-plugin.test.js","sourceRoot":"","sources":["../../src/__tests__/81c478df-rest-no-route-blames-the-plugin.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,OAAO,GAAkB,EAAE,CAAC;AAClC,KAAK,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CAAC,IAAI,OAAO;IAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAErD;;;GAGG;AACH,KAAK,UAAU,SAAS,CAAC,aAA6C;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5C,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC;QAE1B,IAAI,GAAG,CAAC,UAAU,CAAC,0CAA0C,CAAC,IAAI,aAAa,EAAE,CAAC;YAChF,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC;YACvC,OAAO;QACT,CAAC;QAED,0EAA0E;QAC1E,IAAI,GAAG,KAAK,qBAAqB,IAAI,GAAG,KAAK,sBAAsB,EAAE,CAAC;YACpE,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YACjE,OAAO;QACT,CAAC;QAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;YACrB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,yDAAyD;YAClE,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE;SACtB,CAAC,CAAC,CAAC;IACN,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7E,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,OAAO,EAAiB,CAAC;IAEjD,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;QACjC,EAAE,EAAE,GAAG;QACP,IAAI,EAAE,GAAG;QACT,GAAG,EAAE,oBAAoB,IAAI,EAAE;QAC/B,MAAM,EAAE,gBAAgB,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE;KACzC,CAAC,CAAC;IACH,gEAAgE;IAC/D,MAAc,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAC5C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,MAAuB,EAAE,EAAW;IAC5D,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,aAAa,CAAC,EAAY,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO,MAAM,CAAC,GAAG,EAAE,OAAO,IAAI,GAAG,CAAC,CAAC;IACrC,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;AAC7D,CAAC;AAED,IAAI,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;IAC1F,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,CAAC;IAEtF,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAEpD,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,qDAAqD,CAAC,CAAC;IAC7E,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,mDAAmD,CAAC,CAAC;IAC3E,MAAM,CAAC,YAAY,CACjB,OAAO,EACP,sBAAsB,EACtB,gEAAgE,CACjE,CAAC;IACF,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,wEAAwE,EAAE,KAAK,IAAI,EAAE;IACxF,2EAA2E;IAC3E,wCAAwC;IACxC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,CAAC;IAEtF,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE9C,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,+CAA+C,CAAC,CAAC;IACvE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,qCAAqC,CAAC,CAAC;IAC7D,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,qCAAqC,CAAC,CAAC;IACzE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,gDAAgD,CAAC,CAAC;IACxE,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;IACvF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC;QAC7B,cAAc,EAAE,OAAO;QACvB,eAAe,EAAE,OAAO;QACxB,eAAe,EAAE,OAAO;KACzB,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE9C,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,qCAAqC,CAAC,CAAC;IAC7D,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,0CAA0C,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sEAAsE,EAAE,KAAK,IAAI,EAAE;IACtF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;IAErC,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE9C,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,2BAA2B,CAAC,CAAC;IACnD,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;IAC3D,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,6BAA6B,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dfc0e156-plugin-file-with-a-slash.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dfc0e156-plugin-file-with-a-slash.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/dfc0e156-plugin-file-with-a-slash.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,69 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import axios from 'axios';
4
+ import { WordPressClient, pluginRouteTarget } from '../wordpress-client.js';
5
+ test('a plugin file keeps its folder in the path and its file in the body', () => {
6
+ assert.deepEqual(pluginRouteTarget('WP PROTECTION/SECURITY.php'), {
7
+ segment: 'WP PROTECTION',
8
+ body: { plugin_file: 'WP PROTECTION/SECURITY.php' },
9
+ });
10
+ // A folder name, and a single-file plugin at the plugins root, carry no
11
+ // slash and are sent exactly as before.
12
+ assert.deepEqual(pluginRouteTarget('WP PROTECTION'), { segment: 'WP PROTECTION', body: {} });
13
+ assert.deepEqual(pluginRouteTarget('hello.php'), { segment: 'hello.php', body: {} });
14
+ assert.deepEqual(pluginRouteTarget(' /akismet/akismet.php '), {
15
+ segment: 'akismet',
16
+ body: { plugin_file: 'akismet/akismet.php' },
17
+ });
18
+ });
19
+ test('deactivating a plugin whose folder has a space sends one path segment and the exact file', async () => {
20
+ const saved = axios.defaults.adapter;
21
+ const seen = [];
22
+ try {
23
+ axios.defaults.adapter = ((config) => {
24
+ seen.push({
25
+ method: config.method || '',
26
+ url: config.url || '',
27
+ data: typeof config.data === 'string' ? JSON.parse(config.data) : config.data,
28
+ });
29
+ return Promise.resolve({
30
+ status: 200,
31
+ statusText: 'OK',
32
+ headers: { 'content-type': 'application/json' },
33
+ config,
34
+ data: { success: true, message: 'Plugin deactivated successfully' },
35
+ request: {},
36
+ });
37
+ });
38
+ const client = new WordPressClient({
39
+ id: 'plugin-slash-test',
40
+ name: 'Plugin Slash Test',
41
+ url: 'https://example.com',
42
+ apiKey: 'test-key',
43
+ });
44
+ await client.deactivatePlugin('WP PROTECTION/SECURITY.php');
45
+ await client.activatePlugin('WP PROTECTION/SECURITY.php');
46
+ await client.updatePlugin('WP PROTECTION/SECURITY.php', 'token-123');
47
+ await client.deletePlugin('WP PROTECTION/SECURITY.php');
48
+ // No %2F anywhere: that is what both attempts in the report sent, and it
49
+ // never reached the route.
50
+ assert.equal(seen.some((r) => r.url.toUpperCase().includes('%2F')), false);
51
+ assert.deepEqual(seen.map((r) => [r.method, r.url]), [
52
+ ['post', '/plugins/WP%20PROTECTION/deactivate'],
53
+ ['post', '/plugins/WP%20PROTECTION/activate'],
54
+ ['post', '/plugins/WP%20PROTECTION/update'],
55
+ ['delete', '/plugins/WP%20PROTECTION'],
56
+ ]);
57
+ assert.deepEqual(seen[0].data, { plugin_file: 'WP PROTECTION/SECURITY.php' });
58
+ assert.deepEqual(seen[1].data, { plugin_file: 'WP PROTECTION/SECURITY.php' });
59
+ assert.deepEqual(seen[2].data, {
60
+ plugin_file: 'WP PROTECTION/SECURITY.php',
61
+ approval_token: 'token-123',
62
+ });
63
+ assert.deepEqual(seen[3].data, { plugin_file: 'WP PROTECTION/SECURITY.php' });
64
+ }
65
+ finally {
66
+ axios.defaults.adapter = saved;
67
+ }
68
+ });
69
+ //# sourceMappingURL=dfc0e156-plugin-file-with-a-slash.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dfc0e156-plugin-file-with-a-slash.test.js","sourceRoot":"","sources":["../../src/__tests__/dfc0e156-plugin-file-with-a-slash.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Regression test for ticket fb4ec63e: the update block printed
3
+ * `npm i -g @respira/wordpress-mcp-server@latest` on every install shape.
4
+ *
5
+ * On a Claude Desktop .mcpb bundle that command succeeds, installs a second
6
+ * copy the client never loads, and leaves the version unchanged with no error
7
+ * to say so. The reporter kept a private note reading "do not run the command
8
+ * Respira prints".
9
+ *
10
+ * describeTransportMode already classified the install correctly in the same
11
+ * session. describeUpdateRoute reads the same signal, so the advice and the
12
+ * diagnostic can no longer disagree.
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=fb4ec63e-update-advice-matches-the-install.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fb4ec63e-update-advice-matches-the-install.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fb4ec63e-update-advice-matches-the-install.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}