@respira/wordpress-mcp-server 8.3.18 → 8.3.20

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 (43) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/TOOL_CATALOG.md +36 -36
  3. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.d.ts +2 -0
  4. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.d.ts.map +1 -0
  5. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.js +91 -0
  6. package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.js.map +1 -0
  7. package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.d.ts +30 -0
  8. package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.d.ts.map +1 -0
  9. package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.js +162 -0
  10. package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.js.map +1 -0
  11. package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.d.ts +2 -0
  12. package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.d.ts.map +1 -0
  13. package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.js +207 -0
  14. package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.js.map +1 -0
  15. package/dist/__tests__/e105ed25-report-issue-builder-context.test.d.ts +2 -0
  16. package/dist/__tests__/e105ed25-report-issue-builder-context.test.d.ts.map +1 -0
  17. package/dist/__tests__/e105ed25-report-issue-builder-context.test.js +115 -0
  18. package/dist/__tests__/e105ed25-report-issue-builder-context.test.js.map +1 -0
  19. package/dist/__tests__/respira-outage-blamed-on-the-site.test.d.ts +2 -0
  20. package/dist/__tests__/respira-outage-blamed-on-the-site.test.d.ts.map +1 -0
  21. package/dist/__tests__/respira-outage-blamed-on-the-site.test.js +83 -0
  22. package/dist/__tests__/respira-outage-blamed-on-the-site.test.js.map +1 -0
  23. package/dist/__tests__/upload-media-path-detection.test.js +35 -1
  24. package/dist/__tests__/upload-media-path-detection.test.js.map +1 -1
  25. package/dist/__tests__/v2-negotiation-ttl-retry.test.d.ts +2 -0
  26. package/dist/__tests__/v2-negotiation-ttl-retry.test.d.ts.map +1 -0
  27. package/dist/__tests__/v2-negotiation-ttl-retry.test.js +108 -0
  28. package/dist/__tests__/v2-negotiation-ttl-retry.test.js.map +1 -0
  29. package/dist/config.d.ts.map +1 -1
  30. package/dist/config.js +59 -4
  31. package/dist/config.js.map +1 -1
  32. package/dist/server.d.ts +7 -0
  33. package/dist/server.d.ts.map +1 -1
  34. package/dist/server.js +54 -4
  35. package/dist/server.js.map +1 -1
  36. package/dist/wordpress-client.d.ts +108 -1
  37. package/dist/wordpress-client.d.ts.map +1 -1
  38. package/dist/wordpress-client.js +467 -77
  39. package/dist/wordpress-client.js.map +1 -1
  40. package/package.json +2 -2
  41. package/skills/woo-marketing-campaigns/SKILL.md +128 -0
  42. package/skills/woo-pricing-promotions/SKILL.md +119 -0
  43. package/tool-capabilities.json +92 -344
