@respira/wordpress-mcp-server 7.1.4 → 7.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SOUL.md CHANGED
@@ -29,7 +29,7 @@ Non-negotiable principles. If a request asks you to violate one, refuse and expl
29
29
 
30
30
  **Privacy is a hard line.** Telemetry collects metadata only: site UUID, plugin version, builder names, agent type, write counts, locale. Never prompts. Never tool arguments. Never tool results. Never WordPress content. Never customer URLs without explicit opt-in. The customer can set `RESPIRA_USAGE_OPT_OUT=1` and all collection stops at the source.
31
31
 
32
- **Builder-native edits.** When editing content inside a page builder, always use the element-level tools: `respira_find_element` then `respira_update_element` or `respira_update_module`. Never reach for `respira_update_page` to change content. Never write to `wp_postmeta` directly. Never write SQL. The whole point of Respira is that edits land in the native builder format, parseable by the builder, reversible by the snapshot system.
32
+ **Builder-native edits.** When editing content inside a page builder, always use the element-level tools: `respira_find_element` then `respira_update_element` or `respira_update_module`. Never reach for `respira_update_page` to change content. Never write to `wp_postmeta` directly. Never write SQL. The whole point of Respira is that edits land in the native builder format, parseable by the builder, reversible per page by the snapshot system.
33
33
 
34
34
  **Dogfood first.** Every release is tested on the Respira Studio environment before the tag is cut. If a tool produces unexpected output, the founder ran it on real sites before you saw it. Treat reported edge cases seriously. They are reproducible.
35
35
 
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Provides comprehensive Divi documentation for AI assistants.
5
5
  */