package/CHANGELOG.md CHANGED
@@ -5,6 +5,95 @@ All notable changes to Respira WordPress MCP Server will be documented in this f
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [8.3.20] - 2026-09-09
9
+
10
+ ### Fixed
11
+
12
+ - **One failed handshake made a site look broken for the rest of the session.** A
13
+ successful version negotiation is cached for the life of the client, which is right.
14
+ A failure was cached the same way, which is not: a single transient error from the
15
+ host meant every later call replayed the same verdict without sending a request, so
16
+ nothing appeared in the site's own logs either and the site looked dead for as long as
17
+ the session lasted. Two customers sat blocked for a quarter of an hour on sites that
18
+ were already healthy. Failures now expire after a minute, and a message that came from
19
+ cache says when the failure was first seen instead of implying a fresh check.
20
+ - **A Respira outage was described as a fault on the customer's site.** When the
21
+ account service could not answer, the resulting error was rendered as though the site
22
+ had produced it, ending with advice to go and read wp-content/debug.log. The failure
23
+ is now named for what it is, a problem reaching Respira rather than anything to fix on
24
+ the site, and the debug.log advice is gone from that path.
25
+ - **Editing a menu item failed on hosts that block PUT.** Those hosts already had a
26
+ fallback that reissues the request as a POST, but it was only wired into some paths,
27
+ and twenty five methods called PUT directly without it, menus among them. The fallback
28
+ could not have rescued them anyway: it decided whether to retry by reading a status
29
+ that the shared error handler discards, so it only ever fired for one specific case
30
+ and never for a real 405. Both halves are fixed, so every PUT write now falls back
31
+ where the host requires it. Deletes still need a change on the WordPress side and are
32
+ unchanged for now.
33
+ - **Redeeming an install token discarded local per-site settings.** The redeemed entry
34
+ replaced the stored one outright, and the dashboard only sends five fields, so
35
+ anything set by hand was dropped: the flag that forces the query-string REST route,
36
+ the forced write method, HTTP auth credentials, the TLS exception, and local tool
37
+ preferences. The site kept working until the next restart, which put the loss a long
38
+ way from its cause. A redeem now updates the fields it carries and leaves the rest
39
+ alone.
40
+ - **A password-protected site was reported as one with Respira uninstalled.** The check
41
+ for whether the Respira routes exist was the one request in the client that did not
42
+ send configured credentials, so on a site behind HTTP auth it was refused, and the
43
+ refusal was read as the routes being absent. The connector then said the plugin was
44
+ deactivated, deleted, or failing to load. It also called an authentication challenge a
45
+ firewall or maintenance mode, and told operators to add credentials they had already
46
+ added. The probe now authenticates, an unanswerable check says so instead of claiming
47
+ absence, and the connection diagnostic reports an authentication challenge as what it
48
+ is rather than as a blocked method.
49
+ - **Filed issue reports lost the builder they came from.** The report tool read the
50
+ builder name from a field the site does not return, so every report was filed with no
51
+ builder recorded even when the site could name it unambiguously.
52
+ - **Two commerce skills were never shipped.** The bundling step rebuilds the skills
53
+ directory from source on every build, and these two existed only in the built copy, so
54
+ each build deleted them. They were listed in the public catalogue throughout. Both now
55
+ ship, and the build fails loudly rather than quietly deleting a skill that is missing
56
+ from source.
57
+
58
+ ## [8.3.19] - 2026-09-04
59
+
60
+ ### Fixed
61
+
62
+ - **Uploading a JPEG never worked.** `upload_media` decided whether the `file`
63
+ argument was a filesystem path by testing whether it started with `/`. Base64 of a
64
+ JPEG's opening bytes is `/9j/`, so every bare base64 JPEG matched that test, was
65
+ treated as an absolute path, and failed with "File not found: /9j/...". Nothing
66
+ ever reached WordPress. The existing test missed it because its sample payload
67
+ happened to start with a letter.
68
+ - **A failed upload hung for minutes instead of retrying.** The multipart body was a
69
+ single-use stream, and both retry paths (the 5xx idempotency retry and the
70
+ `?rest_route=` fallback) re-sent the already-consumed stream with the original
71
+ `Content-Length`. The request promised bytes it then never wrote, so it never
72
+ reached the server and sat until the socket timed out. Measured at 88 seconds for
73
+ one call, with only the first attempt ever delivered. The body is now replayable
74
+ and no streaming body is re-sent.
75
+ - **The `?rest_route=` upload fallback had never worked.** It inherited the same
76
+ exhausted stream, so the fallback reported that the host "also returned HTML"
77
+ without having sent anything.
78
+ - **One bad upload could take the whole server down.** Error responses skipped the
79
+ size cap that success responses go through, so a failed upload echoed the entire
80
+ base64 payload back inside the error message. A 900 KB image produced a 1.2 MB
81
+ JSON-RPC frame against a 100 KiB cap, and an oversized frame makes MCP hosts kill
82
+ the subprocess, which is why every later tool call timed out until a restart. Error
83
+ messages are now truncated.
84
+ - The tool description advertised a 120 second upload timeout; the actual default has
85
+ been 90 seconds since 6.18.4. Corrected.
86
+
87
+ ### Changed
88
+
89
+ - `mcp-bundle/build.sh` now packs the local `mcp-server` when its version matches the
90
+ bundle's pin, instead of always resolving from the npm registry. The registry path
91
+ remains as the fallback. The bundle could previously only be built after publishing,
92
+ while the release workflow's parity gate runs before publishing and requires the
93
+ built bundle to already carry the version being released, so a release was
94
+ unsatisfiable on the first attempt at any version. It also means the bundle is now
95
+ reproducible from the tag rather than dependent on registry state.
96
+
8
97
  ## [8.3.18] - 2026-09-03
9
98
 
10
99
  ### Fixed
package/TOOL_CATALOG.md CHANGED
@@ -9,24 +9,24 @@ The runtime applies site context and per-site governance, so an individual conne
9
9
  ## WordPress and Respira
10
10
 
11
11
  - `respira_duplicate_with_translations` — write; hosted/stdio
12
- - `wordpress_abilities_gap_report` — read; hosted/stdio
12
+ - `wordpress_abilities_gap_report` — read; native + hosted
13
13
  - `wordpress_activate_design_direction` — write; native + hosted
14
14
  - `wordpress_activate_plugin` — write; native + hosted
15
15
  - `wordpress_analyze_aeo` — read; native + hosted
16
16
  - `wordpress_analyze_images` — read; native + hosted
17
- - `wordpress_analyze_pagespeed` — read; hosted/stdio
17
+ - `wordpress_analyze_pagespeed` — read; native + hosted
18
18
  - `wordpress_analyze_performance` — read; native + hosted
19
19
  - `wordpress_analyze_rankmath` — read; native + hosted
20
20
  - `wordpress_analyze_readability` — read; native + hosted
21
21
  - `wordpress_analyze_seo` — read; native + hosted
22
- - `wordpress_append_theme_file` — read; hosted/stdio
23
- - `wordpress_apply_accessibility_fixes` — write; hosted/stdio
22
+ - `wordpress_append_theme_file` — read; native + hosted
23
+ - `wordpress_apply_accessibility_fixes` — write; native + hosted
24
24
  - `wordpress_apply_builder_patch` — write; native + hosted
25
25
  - `wordpress_apply_design_direction` — write; native + hosted
26
- - `wordpress_apply_divi_library_item` — write; hosted/stdio
26
+ - `wordpress_apply_divi_library_item` — write; native + hosted
27
27
  - `wordpress_assign_menu_location` — write; native + hosted
28
28
  - `wordpress_batch_update` — write; native + hosted
29
- - `wordpress_begin_session` — read; hosted/stdio
29
+ - `wordpress_begin_session` — read; native + hosted
30
30
  - `wordpress_build_mega_menu` — write; native + hosted
31
31
  - `wordpress_build_page` — write; native + hosted
32
32
  - `wordpress_bulk_pages_operation` — write; native + hosted
@@ -34,7 +34,7 @@ The runtime applies site context and per-site governance, so an individual conne
34
34
  - `wordpress_check_seo_issues` — read; native + hosted
35
35
  - `wordpress_check_structured_data` — read; native + hosted
36
36
  - `wordpress_convert_html_to_builder` — write; native + hosted
37
- - `wordpress_create_acf_field_group` — write; hosted/stdio
37
+ - `wordpress_create_acf_field_group` — write; native + hosted
38
38
  - `wordpress_create_comment` — write; native + hosted
39
39
  - `wordpress_create_custom_post` — write; native + hosted
40
40
  - `wordpress_create_design_token` — write; native + hosted
@@ -43,23 +43,23 @@ The runtime applies site context and per-site governance, so an individual conne
43
43
  - `wordpress_create_page_duplicate` — write; native + hosted
44
44
  - `wordpress_create_playbook` — write; hosted/stdio
45
45
  - `wordpress_create_post_duplicate` — write; native + hosted
46
- - `wordpress_create_post_type` — write; hosted/stdio
46
+ - `wordpress_create_post_type` — write; native + hosted
47
47
  - `wordpress_create_site_navigation` — write; native + hosted
48
48
  - `wordpress_create_site_pattern` — write; native + hosted
49
49
  - `wordpress_create_site_template` — write; native + hosted
50
- - `wordpress_create_taxonomy` — write; hosted/stdio
50
+ - `wordpress_create_taxonomy` — write; native + hosted
51
51
  - `wordpress_create_term` — write; native + hosted
52
52
  - `wordpress_create_theme_builder_template` — write; native + hosted
53
53
  - `wordpress_create_translation` — write; native + hosted
54
54
  - `wordpress_create_user` — write; native + hosted
55
55
  - `wordpress_deactivate_design_direction` — write; native + hosted
56
56
  - `wordpress_deactivate_plugin` — write; native + hosted
57
- - `wordpress_delete_acf_field_group` — write; hosted/stdio
57
+ - `wordpress_delete_acf_field_group` — write; native + hosted
58
58
  - `wordpress_delete_comment` — write; native + hosted
59
59
  - `wordpress_delete_custom_post` — write; native + hosted
60
60
  - `wordpress_delete_design_direction` — write; native + hosted
61
61
  - `wordpress_delete_design_token` — write; native + hosted
62
- - `wordpress_delete_divi_preset` — write; hosted/stdio
62
+ - `wordpress_delete_divi_preset` — write; native + hosted
63
63
  - `wordpress_delete_media` — write; native + hosted
64
64
  - `wordpress_delete_menu` — write; native + hosted
65
65
  - `wordpress_delete_menu_item` — write; native + hosted
@@ -68,37 +68,37 @@ The runtime applies site context and per-site governance, so an individual conne
68
68
  - `wordpress_delete_playbook` — write; hosted/stdio
69
69
  - `wordpress_delete_plugin` — write; native + hosted
70
70
  - `wordpress_delete_post` — write; native + hosted
71
- - `wordpress_delete_post_type` — write; hosted/stdio
71
+ - `wordpress_delete_post_type` — write; native + hosted
72
72
  - `wordpress_delete_site_navigation` — write; native + hosted
73
73
  - `wordpress_delete_site_pattern` — write; native + hosted
74
- - `wordpress_delete_taxonomy` — write; hosted/stdio
74
+ - `wordpress_delete_taxonomy` — write; native + hosted
75
75
  - `wordpress_delete_term` — write; native + hosted
76
76
  - `wordpress_delete_user` — write; native + hosted
77
77
  - `wordpress_diagnose_connection` — read; hosted/stdio
78
78
  - `wordpress_diff_snapshots` — read; native + hosted
79
79
  - `wordpress_duplicate_element` — write; native + hosted
80
- - `wordpress_end_session` — read; hosted/stdio
80
+ - `wordpress_end_session` — read; native + hosted
81
81
  - `wordpress_export_design_direction` — read; native + hosted
82
82
  - `wordpress_extract_builder_content` — read; native + hosted
83
83
  - `wordpress_find_builder_targets` — read; native + hosted
84
84
  - `wordpress_find_element` — read; native + hosted
85
85
  - `wordpress_forget` — read; native + hosted
86
- - `wordpress_get_accessibility_scan` — read; hosted/stdio
86
+ - `wordpress_get_accessibility_scan` — read; native + hosted
87
87
  - `wordpress_get_active_site` — read; hosted/stdio
88
88
  - `wordpress_get_activity` — read; native + hosted
89
89
  - `wordpress_get_builder_info` — read; native + hosted