6
- export declare const diviContext = "\nYou are editing a Divi Builder page. Divi has two storage formats you must handle safely:\n- Divi 5: block-based layout using `divi/*` blocks in post content\n- Divi 4: legacy shortcode layout using `et_pb_*`\n\n## Divi 5 Structure (Primary)\n\n- **Sections (`divi/section`)**: Top-level containers\n- **Rows (`divi/row`)**: Layout containers inside sections\n- **Columns/Groups (`divi/column`, `divi/group`)**: Nested containers\n- **Modules (`divi/*`)**: Text, button, image, CTA, and other content blocks\n\n## Divi 4 Structure (Legacy)\n\n- **Sections (`et_pb_section`)**\n- **Rows (`et_pb_row`)**\n- **Modules (`et_pb_text`, `et_pb_image`, `et_pb_button`, etc.)**\n\n## Editing Contract\n\n- Use structured nodes: `{ type, attributes, children, content }`\n- Preserve unknown attributes exactly\n- Do not rewrite entire trees for small edits\n- Keep non-Divi blocks and legacy shortcode wrapper blocks unless explicitly asked to remove\n- Treat legacy wrappers as opaque: move/delete allowed, deep edits discouraged\n\n## Important Rules\n\n1. Prefer Divi 5 block edits (`divi/*`) when present\n2. Keep Divi 4 shortcode edits only for legacy pages\n3. Preserve parent/child nesting and ordering\n4. Preserve all existing attributes unless explicitly changed\n5. Use hex colors and full URLs where relevant\n6. Avoid lossy conversion between Divi 4 and Divi 5 formats\n7. For every Divi inject operation, ask for explicit confirmation: Divi 4 or Divi 5\n8. For code modules, preserve extracted content encoding; do not blindly base64-encode content\n";
6
+ export declare const diviContext = "\nYou are editing a Divi Builder page. Divi has two storage formats you must handle safely:\n- Divi 5: block-based layout using `divi/*` blocks in post content\n- Divi 4: legacy shortcode layout using `et_pb_*`\n\n## Divi 5 Structure (Primary)\n\n- **Sections (`divi/section`)**: Top-level containers\n- **Rows (`divi/row`)**: Layout containers inside sections\n- **Columns/Groups (`divi/column`, `divi/group`)**: Nested containers\n- **Modules (`divi/*`)**: Text, button, image, CTA, and other content blocks\n\n## Divi 4 Structure (Legacy)\n\n- **Sections (`et_pb_section`)**\n- **Rows (`et_pb_row`)**\n- **Modules (`et_pb_text`, `et_pb_image`, `et_pb_button`, etc.)**\n\n## Editing Contract\n\n- Use structured nodes: `{ type, attributes, children, content }`\n- Preserve unknown attributes exactly\n- Do not rewrite entire trees for small edits\n- Keep non-Divi blocks and legacy shortcode wrapper blocks unless explicitly asked to remove\n- Treat legacy wrappers as opaque: move/delete allowed, deep edits discouraged\n\n## Important Rules\n\n1. Prefer Divi 5 block edits (`divi/*`) when present\n2. Keep Divi 4 shortcode edits only for legacy pages\n3. Preserve parent/child nesting and ordering\n4. Preserve all existing attributes unless explicitly changed\n5. Use hex colors and full URLs where relevant\n6. Avoid lossy conversion between Divi 4 and Divi 5 formats\n7. Auto-detect Divi 4 vs Divi 5 from the page. respira_get_builder_info and respira_read_page reveal the mode, and the plugin already detects Divi 5 and flips into block format on its own. Only ask the human when it is genuinely ambiguous, like a brand new page with no builder set yet\n8. For code modules, preserve extracted content encoding; do not blindly base64-encode content\n";
7
7
  export declare const diviAttributeHelp: {
8
8
  colors: string;
9
9
  spacing: string;
@@ -11,5 +11,5 @@ export declare const diviAttributeHelp: {
11
11
  fonts: string;
12
12
  urls: string;
13
13
  };
14
- export declare const diviValidationRules = "\nCRITICAL Divi Rules:\n1. If Divi 5 blocks exist, keep output in block format (`divi/*`)\n2. Preserve `attributes` and `children` unless explicitly changed\n3. Keep legacy shortcode-wrapper blocks opaque\n4. Never overwrite a Divi 5 page with Divi 4 shortcodes\n5. Preserve module structure and ordering\n6. Always use proper attribute formats\n7. Ask user to confirm Divi 4 vs Divi 5 before every Divi inject\n";
14
+ export declare const diviValidationRules = "\nCRITICAL Divi Rules:\n1. If Divi 5 blocks exist, keep output in block format (`divi/*`)\n2. Preserve `attributes` and `children` unless explicitly changed\n3. Keep legacy shortcode-wrapper blocks opaque\n4. Never overwrite a Divi 5 page with Divi 4 shortcodes\n5. Preserve module structure and ordering\n6. Always use proper attribute formats\n7. Auto-detect Divi 4 vs Divi 5 from the page (respira_get_builder_info / respira_read_page); only ask the human on a genuinely ambiguous page with no builder set\n";
15
15
  //# sourceMappingURL=divi-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"divi-context.d.ts","sourceRoot":"","sources":["../src/divi-context.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,WAAW,yhDAoCvB,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;CAM7B,CAAC;AAEF,eAAO,MAAM,mBAAmB,iaAS/B,CAAC"}
1
+ {"version":3,"file":"divi-context.d.ts","sourceRoot":"","sources":["../src/divi-context.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,WAAW,muDAoCvB,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;CAM7B,CAAC;AAEF,eAAO,MAAM,mBAAmB,mgBAS/B,CAAC"}
@@ -37,7 +37,7 @@ You are editing a Divi Builder page. Divi has two storage formats you must handl
37
37
  4. Preserve all existing attributes unless explicitly changed
38
38
  5. Use hex colors and full URLs where relevant
39
39
  6. Avoid lossy conversion between Divi 4 and Divi 5 formats
40
- 7. For every Divi inject operation, ask for explicit confirmation: Divi 4 or Divi 5
40
+ 7. Auto-detect Divi 4 vs Divi 5 from the page. respira_get_builder_info and respira_read_page reveal the mode, and the plugin already detects Divi 5 and flips into block format on its own. Only ask the human when it is genuinely ambiguous, like a brand new page with no builder set yet
41
41
  8. For code modules, preserve extracted content encoding; do not blindly base64-encode content
42
42
  `;
43
43
  export const diviAttributeHelp = {
@@ -55,6 +55,6 @@ CRITICAL Divi Rules:
55
55
  4. Never overwrite a Divi 5 page with Divi 4 shortcodes
56
56
  5. Preserve module structure and ordering
57
57
  6. Always use proper attribute formats
58
- 7. Ask user to confirm Divi 4 vs Divi 5 before every Divi inject
58
+ 7. Auto-detect Divi 4 vs Divi 5 from the page (respira_get_builder_info / respira_read_page); only ask the human on a genuinely ambiguous page with no builder set
59
59
  `;
60
60
  //# sourceMappingURL=divi-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"divi-prompts.d.ts","sourceRoot":"","sources":["../src/divi-prompts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,gBAAgB,wLAI5B,CAAC;AAEF,eAAO,MAAM,8BAA8B,oHACmE,CAAC;AAE/G,eAAO,MAAM,2BAA2B;;;;GA0BvC,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAmFlD,CAAC;AAEF,eAAO,MAAM,oBAAoB,UAShC,CAAC;AAEF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAElE;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,MAAM,CAYR"}
1
+ {"version":3,"file":"divi-prompts.d.ts","sourceRoot":"","sources":["../src/divi-prompts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,gBAAgB,wLAI5B,CAAC;AAEF,eAAO,MAAM,8BAA8B,oHACmE,CAAC;AAE/G,eAAO,MAAM,2BAA2B;;;;GA0BvC,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAoNlD,CAAC;AAEF,eAAO,MAAM,oBAAoB,UAShC,CAAC;AAEF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAElE;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,MAAM,CAYR"}
@@ -39,22 +39,148 @@ export const diviMigrationCopilotPrompts = [
39
39
  export const diviPrompts = {
40
40
  createHeroSection: {
41
41
  name: 'create_divi_hero',
42
- description: 'Create a Divi hero section using structured nodes',
43
- template: `Create a Divi 5 hero structure (or Divi 4 equivalent on legacy pages) using node format:
42
+ description: 'Build a Divi 5 hero section with native heading, text, and button modules',
43
+ template: `Build a Divi 5 hero with native modules (heading, text, button). Use node format { type, attributes, children, content }. The plugin auto-detects Divi 4 vs Divi 5, so write Divi 5 unless the page is on legacy Divi 4.
44
44
  {
45
45
  type: "divi/section",
46
- attributes: { background_color: "{bg_color}", background_image: "{bg_url}" },
46
+ attributes: { background_color: "{bg_color}" },
47
47
  children: [
48
48
  {
49
49
  type: "divi/row",
50
50
  attributes: {},
51
51
  children: [
52
- { type: "divi/text", attributes: { text_orientation: "center" }, content: "<h1>{heading}</h1><p>{subheading}</p>" },
53
- { type: "divi/button", attributes: { button_url: "{url}", button_text: "{text}", button_alignment: "center" }, content: "" }
52
+ {
53
+ type: "divi/column",
54
+ attributes: {},
55
+ children: [
56
+ { type: "divi/heading", attributes: { title: "{heading}", text_orientation: "center" }, content: "" },
57
+ { type: "divi/text", attributes: { text_orientation: "center" }, content: "<p>{subheading}</p>" },
58
+ { type: "divi/button", attributes: { linkUrl: "{url}", button_text: "{text}", button_alignment: "center" }, content: "" }
59
+ ]
60
+ }
54
61
  ]
55
62
  }
56
63
  ]
57
64
  }`,
65
+ },
66
+ createFeaturesSection: {
67
+ name: 'create_divi_features',
68
+ description: 'Build a Divi 5 features row of three blurb columns',
69
+ template: `Build a Divi 5 features section: one row, three columns, each with a native divi/blurb. Use node format { type, attributes, children, content }. divi/blurb title is an object: { "text": "..." }.
70
+ {
71
+ type: "divi/section",
72
+ attributes: {},
73
+ children: [
74
+ {
75
+ type: "divi/row",
76
+ attributes: { flexColumnStructure: "8_24,8_24,8_24" },
77
+ children: [
78
+ {
79
+ type: "divi/column",
80
+ attributes: {},
81
+ children: [
82
+ { type: "divi/blurb", attributes: { title: { text: "{feature_1_title}" }, image: "{feature_1_icon}" }, content: "<p>{feature_1_text}</p>" }
83
+ ]
84
+ },
85
+ {
86
+ type: "divi/column",
87
+ attributes: {},
88
+ children: [
89
+ { type: "divi/blurb", attributes: { title: { text: "{feature_2_title}" }, image: "{feature_2_icon}" }, content: "<p>{feature_2_text}</p>" }
90
+ ]
91
+ },
92
+ {
93
+ type: "divi/column",
94
+ attributes: {},
95
+ children: [
96
+ { type: "divi/blurb", attributes: { title: { text: "{feature_3_title}" }, image: "{feature_3_icon}" }, content: "<p>{feature_3_text}</p>" }
97
+ ]
98
+ }
99
+ ]
100
+ }
101
+ ]
102
+ }`,
103
+ },
104
+ createCtaSection: {
105
+ name: 'create_divi_cta',
106
+ description: 'Build a centered Divi 5 call-to-action section',
107
+ template: `Build a centered Divi 5 call-to-action: heading, text, and button in one column. Use node format { type, attributes, children, content }.
108
+ {
109
+ type: "divi/section",
110
+ attributes: { background_color: "{bg_color}" },
111
+ children: [
112
+ {
113
+ type: "divi/row",
114
+ attributes: {},
115
+ children: [
116
+ {
117
+ type: "divi/column",
118
+ attributes: {},
119
+ children: [
120
+ { type: "divi/heading", attributes: { title: "{heading}", text_orientation: "center" }, content: "" },
121
+ { type: "divi/text", attributes: { text_orientation: "center" }, content: "<p>{subheading}</p>" },
122
+ { type: "divi/button", attributes: { linkUrl: "{url}", button_text: "{text}", button_alignment: "center" }, content: "" }
123
+ ]
124
+ }
125
+ ]
126
+ }
127
+ ]
128
+ }`,
129
+ },
130
+ createLandingPage: {
131
+ name: 'create_divi_landing',
132
+ description: 'Build a basic Divi 5 landing page: hero, features, image, and CTA sections',
133
+ template: `Build a basic Divi 5 landing page by composing native-module sections in order: hero, features, image, then CTA. Use node format { type, attributes, children, content }. Pass this whole array as the page content.
134
+ [
135
+ {
136
+ type: "divi/section",
137
+ attributes: { background_color: "{hero_bg}" },
138
+ children: [
139
+ { type: "divi/row", attributes: {}, children: [
140
+ { type: "divi/column", attributes: {}, children: [
141
+ { type: "divi/heading", attributes: { title: "{hero_heading}", text_orientation: "center" }, content: "" },
142
+ { type: "divi/text", attributes: { text_orientation: "center" }, content: "<p>{hero_subheading}</p>" },
143
+ { type: "divi/button", attributes: { linkUrl: "{hero_url}", button_text: "{hero_cta}", button_alignment: "center" }, content: "" }
144
+ ] }
145
+ ] }
146
+ ]
147
+ },
148
+ {
149
+ type: "divi/section",
150
+ attributes: {},
151
+ children: [
152
+ { type: "divi/row", attributes: { flexColumnStructure: "8_24,8_24,8_24" }, children: [
153
+ { type: "divi/column", attributes: {}, children: [ { type: "divi/blurb", attributes: { title: { text: "{feature_1_title}" } }, content: "<p>{feature_1_text}</p>" } ] },
154
+ { type: "divi/column", attributes: {}, children: [ { type: "divi/blurb", attributes: { title: { text: "{feature_2_title}" } }, content: "<p>{feature_2_text}</p>" } ] },
155
+ { type: "divi/column", attributes: {}, children: [ { type: "divi/blurb", attributes: { title: { text: "{feature_3_title}" } }, content: "<p>{feature_3_text}</p>" } ] }
156
+ ] }
157
+ ]
158
+ },
159
+ {
160
+ type: "divi/section",
161
+ attributes: {},
162
+ children: [
163
+ { type: "divi/row", attributes: {}, children: [
164
+ { type: "divi/column", attributes: {}, children: [
165
+ { type: "divi/image", attributes: { src: "{image_url}", alt: "{image_alt}", align: "center" }, content: "" }
166
+ ] }
167
+ ] }
168
+ ]
169
+ },
170
+ {
171
+ type: "divi/section",
172
+ attributes: { background_color: "{cta_bg}" },
173
+ children: [
174
+ { type: "divi/row", attributes: {}, children: [
175
+ { type: "divi/column", attributes: {}, children: [
176
+ { type: "divi/heading", attributes: { title: "{cta_heading}", text_orientation: "center" }, content: "" },
177
+ { type: "divi/text", attributes: { text_orientation: "center" }, content: "<p>{cta_subheading}</p>" },
178
+ { type: "divi/button", attributes: { linkUrl: "{cta_url}", button_text: "{cta_text}", button_alignment: "center" }, content: "" }
179
+ ] }
180
+ ] }
181
+ ]
182
+ }
183
+ ]`,
58
184
  },
59
185
  updateModuleStyle: {
60
186
  name: 'update_divi_style',
@@ -1 +1 @@
1
- {"version":3,"file":"divi-prompts.js","sourceRoot":"","sources":["../src/divi-prompts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;CAI/B,CAAC;AAEF,MAAM,CAAC,MAAM,8BAA8B,GACzC,4GAA4G,CAAC;AAE/G,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC;QACE,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,2BAA2B;QAClC,MAAM,EAAE,wOAAwO;KACjP;IACD;QACE,EAAE,EAAE,qBAAqB;QACzB,KAAK,EAAE,uBAAuB;QAC9B,MAAM,EAAE,2MAA2M;KACpN;IACD;QACE,EAAE,EAAE,uBAAuB;QAC3B,KAAK,EAAE,4BAA4B;QACnC,MAAM,EAAE,gNAAgN;KACzN;IACD;QACE,EAAE,EAAE,mBAAmB;QACvB,KAAK,EAAE,mBAAmB;QAC1B,MAAM,EAAE,yMAAyM;KAClN;IACD;QACE,EAAE,EAAE,kBAAkB;QACtB,KAAK,EAAE,uBAAuB;QAC9B,MAAM,EAAE,8OAA8O;KACvP;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAA+B;IACrD,iBAAiB,EAAE;QACjB,IAAI,EAAE,kBAAkB;QACxB,WAAW,EAAE,mDAAmD;QAChE,QAAQ,EAAE;;;;;;;;;;;;;;EAcZ;KACC;IAED,iBAAiB,EAAE;QACjB,IAAI,EAAE,mBAAmB;QACzB,WAAW,EAAE,wCAAwC;QACrD,QAAQ,EAAE;;;;;;;qDAOuC;KAClD;IAED,iBAAiB,EAAE;QACjB,IAAI,EAAE,kBAAkB;QACxB,WAAW,EAAE,yCAAyC;QACtD,QAAQ,EAAE;;;;;gCAKkB;KAC7B;IAED,SAAS,EAAE;QACT,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,kCAAkC;QAC/C,QAAQ,EAAE;;;;;;;;yMAQ2L;KACtM;IAED,WAAW,EAAE;QACX,IAAI,EAAE,mBAAmB;QACzB,WAAW,EAAE,wCAAwC;QACrD,QAAQ,EAAE;;;;;CAKb;KACE;IAED,YAAY,EAAE;QACZ,IAAI,EAAE,oBAAoB;QAC1B,WAAW,EAAE,+BAA+B;QAC5C,QAAQ,EAAE;;;;;;;;wDAQ0C;KACrD;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,kBAAkB;IAClB,YAAY;IACZ,eAAe;IACf,SAAS;IACT,QAAQ;IACR,aAAa;IACb,WAAW;IACX,cAAc;CACf,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,UAAkB,EAClB,SAAiC;IAEjC,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC/B,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACjD,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"divi-prompts.js","sourceRoot":"","sources":["../src/divi-prompts.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoBH,OAAO,KAAK,EAAE,mBAAmB,EAAe,MAAM,kBAAkB,CAAC;AAiLzE,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,KAAK,CAA2C;IACxD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB,CAAS;IAEpC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAsB;IAEhE;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;gBA4Bb,WAAW,EAAE,mBAAmB,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE;IAmUvE,OAAO,CAAC,cAAc;IAItB;;;;;;;;;OASG;IACH,OAAO,CAAC,oBAAoB;IAkB5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IA4BrB,gEAAgE;IAChE,OAAO,CAAC,aAAa;IAUrB;;;;;;OAMG;YACW,UAAU;IA2CxB;;;;;;;;;;;;;;OAcG;YACW,WAAW;IAiJzB;;;;;;;;;OASG;YACW,kBAAkB;IA0GhC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,6BAA6B;IA0BrC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IA+BjC,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,aAAa;YA2PP,kBAAkB;YA6BlB,yBAAyB;IASvC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;YAyBd,QAAQ;IA6tFtB;;;;;;OAMG;IACH,yEAAyE;IACzE,OAAO,CAAC,mBAAmB,CAAoD;IAC/E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAU;YAEpC,oBAAoB;YAqDpB,2BAA2B;IAazC;;;;OAIG;YACW,cAAc;IAY5B,OAAO,CAAC,mBAAmB;IAwT3B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAe3C;IAEF;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;YAmCpB,cAAc;IAqG5B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,eAAe;YAuCT,gBAAgB;IA81B9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoUzB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA6UxB,GAAG;CAyCV"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoBH,OAAO,KAAK,EAAE,mBAAmB,EAAe,MAAM,kBAAkB,CAAC;AAkQzE,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,KAAK,CAA2C;IACxD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,YAAY,CAA4B;IAChD,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB,CAAS;IAEpC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAsB;IAEhE;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;gBA4Bb,WAAW,EAAE,mBAAmB,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE;IAqUvE,OAAO,CAAC,cAAc;IAItB;;;;;;;;;OASG;IACH,OAAO,CAAC,oBAAoB;IAkB5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IA4BrB,gEAAgE;IAChE,OAAO,CAAC,aAAa;IAUrB;;;;;;OAMG;YACW,UAAU;IA2CxB;;;;;;;;;;;;;;OAcG;YACW,WAAW;IAiJzB;;;;;;;;;OASG;YACW,kBAAkB;IA0GhC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,6BAA6B;IA0BrC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IA+BjC,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,aAAa;YAuPP,kBAAkB;YA6BlB,yBAAyB;IASvC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;YAyBd,QAAQ;IAmvFtB;;;;;;OAMG;IACH,yEAAyE;IACzE,OAAO,CAAC,mBAAmB,CAAoD;IAC/E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAU;YAEpC,oBAAoB;YAqDpB,2BAA2B;IAazC;;;;OAIG;YACW,cAAc;IAY5B,OAAO,CAAC,mBAAmB;IAwT3B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAe3C;IAEF;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;YAmCpB,cAAc;IAqG5B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,eAAe;YAuCT,gBAAgB;IAy2B9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoUzB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA6UxB,GAAG;CAyCV"}
package/dist/server.js CHANGED
@@ -123,6 +123,81 @@ class ToolTimeoutError extends Error {
123
123
  function getMaxToolTimeoutMs() {
124
124
  return Math.max(5000, parseInt(process.env.RESPIRA_MAX_TOOL_TIMEOUT_MS || '120000', 10));
125
125
  }
126
+ /**
127
+ * v7.2.1: hoist plugin-side dropped-styling warnings to the TOP of a build
128
+ * write result so the agent sees them before it inspects anything else.
129
+ *
130
+ * The Divi 5 build path (and any builder write) can drop style settings the
131
+ * builder does not recognise. The plugin reports these under the tool result's
132
+ * `warnings` array (objects shaped { reason: 'unknown_setting', key, type }) or
133
+ * a top-level `dropped_settings` array. This mirrors the existing
134
+ * write_was_noop hoist on update_element: the dashboard shows agents retry the
135
+ * same write blindly when the signal sits deep in the payload. Front-loading a
136
+ * plain-English hint stops the retry loop and tells the agent to pick a
137
+ * supported attribute or drop into a divi/code block instead.
138
+ *
139
+ * Defensive about the exact key shape: handles both the `warnings` array and a
140
+ * possible top-level `dropped_settings` array, and a pre-summarised
141
+ * `styling_dropped` count/string if the plugin sends one.
142
+ */
143
+ function hoistDroppedStylingWarnings(result) {
144
+ if (!result || typeof result !== 'object') {
145
+ return result;
146
+ }
147
+ const dropped = [];
148
+ // Shape 1: a `warnings` array of objects with reason: 'unknown_setting'.
149
+ if (Array.isArray(result.warnings)) {
150
+ for (const w of result.warnings) {
151
+ if (w && typeof w === 'object' && w.reason === 'unknown_setting') {
152
+ dropped.push({ key: w.key, type: w.type, reason: w.reason });
153
+ }
154
+ }
155
+ }
156
+ // Shape 2: a top-level `dropped_settings` array. Entries may be plain strings
157
+ // (the setting key) or objects with key/type.
158
+ if (Array.isArray(result.dropped_settings)) {
159
+ for (const d of result.dropped_settings) {
160
+ if (typeof d === 'string') {
161
+ dropped.push({ key: d });
162
+ }
163
+ else if (d && typeof d === 'object') {
164
+ dropped.push({ key: d.key, type: d.type, reason: d.reason });
165
+ }
166
+ }
167
+ }
168
+ if (dropped.length === 0) {
169
+ // Shape 3: a pre-summarised count/string with no per-setting detail.
170
+ const summary = result.styling_dropped;
171
+ if (typeof summary === 'number' && summary > 0) {
172
+ const hint = `${summary} style settings did not apply. use a supported attribute or a divi/code block. do not retry the same write.`;
173
+ result.dropped_styling_hint = hint;
174
+ const original = typeof result.message === 'string' ? result.message : '';
175
+ result.message = `STYLING DROPPED — ${hint}\n\n${original}`.trim();
176
+ result.agent_must_not_retry = true;
177
+ }
178
+ else if (typeof summary === 'string' && summary.trim() !== '') {
179
+ const hint = `${summary} use a supported attribute or a divi/code block. do not retry the same write.`;
180
+ result.dropped_styling_hint = hint;
181
+ const original = typeof result.message === 'string' ? result.message : '';
182
+ result.message = `STYLING DROPPED — ${hint}\n\n${original}`.trim();
183
+ result.agent_must_not_retry = true;
184
+ }
185
+ return result;
186
+ }
187
+ // Build a compact, readable list grouped by module type where known.
188
+ const parts = dropped.map((d) => {
189
+ const where = d.type ? ` on ${d.type}` : '';
190
+ return d.key ? `${d.key}${where}` : `unknown setting${where}`;
191
+ });
192
+ const list = parts.join(', ');
193
+ const hint = `${dropped.length} style setting${dropped.length === 1 ? '' : 's'} did not apply on these modules: ${list}. use a supported attribute or a divi/code block. do not retry the same write.`;
194
+ const original = typeof result.message === 'string' ? result.message : '';
195
+ result.message = `STYLING DROPPED — ${hint}\n\n${original}`.trim();
196
+ result.dropped_styling_hint = hint;
197
+ result.dropped_styling = dropped;
198
+ result.agent_must_not_retry = true;
199
+ return result;
200
+ }
126
201
  /**
127
202
  * v6.12.0: Tool names that do NOT receive the optional site_id input parameter.
128
203
  * These tools operate on global Respira config or pre-site setup state, so a
@@ -248,9 +323,9 @@ export class RespiraWordPressServer {
248
323
 
249
324
  ---
250
325
 
251
- Respira MCP Server v${RespiraWordPressServer.MCP_SERVER_VERSION} 180+ tools for WordPress (131 core + 20 Bricks + 8 Elementor + 21+ WooCommerce). New in v6.3: structuredContent in all tool results (MCP 2025-06-18 spec), improved error taxonomy (soft errors for unknown tools with self-correction hints).
326
+ Respira MCP Server — tools for editing WordPress across page builders (Elementor, Divi, Bricks, Beaver, Oxygen, Gutenberg, and more) plus WooCommerce and ACF.
252
327
 
253
- IMPORTANT: Always use Respira tools to read and edit WordPress content. NEVER access the database directly (SQL queries, wp_postmeta, wp_options), edit PHP files, or modify theme templates to change page content. Respira tools handle all content operations safely with snapshots, cache invalidation, and builder-native formats. Even for simple changes like updating a copyright year in a footer — use respira_find_element, not SQL.
328
+ IMPORTANT: Always use Respira tools to read and edit WordPress content. NEVER access the database directly (SQL queries, wp_postmeta, wp_options), edit PHP files, or modify theme templates to change page content. Respira tools handle content edits safely with per-page snapshots, cache invalidation, and builder-native formats. Even for simple changes like updating a copyright year in a footer — use respira_find_element, not SQL.
254
329
 
255
330
  CRITICAL TOOL CHOICE: For ANY in-page content edit (changing text, images, buttons, headings, links, colors, settings on existing widgets), use respira_find_element + respira_update_element. Do NOT use respira_update_page for content edits. respira_update_page replaces the ENTIRE page body and bypasses the page builder's structure — it produces "all code" pages where the builder treats every widget as a single text blob. respira_update_page is ONLY for: page title, slug, status, custom CSS, or full HTML replacement (e.g. "convert this Word doc into a page"). Telemetry across the user base shows respira_update_page being chosen ~13× more often than respira_update_element, but the right tool for "change the headline text" or "swap the hero image" is always respira_update_element.
256
331
 
@@ -340,6 +415,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
340
415
  - HTML-to-Elementor conversion creates native widgets with mapped CSS styles, responsive breakpoints, and optional global color/font creation
341
416
 
342
417
  ### Divi 5 (Full Intelligence)
418
+ - Build real pages with the native modules. divi/heading, divi/text, divi/image, divi/button, and divi/blurb are advertised by default and safe to use. Prefer them over divi/code raw HTML for real content. Reach for divi/code only for genuinely custom markup the native modules cannot express.
343
419
  - Content format: Block comments wrapping JSON module definitions inside <!-- wp:divi/placeholder -->
344
420
  - Every module needs "builderVersion": "5.0.1"
345
421
  - Hierarchy is strict: Section → Row → Column → Module (never skip levels)
@@ -352,6 +428,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
352
428
  - Blurb title must be {"text": "..."} object format, not a plain string
353
429
  - 40+ module definitions and 7 page patterns available
354
430
  - 13-point payload validation with 16 actionable error messages
431
+ - If a write comes back with dropped styling (dropped_styling_hint, or a STYLING DROPPED message at the top of the result), the builder did not recognise those settings. Do not retry the same write. Pick a supported attribute or move that bit into a divi/code block, then write again.
355
432
 
356
433
  ### Divi 4 Legacy (Smart Defaults)
357
434
  - Content format: Shortcode-based — [et_pb_section][et_pb_row][et_pb_column][et_pb_text]content[/et_pb_text][/et_pb_column][/et_pb_row][/et_pb_section]
@@ -459,7 +536,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
459
536
 
460
537
  ## Safety features
461
538
 
462
- - Snapshots: respira_list_snapshots, respira_get_snapshot, respira_diff_snapshots, respira_restore_snapshot — every edit creates a rollback point
539
+ - Snapshots: respira_list_snapshots, respira_get_snapshot, respira_diff_snapshots, respira_restore_snapshot — every page or post edit creates a rollback point. Destructive structure changes Respira makes (deleting a field group, post type, taxonomy, or changing an option) ALSO capture a recovery snapshot, returned as recovery_snapshot_uuid. An entire multi-step session can be undone in one call with wordpress_restore_session (use wordpress_begin_session / wordpress_end_session to bracket a cascade explicitly).
463
540
  - Duplicate workflow: respira_create_page_duplicate creates a safe copy; edits go to the duplicate; user approves in wp-admin to go live
464
541
  - Direct edit mode: when enabled in settings, writes go straight to the original (skip duplicate workflow)
465
542
  - respira_validate_security — check content for XSS and security issues before saving
@@ -1109,7 +1186,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
1109
1186
  content: [
1110
1187
  {
1111
1188
  type: 'text',
1112
- text: JSON.stringify({ ...errorPayload, site: activeSite }, null, 2),
1189
+ text: JSON.stringify({ ...errorPayload, site: activeSite }),
1113
1190
  },
1114
1191
  ],
1115
1192
  isError: true,
@@ -1126,7 +1203,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
1126
1203
  content: [
1127
1204
  {
1128
1205
  type: 'text',
1129
- text: JSON.stringify(resultWithVersionWarning, null, 2),
1206
+ text: JSON.stringify(resultWithVersionWarning),
1130
1207
  },
1131
1208
  ],
1132
1209
  // MCP 2025-06-18+: raw JSON for programmatic access alongside text for LLMs.
@@ -1155,7 +1232,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
1155
1232
  'To triangulate which network layer (origin / Cloudflare / Wordfence) is slow, run respira_diagnose_connection — it probes the same site with HEAD/GET/OPTIONS and reports per-layer latency + edge-layer presence. ' +
1156
1233
  'To file a structured bug report from inside this chat, call respira_report_issue.',
1157
1234
  site: activeSite,
1158
- }, null, 2),
1235
+ }),
1159
1236
  },
1160
1237
  ],
1161
1238
  isError: true,
@@ -1270,7 +1347,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
1270
1347
  // filing (see the "When something goes wrong" section in the
1271
1348
  // server handshake instructions).
1272
1349
  file_bug_hint: 'If retry + respira_diagnose_connection do not resolve this, ask the user if they want to file a bug report. If yes, call respira_report_issue with title, brief, severity, steps_to_reproduce, expected, actual, error_messages, and last_tool. The bug lands at https://www.respira.press/dashboard/bug-reports with full MCP context auto-attached.',
1273
- }, null, 2),
1350
+ }),
1274
1351
  },
1275
1352
  ],
1276
1353
  isError: true,
@@ -1493,7 +1570,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
1493
1570
  },
1494
1571
  {
1495
1572
  name: 'wordpress_create_page_duplicate',
1496
- description: 'Create a duplicate of an existing page for safe editing. IMPORTANT: Always use this before making changes to an original page. After creating a duplicate, edit the duplicate using wordpress_update_page. The user must then approve the duplicate in WordPress admin (Respira → Page Approvals) to replace the original page.',
1573
+ description: 'Create an approval-gated duplicate of a page for staged editing. Optional, NOT required before edits: every write already auto-captures a snapshot, and the write response returns a snapshot_uuid for one-call rollback via respira_restore_snapshot. Use a duplicate only when you want a separate copy the user reviews and approves in WordPress admin (Respira → Page Approvals) before it replaces the original. After creating it, edit the duplicate with wordpress_update_page.',
1497
1574
  inputSchema: {
1498
1575
  type: 'object',
1499
1576
  properties: {
@@ -1639,7 +1716,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
1639
1716
  },
1640
1717
  {
1641
1718
  name: 'wordpress_create_post_duplicate',
1642
- description: 'Create a duplicate of an existing post for safe editing. IMPORTANT: Always use this before making changes to an original post. After creating a duplicate, edit the duplicate using wordpress_update_post. The user must then approve the duplicate in WordPress admin (Respira → Page Approvals) to replace the original post.',
1719
+ description: 'Create an approval-gated duplicate of a post for staged editing. Optional, NOT required before edits: every write already auto-captures a snapshot, and the write response returns a snapshot_uuid for one-call rollback via respira_restore_snapshot. Use a duplicate only when you want a separate copy the user reviews and approves in WordPress admin (Respira → Page Approvals) before it replaces the original. After creating it, edit the duplicate with wordpress_update_post.',
1643
1720
  inputSchema: {
1644
1721
  type: 'object',
1645
1722
  properties: {
@@ -1943,7 +2020,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
1943
2020
  },
1944
2021
  {
1945
2022
  name: 'wordpress_inject_builder_content',
1946
- description: 'REPLACE (or append to) the entire page builder layout. WARNING: By default this REPLACES all existing content (use mode:"append" to add content without destroying existing elements). For editing a single module, use wordpress_update_module instead. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery. For Divi, divi_version is required ("4" or "5"). Starting in plugin v7.0.16, calling this against a page that already has content with mode="replace" (the default) without also passing confirm_replace=true returns a 409 respira_replace_confirmation_required. Pass mode="append" to add to existing content, pass mode="replace" AND confirm_replace=true to overwrite, or pass edit_target="live" to overwrite the live page directly (plugin v7.0.22+ accepts an authorized live edit as the confirmation). The gate prevents silent data loss.\n\nNested Divi 5 content shape (canonical): `content: [{ type: "section", settings: {...}, children: [{ type: "row", children: [{ type: "column", children: [{ type: "heading", settings: {...} }, ...] }] }] }]`. Plugin v7.0.32 also accepts `rows / cols / columns / modules / elements / innerBlocks` as aliases for `children` on Divi (Divi-vocabulary; soft warning surfaces in response.warnings). Plugin v7.0.32 hard-errors with `respira_children_dropped` (HTTP 500) if any container payload declares children but the emit produced fewer innerBlocks (silent data-loss is no longer possible).\n\nDivi render-critical attrs (the plugin returns `respira_divi_required_attrs_missing` if any are absent, because the module would render empty): `divi/heading` requires `title`; `divi/text` requires `content`; `divi/code` requires `raw_content`; `divi/blurb` requires `title`; `divi/team-member` requires `name`; `divi/testimonial` requires `author`; `divi/cta` requires `title`; `divi/pricing-table` requires `title` and `sum`; `divi/number-counter` requires `number`; `divi/button` requires `button_text`. Set each via the module\'s `settings` object.',
2023
+ description: 'REPLACE (or append to) the entire page builder layout. WARNING: By default this REPLACES all existing content (use mode:"append" to add content without destroying existing elements). For editing a single module, use wordpress_update_module instead. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery. For Divi, divi_version is required ("4" or "5"). Replacing a page that already has content with mode="replace" requires confirm_replace=true, else returns 409 respira_replace_confirmation_required. Alternatives: mode="append" to add to existing content, or edit_target="live" to overwrite the live page directly. The gate prevents silent data loss.\n\nCanonical Divi 5 nested shape: `content: [{ type: "section", children: [{ type: "row", children: [{ type: "column", children: [{ type: "heading", settings: {...} }] }] }] }]` (rows/cols/columns/modules/elements/innerBlocks also accepted as `children` aliases). Each Divi module needs its render-critical attr in `settings` (e.g. headingtitle, textcontent, button→button_text); the plugin names any missing one via `respira_divi_required_attrs_missing`.',
1947
2024
  inputSchema: {
1948
2025
  type: 'object',
1949
2026
  properties: {
@@ -2150,6 +2227,28 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2150
2227
  },
2151
2228
  idempotentHint: true,
2152
2229
  },
2230
+ {
2231
+ name: 'wordpress_restore_session',
2232
+ description: 'Cascade rollback: undo an entire session of changes in ONE call. Restores every distinct object the session touched (pages, ACF field groups, post types, taxonomies, options) to its pre-session state. Pass the session_id surfaced as recovery_session_id on a destructive response (or returned by wordpress_begin_session). Use this to recover from a destructive multi-step mess instead of restoring snapshots one at a time.',
2233
+ inputSchema: {
2234
+ type: 'object',
2235
+ properties: {
2236
+ session_id: { type: 'string', description: 'The session id to roll back.' },
2237
+ },
2238
+ required: ['session_id'],
2239
+ },
2240
+ idempotentHint: true,
2241
+ },
2242
+ {
2243
+ name: 'wordpress_begin_session',
2244
+ description: 'Begin an explicit snapshot session. Every change made after this (until wordpress_end_session) is grouped under one session_id, so the whole batch can be rolled back together with wordpress_restore_session. Returns the session_id. Without an explicit session, changes are auto-grouped by a time window.',
2245
+ inputSchema: { type: 'object', properties: {} },
2246
+ },
2247
+ {
2248
+ name: 'wordpress_end_session',
2249
+ description: 'End the explicit snapshot session started by wordpress_begin_session. Later changes fall back to automatic time-window grouping.',
2250
+ inputSchema: { type: 'object', properties: {} },
2251
+ },
2153
2252
  {
2154
2253
  name: 'wordpress_apply_builder_patch',
2155
2254
  description: 'Apply a list of targeted builder patch operations. Each operation is { identifier: { id|admin_label|path|type [+match_content] }, updates: { content?, attributes?, ...flat_settings? } }. The identifier block follows the same shape as wordpress_find_element\'s identifier; the updates block follows the same shape as wordpress_update_module\'s updates. Returns 400 respira_patch_invalid_operation when an entry is missing either field. This is NOT a JSON-Patch document — do not pass { op, path, value } shapes (those will be rejected). Example: { operations: [{ identifier: { admin_label: "Hero" }, updates: { admin_label: "Hero Content" } }] }.',
@@ -2333,7 +2432,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
2333
2432
  // token or per-site license api_token), exactly like mcp-spend.
2334
2433
  // Server-side this lands as a bug_report row with source='mcp'.
2335
2434
  name: 'wordpress_report_issue',
2336
- description: 'File a structured bug report from inside the chat. Call this when the user reports something broken a tool failed, the WordPress edit produced wrong output, a step in your workflow timed out, the MCP behaved differently than the description promised, or anything else worth fixing. The report goes directly to Mihai (the maintainer) with the live MCP context attached and shows up at https://www.respira.press/dashboard/bug-reports for tracking. Prefer this over asking the user to email or open a Discord thread — they\'ll have to retype everything you already know about the failure. Required args: title (8-120 chars), brief (20-280 chars), steps_to_reproduce (>=12 chars). Optional but strongly encouraged: severity (blocker|major|minor|cosmetic), expected, actual, error_messages (paste verbatim), last_tool (which tool was running when it failed). Auto-attached from the MCP server: site_url, mcp_version, builder, builder_version. If you ran respira_diagnose_connection in this chat, pass its full result as the diagnostics arg. Privacy: this tool never sends prompts, tool args, tool results, or WordPress content. Only the structured fields you put in.',
2435
+ description: 'File a structured bug report from inside the chat when something breaks: a tool failed, an edit produced wrong output, a step timed out, or the MCP behaved differently than its description. Goes directly to the maintainer with live MCP context and shows up at https://www.respira.press/dashboard/bug-reports. Required: title (8-120 chars), brief (20-280 chars), steps_to_reproduce (>=12 chars). Optional but encouraged: severity (blocker|major|minor|cosmetic), expected, actual, error_messages (verbatim), last_tool. Auto-attached: site_url, mcp_version, builder, builder_version. If you ran respira_diagnose_connection, pass its full result as diagnostics. Privacy: never sends prompts, tool args, tool results, or WordPress content, only the structured fields you put in.',
2337
2436
  inputSchema: {
2338
2437
  type: 'object',
2339
2438
  required: ['title', 'brief', 'steps_to_reproduce'],
@@ -3261,13 +3360,13 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
3261
3360
  },
3262
3361
  {
3263
3362
  name: 'wordpress_create_acf_field_group',
3264
- description: "Create an ACF (Advanced Custom Fields) field group. Requires ACF (free or Pro) active on the site — call wordpress_get_site_context first to confirm. Stored in wp_option respira_custom_acf_field_groups and registered via acf_add_local_field_group() on acf/init. Use to bind structured fields (text, number, image, post relation) to specific post types via the location rules.",
3363
+ description: "Create an ACF (Advanced Custom Fields) field group. Requires ACF (free or Pro) active on the site — call wordpress_get_site_context first to confirm. Stored in wp_option respira_custom_acf_field_groups and registered via acf_add_local_field_group() on acf/init. Use to bind structured fields (text, number, image, post relation) to specific post types via the location rules. Note: groups created this way are registered locally, so they are read-only in the ACF admin UI and editable only via these tools. The response includes `field_hints` when a field looks like it picked the wrong type or width.",
3265
3364
  inputSchema: {
3266
3365
  type: 'object',
3267
3366
  properties: {
3268
3367
  key: { type: 'string', description: 'ACF group key. Defaults to "group_<uuid>" if omitted. Must start with "group_".' },
3269
3368
  title: { type: 'string', description: 'Human-readable title shown in the post edit screen.' },
3270
- fields: { type: 'array', description: 'Array of ACF field definitions. Each follows ACF\'s schema: { key, label, name, type, ... }. See ACF docs for type-specific properties.', items: { type: 'object' } },
3369
+ fields: { type: 'array', description: "Array of ACF field definitions. Each field: { name (machine snake_case), label (human), type, wrapper:{ width: 1-100 }, return_format?, ... }. `key` is auto-generated if omitted. PICK THE RIGHT TYPE — do not default everything to 'text': use 'taxonomy' to link real WordPress terms (categories/tags), 'relationship' or 'post_object' to reference other posts/pages, 'url' for links and map links (not 'text'), 'image'/'file'/'gallery' with return_format ('array'|'url'|'id') for media so it uploads to the media library instead of storing a raw URL, plus 'number'/'email'/'true_false'/'select'/'checkbox'/'radio'/'date_picker'/'wysiwyg'/'repeater'/'group' where they fit. Set wrapper.width to lay related fields side by side (e.g. 50/50, 33/33/33, 25) rather than leaving every field at the 100% default. Set return_format on image/file/url/date fields.", items: { type: 'object' } },
3271
3370
  location: { type: 'array', description: 'ACF location rules nested array. Example: [[{param:"post_type", operator:"==", value:"case_study"}]] = attach to case_study post type.', items: { type: 'array', items: { type: 'object' } } },
3272
3371
  },
3273
3372
  required: ['title'],
@@ -4877,17 +4976,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4877
4976
  case 'wordpress_get_builder_inline_schemas':
4878
4977
  return await client.getBuilderInlineSchemas(args.builder, args.types);
4879
4978
  case 'wordpress_inject_builder_content':
4880
- return {
4979
+ return hoistDroppedStylingWarnings({
4881
4980
  ...(await client.injectBuilderContent(args.builder, args.page_id, args.content, args.divi_version, args.edit_target, args.mode, args.confirm_replace)),
4882
4981
  respira_approvals_url: client.getApprovalsUrl(),
4883
- };
4982
+ });
4884
4983
  case 'wordpress_make_responsive':
4885
4984
  return await client.makeResponsive(args.page_id, args.dry_run || false);
4886
4985
  case 'wordpress_update_module':
4887
- return {
4986
+ return hoistDroppedStylingWarnings({
4888
4987
  ...(await client.updateModule(args.builder, args.page_id, args.module_identifier, args.updates, args.edit_target)),
4889
4988
  respira_approvals_url: client.getApprovalsUrl(),
4890
- };
4989
+ });
4891
4990
  case 'wordpress_validate_security':
4892
4991
  return await client.validateSecurity(args.content);
4893
4992
  case 'wordpress_read_theme_file':
@@ -5181,6 +5280,12 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5181
5280
  return await client.diffSnapshots(args.snapshot_uuid_a, args.snapshot_uuid_b);
5182
5281
  case 'wordpress_restore_snapshot':
5183
5282
  return await client.restoreSnapshot(args.snapshot_uuid);
5283
+ case 'wordpress_restore_session':
5284
+ return await client.callRestV2('POST', '/snapshots/restore-session', { session_id: args.session_id });
5285
+ case 'wordpress_begin_session':
5286
+ return await client.callRestV2('POST', '/snapshots/session/begin', {});
5287
+ case 'wordpress_end_session':
5288
+ return await client.callRestV2('POST', '/snapshots/session/end', {});
5184
5289
  case 'wordpress_apply_builder_patch':
5185
5290
  return await client.applyBuilderPatch(args.builder, args.post_id, args.operations, args.include, args.edit_target);
5186
5291
  case 'woocommerce_list_products':
@@ -5309,7 +5414,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5309
5414
  return await client.callRestV2('POST', `/builder/elements/reorder/${post_id}`, rest);
5310
5415
  }
5311
5416
  case 'wordpress_build_page':
5312
- return await client.callRestV2('POST', '/builder/build', args);
5417
+ return hoistDroppedStylingWarnings(await client.callRestV2('POST', '/builder/build', args));
5313
5418
  case 'wordpress_convert_html_to_builder':
5314
5419
  return await client.callRestV2('POST', '/builder/convert', args);
5315
5420
  case 'wordpress_bulk_pages_operation':
@@ -5549,7 +5654,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5549
5654
  // --- Composite Tools ---
5550
5655
  {
5551
5656
  name: 'wordpress_build_page',
5552
- description: 'Create a complete page from a declarative structure. Accepts builder-specific widget definitions and creates the page with all elements in one call. Returns page_id, edit_url, and preview_url.\n\nNested structure shape (Divi 5 / any builder that nests): use `children: [...]` at every container level. As of plugin v7.0.32 the Divi builder also accepts `rows / cols / columns / modules / elements / innerBlocks` as aliases for `children` (Divi-vocabulary; soft warning surfaces in the response trace). Canonical example for a Divi 5 hero:\n\n```\nstructure: [{\n type: "section",\n settings: { background_color: "#1f4e7a", padding: "120px|0|120px|0" },\n children: [{\n type: "row",\n children: [{\n type: "column",\n children: [\n { type: "heading", settings: { title: "Hello", tag: "h1" } },\n { type: "text", settings: { content: "<p>Para</p>" } }\n ]\n }]\n }]\n}]\n```\n\nFlat shape also works pass leaf modules at the top level and the builder auto-wraps them in section > row > column. Plugin v7.0.32 hard-errors with `respira_children_dropped` (HTTP 500) if any container payload declares children but the emit produced fewer innerBlocks, so silent data-loss is no longer possible.',
5657
+ description: 'Create a complete page from a declarative structure. Accepts builder-specific widget definitions and creates the page with all elements in one call. Returns page_id, edit_url, and preview_url.\n\nNested shape (Divi 5 / any nesting builder): use `children: [...]` at every container level, e.g. `structure: [{ type: "section", children: [{ type: "row", children: [{ type: "column", children: [{ type: "heading", settings: { title: "Hi", tag: "h1" } }] }] }] }]`. Divi also accepts `rows/cols/columns/modules/elements/innerBlocks` as `children` aliases. Flat shape also works: pass leaf modules at the top level and the builder auto-wraps them in section > row > column.',
5553
5658
  inputSchema: {
5554
5659
  type: 'object',
5555
5660
  properties: {