90
- - `wordpress_get_builder_inline_schemas` — read; hosted/stdio
90
+ - `wordpress_get_builder_inline_schemas` — read; native + hosted
91
91
  - `wordpress_get_comment` — read; native + hosted
92
92
  - `wordpress_get_core_web_vitals` — read; native + hosted
93
93
  - `wordpress_get_custom_post` — read; native + hosted
94
94
  - `wordpress_get_design_apply_reports` — read; native + hosted
95
95
  - `wordpress_get_design_direction` — read; native + hosted
96
- - `wordpress_get_divi_migration_readiness` — read; hosted/stdio
96
+ - `wordpress_get_divi_migration_readiness` — read; native + hosted
97
97
  - `wordpress_get_media` — read; native + hosted
98
98
  - `wordpress_get_menu` — read; native + hosted
99
99
  - `wordpress_get_menu_item` — read; native + hosted
100
100
  - `wordpress_get_option` — read; native + hosted
101
- - `wordpress_get_page_outline` — read; hosted/stdio
101
+ - `wordpress_get_page_outline` — read; native + hosted
102
102
  - `wordpress_get_playbook` — read; hosted/stdio
103
103
  - `wordpress_get_post_type` — read; native + hosted
104
104
  - `wordpress_get_server_compatibility` — read; native + hosted
@@ -116,16 +116,16 @@ The runtime applies site context and per-site governance, so an individual conne
116
116
  - `wordpress_inject_builder_content` — write; native + hosted
117
117
  - `wordpress_install_plugin` — write; native + hosted
118
118
  - `wordpress_invoke_ability` — write; hosted/stdio
119
- - `wordpress_list_accessibility_scans` — read; hosted/stdio
120
- - `wordpress_list_acf_field_groups` — read; hosted/stdio
119
+ - `wordpress_list_accessibility_scans` — read; native + hosted
120
+ - `wordpress_list_acf_field_groups` — read; native + hosted
121
121
  - `wordpress_list_activity` — read; native + hosted
122
122
  - `wordpress_list_comments` — read; native + hosted
123
- - `wordpress_list_custom_post_types` — read; hosted/stdio
123
+ - `wordpress_list_custom_post_types` — read; native + hosted
124
124
  - `wordpress_list_custom_posts` — read; native + hosted
125
- - `wordpress_list_custom_taxonomies` — read; hosted/stdio
125
+ - `wordpress_list_custom_taxonomies` — read; native + hosted
126
126
  - `wordpress_list_design_directions` — read; native + hosted
127
127
  - `wordpress_list_design_tokens` — read; native + hosted
128
- - `wordpress_list_divi_presets` — read; hosted/stdio
128
+ - `wordpress_list_divi_presets` — read; native + hosted
129
129
  - `wordpress_list_media` — read; native + hosted
130
130
  - `wordpress_list_memory` — read; native + hosted
131
131
  - `wordpress_list_menu_items` — read; native + hosted
@@ -146,40 +146,40 @@ The runtime applies site context and per-site governance, so an individual conne
146
146
  - `wordpress_list_terms` — read; native + hosted
147
147
  - `wordpress_list_theme_builder_templates` — read; native + hosted
148
148
  - `wordpress_list_users` — read; native + hosted
149
- - `wordpress_make_responsive` — read; hosted/stdio
149
+ - `wordpress_make_responsive` — read; native + hosted
150
150
  - `wordpress_mint_design_preview` — read; native + hosted
151
151
  - `wordpress_move_element` — write; native + hosted
152
152
  - `wordpress_purge_cache` — write; native + hosted
153
153
  - `wordpress_read_page` — read; native + hosted
154
154
  - `wordpress_read_post` — read; native + hosted
155
- - `wordpress_read_theme_file` — read; hosted/stdio
155
+ - `wordpress_read_theme_file` — read; native + hosted
156
156
  - `wordpress_redeem_token` — write; hosted/stdio
157
157
  - `wordpress_remember` — read; native + hosted
158
158
  - `wordpress_remove_element` — write; native + hosted
159
159
  - `wordpress_reorder_elements` — write; native + hosted
160
160
  - `wordpress_report_issue` — read; hosted/stdio
161
161
  - `wordpress_reset_site_template` — write; native + hosted
162
- - `wordpress_resolve_divi_preset` — read; hosted/stdio
163
- - `wordpress_restore_session` — write; hosted/stdio
162
+ - `wordpress_resolve_divi_preset` — read; native + hosted
163
+ - `wordpress_restore_session` — write; native + hosted
164
164
  - `wordpress_restore_snapshot` — write; native + hosted
165
- - `wordpress_run_pagespeed_audit` — read; hosted/stdio
165
+ - `wordpress_run_pagespeed_audit` — read; native + hosted
166
166
  - `wordpress_run_security_audit` — read; native + hosted
167
167
  - `wordpress_save_design_direction` — write; native + hosted
168
- - `wordpress_save_divi_library_item` — write; hosted/stdio
169
- - `wordpress_scan_page_accessibility` — read; hosted/stdio
168
+ - `wordpress_save_divi_library_item` — write; native + hosted
169
+ - `wordpress_scan_page_accessibility` — read; native + hosted
170
170
  - `wordpress_search_abilities` — read; native + hosted
171
171
  - `wordpress_search_docs` — read; hosted/stdio
172
172
  - `wordpress_search_stock_images` — read; native + hosted
173
173
  - `wordpress_sideload_image` — write; native + hosted
174
174
  - `wordpress_switch_site` — write; native + hosted
175
- - `wordpress_update_acf_field_group` — write; hosted/stdio
175
+ - `wordpress_update_acf_field_group` — write; native + hosted
176
176
  - `wordpress_update_comment` — write; native + hosted
177
177
  - `wordpress_update_core_security` — write; native + hosted
178
178
  - `wordpress_update_custom_post` — write; native + hosted
179
179
  - `wordpress_update_design_token` — write; native + hosted
180
180
  - `wordpress_update_element` — write; native + hosted
181
181
  - `wordpress_update_media` — write; native + hosted
182
- - `wordpress_update_media_batch` — write; hosted/stdio
182
+ - `wordpress_update_media_batch` — write; native + hosted
183
183
  - `wordpress_update_menu` — write; native + hosted
184
184
  - `wordpress_update_menu_item` — write; native + hosted
185
185
  - `wordpress_update_module` — write; native + hosted
@@ -188,19 +188,19 @@ The runtime applies site context and per-site governance, so an individual conne
188
188
  - `wordpress_update_playbook` — write; hosted/stdio
189
189
  - `wordpress_update_plugin` — write; native + hosted
190
190
  - `wordpress_update_post` — write; native + hosted
191
- - `wordpress_update_post_type` — write; hosted/stdio
191
+ - `wordpress_update_post_type` — write; native + hosted
192
192
  - `wordpress_update_site_navigation` — write; native + hosted
193
193
  - `wordpress_update_site_pattern` — write; native + hosted
194
194
  - `wordpress_update_site_template` — write; native + hosted
195
- - `wordpress_update_taxonomy` — write; hosted/stdio
195
+ - `wordpress_update_taxonomy` — write; native + hosted
196
196
  - `wordpress_update_term` — write; native + hosted
197
197
  - `wordpress_update_theme_builder_template` — write; native + hosted
198
198
  - `wordpress_update_translation` — write; native + hosted
199
199
  - `wordpress_update_user` — write; native + hosted
200
200
  - `wordpress_upload_media` — write; native + hosted
201
- - `wordpress_upsert_divi_presets` — read; hosted/stdio
201
+ - `wordpress_upsert_divi_presets` — read; native + hosted
202
202
  - `wordpress_validate_security` — read; native + hosted
203
- - `wordpress_write_theme_file` — read; hosted/stdio
203
+ - `wordpress_write_theme_file` — read; native + hosted
204
204
 
205
205
  ## WooCommerce add-on
206
206
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=0e6bd33c-redeem-preserves-per-site-settings.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0e6bd33c-redeem-preserves-per-site-settings.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Ticket 0e6bd33c — redeeming a token must not silently discard the per-site
3
+ * settings the token does not own.
4
+ *
5
+ * A redeem token carries exactly five fields per site: id, name, url, apiKey
6
+ * and default (buildRespiraConfig, product-website/src/lib/mcp-config.ts).
7
+ * Everything else on a site object in ~/.respira/config.json was put there by
8
+ * the operator, usually on this connector's own advice:
9
+ *
10
+ * forceRestRoute — the only thing that makes hosts work where pretty
11
+ * /wp-json/ 404s and ?rest_route= returns JSON
12
+ * forceWriteMethod — the only thing that makes hosts work where PUT is
13
+ * blocked or dropped
14
+ * httpAuth — server-level Basic Auth for staging sites
15
+ * insecureTLS — the documented TLS escape hatch
16
+ *
17
+ * mergeRedeemedSites used to do `byKey.set(key, site)` on a URL collision:
18
+ * the freshly minted entry replaced the whole object. Redeem reported
19
+ * success, the file was rewritten, and every one of those flags was gone.
20
+ * The session kept working (the live clients were already built) so nothing
21
+ * failed until the next restart, at which point the site failed exactly the
22
+ * way it had failed before the flag was added.
23
+ *
24
+ * These assertions fail on unmodified main.
25
+ */
26
+ import { test } from 'node:test';
27
+ import assert from 'node:assert/strict';
28
+ import { mergeRedeemedSites } from '../config.js';
29
+ /** What the dashboard mints: five fields, nothing else. */
30
+ function tokenSite(id, url, extra = {}) {
31
+ return { id, name: id, url, apiKey: `respira_site_${id}`, ...extra };
32
+ }
33
+ test('a re-redeem keeps forceRestRoute on the site that needed it', () => {
34
+ const existing = [
35
+ tokenSite('a', 'https://a.example.com', { default: true, forceRestRoute: true }),
36
+ ];
37
+ const merged = mergeRedeemedSites(existing, [tokenSite('a', 'https://a.example.com', { default: true })]);
38
+ assert.equal(merged.length, 1);
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
+ });
41
+ test('a re-redeem keeps every per-site key the token does not carry', () => {
42
+ const existing = [
43
+ tokenSite('a', 'https://a.example.com', {
44
+ default: true,
45
+ forceRestRoute: true,
46
+ forceWriteMethod: 'post',
47
+ insecureTLS: true,
48
+ httpAuth: { username: 'staging', password: 'hunter2' },
49
+ }),
50
+ ];
51
+ const merged = mergeRedeemedSites(existing, [tokenSite('a', 'https://a.example.com', { default: true })]);
52
+ assert.equal(merged[0].forceRestRoute, true, 'forceRestRoute dropped');
53
+ assert.equal(merged[0].forceWriteMethod, 'post', 'forceWriteMethod dropped');
54
+ assert.equal(merged[0].insecureTLS, true, 'insecureTLS dropped');
55
+ assert.deepEqual(merged[0].httpAuth, { username: 'staging', password: 'hunter2' }, 'httpAuth dropped');
56
+ });
57
+ test('an unrecognised per-site key added by hand also survives', () => {
58
+ // Deliberately not in WordPressSiteConfig: an allowlist-based fix would
59
+ // drop this, and would drop every future key added to the schema too.
60
+ const existing = [
61
+ { ...tokenSite('a', 'https://a.example.com', { default: true }), someFutureFlag: 'keep-me' },
62
+ ];
63
+ const merged = mergeRedeemedSites(existing, [tokenSite('a', 'https://a.example.com', { default: true })]);
64
+ assert.equal(merged[0].someFutureFlag, 'keep-me');
65
+ });
66
+ test('the token still wins for the fields it does carry', () => {
67
+ const existing = [
68
+ tokenSite('a', 'https://a.example.com', { default: true, forceRestRoute: true }),
69
+ ];
70
+ const merged = mergeRedeemedSites(existing, [
71
+ { id: 'a', name: 'Renamed', url: 'https://a.example.com', apiKey: 'respira_site_rotated', default: true },
72
+ ]);
73
+ assert.equal(merged[0].apiKey, 'respira_site_rotated', 'a rotated key must still land');
74
+ assert.equal(merged[0].name, 'Renamed', 'a renamed site must still land');
75
+ assert.equal(merged[0].forceRestRoute, true, 'and the local flag must still survive it');
76
+ });
77
+ test('preserving local keys does not leave two sites claiming default', () => {
78
+ const existing = [
79
+ tokenSite('a', 'https://a.example.com', { default: true, forceRestRoute: true }),
80
+ tokenSite('b', 'https://b.example.com'),
81
+ ];
82
+ // A full-account token re-mints both and marks b default.
83
+ const merged = mergeRedeemedSites(existing, [
84
+ tokenSite('b', 'https://b.example.com', { default: true }),
85
+ tokenSite('a', 'https://a.example.com'),
86
+ ]);
87
+ assert.equal(merged.filter((s) => s.default === true).length, 1, 'exactly one default must survive the merge');
88
+ assert.equal(merged.find((s) => s.default === true)?.url, 'https://b.example.com', "the just-redeemed token's default wins");
89
+ assert.equal(merged.find((s) => s.url === 'https://a.example.com')?.forceRestRoute, true);
90
+ });
91
+ //# sourceMappingURL=0e6bd33c-redeem-preserves-per-site-settings.test.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Ticket 139f7353 — the PUT-blocked-host fallback never reached the ~25
3
+ * dedicated client methods that call `client.put()` directly.
4
+ *
5
+ * Two earlier tickets fixed this in two places: e2d884cb wired
6
+ * `putWithPostFallback` into updatePost/updatePage/updateCustomPost, and
7
+ * 2b3a706b wired it into the shared `callRestV1`/`callRestV2` callers. Both
8
+ * left the dedicated methods alone. On a host whose edge blocks PUT (reported
9
+ * on Kinsta + Cloudflare, where `respira_diagnose_connection` shows
10
+ * `write_method_probe.put_status` 404/405) that produced a very specific
11
+ * shape of broken: `respira_create_menu_item` worked, because creating is a
12
+ * POST, and `respira_update_menu_item` returned 405, so a menu could be built
13
+ * and then never reordered.
14
+ *
15
+ * `respira_update_menu_item` is the one that got reported. It is not special:
16
+ * every menu, media, user, comment, term, WooCommerce and ACF update method
17
+ * had the identical gap.
18
+ *
19
+ * The fallback is safe here for the same reason it is safe for posts and
20
+ * pages: the plugin registers all of these routes with
21
+ * `WP_REST_Server::EDITABLE`, which is `'POST, PUT, PATCH'`, so the fallback
22
+ * POST lands on the identical handler. Verified against the route table in
23
+ * wordpress-plugin/respira-for-wordpress/includes/class-respira-api.php.
24
+ *
25
+ * A real HTTP server, not a mocked adapter, so the whole interceptor chain
26
+ * runs — including the codedError() step that strips `.response` and made an
27
+ * earlier status-only version of this predicate a no-op.
28
+ */
29
+ export {};
30
+ //# sourceMappingURL=139f7353-put-fallback-dedicated-methods.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"139f7353-put-fallback-dedicated-methods.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/139f7353-put-fallback-dedicated-methods.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Ticket 139f7353 — the PUT-blocked-host fallback never reached the ~25
3
+ * dedicated client methods that call `client.put()` directly.
4
+ *
5
+ * Two earlier tickets fixed this in two places: e2d884cb wired
6
+ * `putWithPostFallback` into updatePost/updatePage/updateCustomPost, and
7
+ * 2b3a706b wired it into the shared `callRestV1`/`callRestV2` callers. Both
8
+ * left the dedicated methods alone. On a host whose edge blocks PUT (reported
9
+ * on Kinsta + Cloudflare, where `respira_diagnose_connection` shows
10
+ * `write_method_probe.put_status` 404/405) that produced a very specific
11
+ * shape of broken: `respira_create_menu_item` worked, because creating is a
12
+ * POST, and `respira_update_menu_item` returned 405, so a menu could be built
13
+ * and then never reordered.
14
+ *
15
+ * `respira_update_menu_item` is the one that got reported. It is not special:
16
+ * every menu, media, user, comment, term, WooCommerce and ACF update method
17
+ * had the identical gap.
18
+ *
19
+ * The fallback is safe here for the same reason it is safe for posts and
20
+ * pages: the plugin registers all of these routes with
21
+ * `WP_REST_Server::EDITABLE`, which is `'POST, PUT, PATCH'`, so the fallback
22
+ * POST lands on the identical handler. Verified against the route table in
23
+ * wordpress-plugin/respira-for-wordpress/includes/class-respira-api.php.
24
+ *
25
+ * A real HTTP server, not a mocked adapter, so the whole interceptor chain
26
+ * runs — including the codedError() step that strips `.response` and made an
27
+ * earlier status-only version of this predicate a no-op.
28
+ */
29
+ import { test, after } from 'node:test';
30
+ import assert from 'node:assert/strict';
31
+ import http from 'node:http';
32
+ import { WordPressClient, ORIGINAL_METHOD_HEADER, isRetryableAsPost } from '../wordpress-client.js';
33
+ /**
34
+ * The second half of the same ticket. PUT_FALLBACK_STATUSES has said
35
+ * {403, 404, 405} since 2b3a706b, but `handleError` rebuilds every non-2xx
36
+ * through `codedError`, which drops `.response`. So the status check in
37
+ * `isRetryableAsPost` could never fire on a real client error, and the
38
+ * name-based check that patched around that was hardcoded to 403 alone.
39
+ * A host that answers PUT with 405 and an HTML page — which is what the
40
+ * reporter's edge does — was therefore never retried, even at the call sites
41
+ * where the fallback WAS already wired in.
42
+ */
43
+ test('an HTML 405 from the edge is retryable as POST, not just an HTML 403', () => {
44
+ assert.equal(isRetryableAsPost({ name: 'http_403_non_wordpress' }), true);
45
+ assert.equal(isRetryableAsPost({ name: 'http_405_non_wordpress' }), true, '405 is the reported case');
46
+ assert.equal(isRetryableAsPost({ name: 'http_404_non_wordpress' }), true, '404 is the other reported case');
47
+ // A status raised BY WordPress keeps its WP_Error code and stays a decision.
48
+ assert.equal(isRetryableAsPost({ name: 'rest_forbidden' }), false);
49
+ assert.equal(isRetryableAsPost({ name: 'http_500_non_wordpress' }), false);
50
+ assert.equal(isRetryableAsPost({ name: 'http_401_non_wordpress' }), false);
51
+ });
52
+ const servers = [];
53
+ after(() => { for (const s of servers)
54
+ s.close(); });
55
+ /**
56
+ * A host that answers 405 Method Not Allowed to PUT before WordPress runs
57
+ * (Cloudflare / Kinsta edge rule on the verb) and 200 to POST on the same
58
+ * path. The 405 body is HTML, like a real edge rejection, so the client
59
+ * cannot mistake it for a WordPress decision.
60
+ */
61
+ async function startPutBlockedSite() {
62
+ const hits = [];
63
+ const server = http.createServer((req, res) => {
64
+ hits.push({
65
+ method: req.method || '',
66
+ url: req.url || '',
67
+ idempotencyKey: req.headers['idempotency-key'] || null,
68
+ originalMethod: req.headers[ORIGINAL_METHOD_HEADER.toLowerCase()] || null,
69
+ });
70
+ if (req.method === 'PUT') {
71
+ res.writeHead(405, { 'Content-Type': 'text/html', Allow: 'GET, POST, HEAD' });
72
+ res.end('<!DOCTYPE html><html><head><title>405 Not Allowed</title></head><body>405</body></html>');
73
+ return;
74
+ }
75
+ if (req.method === 'POST') {
76
+ let body = '';
77
+ req.on('data', (c) => { body += c; });
78
+ req.on('end', () => {
79
+ res.writeHead(200, { 'Content-Type': 'application/json' });
80
+ res.end(JSON.stringify({ success: true, via: 'POST', echo: body ? JSON.parse(body) : null }));
81
+ });
82
+ return;
83
+ }
84
+ res.writeHead(200, { 'Content-Type': 'application/json' });
85
+ res.end(JSON.stringify({ ok: true }));
86
+ });
87
+ servers.push(server);
88
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
89
+ const { port } = server.address();
90
+ const client = new WordPressClient({
91
+ id: 'blocked',
92
+ name: 'blocked',
93
+ url: `http://127.0.0.1:${port}`,
94
+ apiKey: `respira_site_${'x'.repeat(24)}`,
95
+ });
96
+ return { client, hits };
97
+ }
98
+ test('update_menu_item survives a host that answers 405 to PUT', async () => {
99
+ const { client, hits } = await startPutBlockedSite();
100
+ const result = await client.updateMenuItem(42, { position: 2 });
101
+ assert.equal(result.success, true, 'the reorder must actually land, not surface a 405');
102
+ assert.deepEqual(hits.map((h) => h.method), ['PUT', 'POST'], 'exactly one PUT then exactly one POST');
103
+ assert.equal(hits[1].url, '/wp-json/respira/v1/menus/items/42');
104
+ assert.equal(hits[1].originalMethod, 'PUT', 'the fallback POST must declare the verb it replaces');
105
+ assert.equal(hits[0].idempotencyKey, hits[1].idempotencyKey, 'both attempts must share one Idempotency-Key or the plugin cannot dedupe them');
106
+ assert.ok(hits[0].idempotencyKey, 'an idempotency key must be present at all');
107
+ });
108
+ /**
109
+ * update_menu_item is the reported one. These are the sibling methods that had
110
+ * the identical gap — all of them EDITABLE routes, so all of them fixable the
111
+ * same way. Named individually so a future regression names the tool it broke.
112
+ */
113
+ const SIBLINGS = [
114
+ ['update_menu', (c) => c.updateMenu(7, { name: 'Main' }), '/wp-json/respira/v1/menus/7'],
115
+ ['assign_menu_location', (c) => c.assignMenuToLocation('primary', 7), '/wp-json/respira/v1/menus/locations/primary'],
116
+ ['update_media', (c) => c.updateMedia(3, { alt: 'a' }), '/wp-json/respira/v1/media/3'],
117
+ ['update_media_batch', (c) => c.updateMediaBatch([{ id: 3, alt: 'a' }]), '/wp-json/respira/v1/media/batch'],
118
+ ['update_user', (c) => c.updateUser(5, { display_name: 'A' }), '/wp-json/respira/v1/users/5'],
119
+ ['update_comment', (c) => c.updateComment(9, { content: 'x' }), '/wp-json/respira/v1/comments/9'],
120
+ ['update_term', (c) => c.updateTerm('category', 4, { name: 'n' }), '/wp-json/respira/v1/taxonomies/category/terms/4'],
121
+ ];
122
+ for (const [label, call, expectedPath] of SIBLINGS) {
123
+ test(`${label} survives a host that answers 405 to PUT`, async () => {
124
+ const { client, hits } = await startPutBlockedSite();
125
+ const result = await call(client);
126
+ assert.equal(result.success, true, `${label} must fall back to POST`);
127
+ assert.deepEqual(hits.map((h) => h.method), ['PUT', 'POST']);
128
+ assert.equal(hits[1].url, expectedPath);
129
+ assert.equal(hits[1].originalMethod, 'PUT');
130
+ assert.equal(hits[0].idempotencyKey, hits[1].idempotencyKey);
131
+ });
132
+ }
133
+ test('the ACF dispatcher falls back too', async () => {
134
+ const { client, hits } = await startPutBlockedSite();
135
+ const result = await client.dispatchAcfTool('respira_acf_update_field', {
136
+ post_id: 12,
137
+ field_key: 'field_1',
138
+ value: 'x',
139
+ });
140
+ assert.equal(result.success, true);
141
+ assert.deepEqual(hits.map((h) => h.method), ['PUT', 'POST']);
142
+ assert.equal(hits[1].originalMethod, 'PUT');
143
+ });
144
+ test('a WordPress-issued 405 on a POST is not retried into a loop', async () => {
145
+ // Guard against the fix turning one failed call into an unbounded ladder:
146
+ // the fallback is exactly one POST, and a POST that fails is final.
147
+ const hits = [];
148
+ const server = http.createServer((req, res) => {
149
+ hits.push(req.method || '');
150
+ res.writeHead(405, { 'Content-Type': 'text/html' });
151
+ res.end('<!DOCTYPE html><html><head><title>405</title></head><body>405</body></html>');
152
+ });
153
+ servers.push(server);
154
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
155
+ const { port } = server.address();
156
+ const client = new WordPressClient({
157
+ id: 'x', name: 'x', url: `http://127.0.0.1:${port}`, apiKey: `respira_site_${'x'.repeat(24)}`,
158
+ });
159
+ await assert.rejects(() => client.updateMenuItem(1, { position: 1 }));
160
+ assert.deepEqual(hits, ['PUT', 'POST'], 'one PUT, one POST, then stop');
161
+ });
162
+ //# sourceMappingURL=139f7353-put-fallback-dedicated-methods.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"139f7353-put-fallback-dedicated-methods.test.js","sourceRoot":"","sources":["../../src/__tests__/139f7353-put-fallback-dedicated-methods.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=44fcf6b5-basic-auth-is-not-a-blocked-verb.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"44fcf6b5-basic-auth-is-not-a-blocked-verb.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.ts"],"names":[],"mappings":""}