@respira/wordpress-mcp-server 6.11.0 → 6.11.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/dist/server.js CHANGED
@@ -43,7 +43,7 @@ export class RespiraWordPressServer {
43
43
  allowedSites = null;
44
44
  /** Whether the plugin version warning has already been shown this session. */
45
45
  versionWarningShown = false;
46
- static MCP_SERVER_VERSION = '6.6.4';
46
+ static MCP_SERVER_VERSION = '6.11.1';
47
47
  /**
48
48
  * Normalize a tool name: respira_* → wordpress_* for switch dispatch.
49
49
  * Tracks whether the deprecated wordpress_* name was used.
@@ -196,7 +196,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
196
196
  - Module names use et_pb_ prefix: et_pb_text, et_pb_blurb, et_pb_image, et_pb_button, et_pb_cta, et_pb_slider, etc.
197
197
  - Settings are shortcode attributes: [et_pb_text text_font="Roboto" text_text_color="#333333"]
198
198
  - Button uses url/url_new_window attributes (different from Divi 5's linkUrl/linkTarget)
199
- - IMPORTANT: When injecting content, set diviVersion to "4" in respira_inject_builder_content
199
+ - IMPORTANT: When injecting content, set divi_version to "4" in respira_inject_builder_content
200
200
  - Respira auto-detects whether the site runs Divi 4 or Divi 5 — check respira_get_builder_info for the version
201
201
  - Element-level operations work via shortcode tree parsing
202
202
 
@@ -668,7 +668,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
668
668
  type: 'number',
669
669
  description: 'Page number for pagination',
670
670
  },
671
- perPage: {
671
+ per_page: {
672
672
  type: 'number',
673
673
  description: 'Number of items per page',
674
674
  },
@@ -705,7 +705,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
705
705
  inputSchema: {
706
706
  type: 'object',
707
707
  properties: {
708
- originalId: {
708
+ original_id: {
709
709
  type: 'number',
710
710
  description: 'ID of the original page to duplicate',
711
711
  },
@@ -714,7 +714,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
714
714
  description: 'Optional suffix for the duplicated page title',
715
715
  },
716
716
  },
717
- required: ['originalId'],
717
+ required: ['original_id'],
718
718
  },
719
719
  },
720
720
  {
@@ -743,7 +743,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
743
743
  type: 'string',
744
744
  description: 'URL slug (post_name). Example: "about-us" produces /about-us/.',
745
745
  },
746
- customCss: {
746
+ custom_css: {
747
747
  type: 'string',
748
748
  description: 'Custom CSS for the page. Auto-detects builder: Divi → _et_pb_custom_css, Elementor → _elementor_page_settings.custom_css.',
749
749
  },
@@ -751,7 +751,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
751
751
  type: 'object',
752
752
  description: 'Post meta data',
753
753
  },
754
- editTarget: {
754
+ edit_target: {
755
755
  type: 'string',
756
756
  description: 'Choose ask, live, or duplicate when the target is an original page.',
757
757
  enum: ['ask', 'live', 'duplicate'],
@@ -760,7 +760,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
760
760
  type: 'boolean',
761
761
  description: 'Backward-compatible alias for explicit live editing when direct editing is enabled.',
762
762
  },
763
- skipSecurityCheck: {
763
+ skip_security_check: {
764
764
  type: 'boolean',
765
765
  description: 'Advanced: bypass Respira content security checks and sanitization for this update. Use only with trusted content.',
766
766
  },
@@ -806,7 +806,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
806
806
  type: 'number',
807
807
  description: 'Page number',
808
808
  },
809
- perPage: {
809
+ per_page: {
810
810
  type: 'number',
811
811
  description: 'Items per page',
812
812
  },
@@ -843,7 +843,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
843
843
  inputSchema: {
844
844
  type: 'object',
845
845
  properties: {
846
- originalId: {
846
+ original_id: {
847
847
  type: 'number',
848
848
  description: 'Original post ID',
849
849
  },
@@ -852,7 +852,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
852
852
  description: 'Optional suffix',
853
853
  },
854
854
  },
855
- required: ['originalId'],
855
+ required: ['original_id'],
856
856
  },
857
857
  },
858
858
  {
@@ -912,19 +912,19 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
912
912
  type: 'object',
913
913
  description: 'Generic map of taxonomy slug to array of term ids/slugs for custom taxonomies (e.g. product_cat). Example: { "product_cat": [12, "featured"] }.',
914
914
  },
915
- featuredMedia: {
915
+ featured_media: {
916
916
  type: 'number',
917
917
  description: 'Attachment ID to set as the featured image. Pass 0 to clear.',
918
918
  },
919
- createMissingTerms: {
919
+ create_missing_terms: {
920
920
  type: 'boolean',
921
921
  description: 'When true, auto-create taxonomy terms that do not exist by name. Default false (strict: unknown slug returns 400).',
922
922
  },
923
- appendTerms: {
923
+ append_terms: {
924
924
  type: 'boolean',
925
925
  description: 'When true, append to existing taxonomy terms instead of replacing. Default false (replace).',
926
926
  },
927
- customCss: {
927
+ custom_css: {
928
928
  type: 'string',
929
929
  description: 'Custom CSS for the post. Auto-detects builder: Divi → _et_pb_custom_css, Elementor → _elementor_page_settings.custom_css.',
930
930
  },
@@ -932,7 +932,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
932
932
  type: 'object',
933
933
  description: 'Post meta data',
934
934
  },
935
- editTarget: {
935
+ edit_target: {
936
936
  type: 'string',
937
937
  description: 'Choose ask, live, or duplicate when the target is an original post.',
938
938
  enum: ['ask', 'live', 'duplicate'],
@@ -941,7 +941,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
941
941
  type: 'boolean',
942
942
  description: 'Backward-compatible alias for explicit live editing when direct editing is enabled.',
943
943
  },
944
- skipSecurityCheck: {
944
+ skip_security_check: {
945
945
  type: 'boolean',
946
946
  description: 'Advanced: bypass Respira content security checks and sanitization for this update. Use only with trusted content.',
947
947
  },
@@ -983,7 +983,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
983
983
  type: 'number',
984
984
  description: 'Page number',
985
985
  },
986
- perPage: {
986
+ per_page: {
987
987
  type: 'number',
988
988
  description: 'Items per page',
989
989
  },
@@ -1005,7 +1005,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1005
1005
  type: 'string',
1006
1006
  description: 'Filename for the uploaded file',
1007
1007
  },
1008
- mimeType: {
1008
+ mime_type: {
1009
1009
  type: 'string',
1010
1010
  description: 'MIME type of the file (e.g., image/jpeg, image/png, application/pdf)',
1011
1011
  },
@@ -1035,12 +1035,12 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1035
1035
  type: 'string',
1036
1036
  description: 'Builder name. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery. Use get_builder_info to discover the active builder first.',
1037
1037
  },
1038
- pageId: {
1038
+ page_id: {
1039
1039
  type: 'number',
1040
1040
  description: 'Page ID',
1041
1041
  },
1042
1042
  },
1043
- required: ['builder', 'pageId'],
1043
+ required: ['builder', 'page_id'],
1044
1044
  },
1045
1045
  readOnlyHint: true,
1046
1046
  },
@@ -1054,7 +1054,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1054
1054
  type: 'string',
1055
1055
  description: 'Builder name. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery. Use get_builder_info to discover the active builder first.',
1056
1056
  },
1057
- pageId: {
1057
+ page_id: {
1058
1058
  type: 'number',
1059
1059
  description: 'Page or post ID',
1060
1060
  },
@@ -1067,13 +1067,13 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1067
1067
  description: 'Maximum number of matches to return (default 10)',
1068
1068
  },
1069
1069
  },
1070
- required: ['builder', 'pageId'],
1070
+ required: ['builder', 'page_id'],
1071
1071
  },
1072
1072
  readOnlyHint: true,
1073
1073
  },
1074
1074
  {
1075
1075
  name: 'wordpress_inject_builder_content',
1076
- 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, diviVersion is required ("4" or "5").',
1076
+ 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").',
1077
1077
  inputSchema: {
1078
1078
  type: 'object',
1079
1079
  properties: {
@@ -1081,7 +1081,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1081
1081
  type: 'string',
1082
1082
  description: 'Builder identifier. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery.',
1083
1083
  },
1084
- pageId: {
1084
+ page_id: {
1085
1085
  type: 'number',
1086
1086
  description: 'Page ID',
1087
1087
  },
@@ -1094,18 +1094,18 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1094
1094
  description: 'replace (default): OVERWRITES all existing page content. append: adds new content after existing elements, preserving the current layout.',
1095
1095
  enum: ['replace', 'append'],
1096
1096
  },
1097
- editTarget: {
1097
+ edit_target: {
1098
1098
  type: 'string',
1099
1099
  description: 'When editing an original, choose ask, live, or duplicate. Defaults to ask when direct editing is enabled.',
1100
1100
  enum: ['ask', 'live', 'duplicate'],
1101
1101
  },
1102
- diviVersion: {
1102
+ divi_version: {
1103
1103
  type: 'string',
1104
1104
  description: 'Divi generation hint ("4" for shortcode format, "5" for block format). Required for all Divi inject calls.',
1105
1105
  enum: ['4', '5'],
1106
1106
  },
1107
1107
  },
1108
- required: ['builder', 'pageId', 'content'],
1108
+ required: ['builder', 'page_id', 'content'],
1109
1109
  },
1110
1110
  idempotentHint: true,
1111
1111
  },
@@ -1119,11 +1119,11 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1119
1119
  type: 'string',
1120
1120
  description: 'Builder name. Use exactly: gutenberg, divi, elementor, bricks, beaver, oxygen, breakdance, brizy, thrive, visual-composer, wpbakery.',
1121
1121
  },
1122
- pageId: {
1122
+ page_id: {
1123
1123
  type: 'number',
1124
1124
  description: 'Page ID',
1125
1125
  },
1126
- moduleIdentifier: {
1126
+ module_identifier: {
1127
1127
  type: 'object',
1128
1128
  description: 'Module identifier - use one of: admin_label, path, or type. For Elementor: prefer id or path (dot notation like "0.0.1"). admin_label maps to Navigator label or CSS ID. For Divi: admin_label maps to Admin Label field.',
1129
1129
  properties: {
@@ -1163,13 +1163,13 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1163
1163
  },
1164
1164
  },
1165
1165
  },
1166
- editTarget: {
1166
+ edit_target: {
1167
1167
  type: 'string',
1168
1168
  description: 'When editing an original, choose ask, live, or duplicate. Defaults to ask when direct editing is enabled.',
1169
1169
  enum: ['ask', 'live', 'duplicate'],
1170
1170
  },
1171
1171
  },
1172
- required: ['builder', 'pageId', 'moduleIdentifier', 'updates'],
1172
+ required: ['builder', 'page_id', 'module_identifier', 'updates'],
1173
1173
  },
1174
1174
  idempotentHint: true,
1175
1175
  },
@@ -1262,7 +1262,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1262
1262
  type: 'string',
1263
1263
  description: 'Builder slug',
1264
1264
  },
1265
- postId: {
1265
+ post_id: {
1266
1266
  type: 'number',
1267
1267
  description: 'Post ID',
1268
1268
  },
@@ -1275,13 +1275,13 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1275
1275
  description: 'Patch operations[]',
1276
1276
  items: { type: 'object' },
1277
1277
  },
1278
- editTarget: {
1278
+ edit_target: {
1279
1279
  type: 'string',
1280
1280
  description: 'When editing an original, choose ask, live, or duplicate. Defaults to ask when direct editing is enabled.',
1281
1281
  enum: ['ask', 'live', 'duplicate'],
1282
1282
  },
1283
1283
  },
1284
- required: ['builder', 'postId', 'operations'],
1284
+ required: ['builder', 'post_id', 'operations'],
1285
1285
  },
1286
1286
  idempotentHint: true,
1287
1287
  },
@@ -1334,12 +1334,12 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1334
1334
  inputSchema: {
1335
1335
  type: 'object',
1336
1336
  properties: {
1337
- pageId: {
1337
+ page_id: {
1338
1338
  type: 'number',
1339
1339
  description: 'Page ID to analyze',
1340
1340
  },
1341
1341
  },
1342
- required: ['pageId'],
1342
+ required: ['page_id'],
1343
1343
  },
1344
1344
  readOnlyHint: true,
1345
1345
  },
@@ -1349,12 +1349,12 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1349
1349
  inputSchema: {
1350
1350
  type: 'object',
1351
1351
  properties: {
1352
- pageId: {
1352
+ page_id: {
1353
1353
  type: 'number',
1354
1354
  description: 'Page ID to analyze',
1355
1355
  },
1356
1356
  },
1357
- required: ['pageId'],
1357
+ required: ['page_id'],
1358
1358
  },
1359
1359
  readOnlyHint: true,
1360
1360
  },
@@ -1364,12 +1364,12 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1364
1364
  inputSchema: {
1365
1365
  type: 'object',
1366
1366
  properties: {
1367
- pageId: {
1367
+ page_id: {
1368
1368
  type: 'number',
1369
1369
  description: 'Page ID to analyze',
1370
1370
  },
1371
1371
  },
1372
- required: ['pageId'],
1372
+ required: ['page_id'],
1373
1373
  },
1374
1374
  readOnlyHint: true,
1375
1375
  },
@@ -1380,12 +1380,12 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1380
1380
  inputSchema: {
1381
1381
  type: 'object',
1382
1382
  properties: {
1383
- pageId: {
1383
+ page_id: {
1384
1384
  type: 'number',
1385
1385
  description: 'Page ID to analyze',
1386
1386
  },
1387
1387
  },
1388
- required: ['pageId'],
1388
+ required: ['page_id'],
1389
1389
  },
1390
1390
  readOnlyHint: true,
1391
1391
  },
@@ -1395,12 +1395,12 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1395
1395
  inputSchema: {
1396
1396
  type: 'object',
1397
1397
  properties: {
1398
- pageId: {
1398
+ page_id: {
1399
1399
  type: 'number',
1400
1400
  description: 'Page ID to analyze',
1401
1401
  },
1402
1402
  },
1403
- required: ['pageId'],
1403
+ required: ['page_id'],
1404
1404
  },
1405
1405
  readOnlyHint: true,
1406
1406
  },
@@ -1410,12 +1410,12 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1410
1410
  inputSchema: {
1411
1411
  type: 'object',
1412
1412
  properties: {
1413
- pageId: {
1413
+ page_id: {
1414
1414
  type: 'number',
1415
1415
  description: 'Page ID to analyze',
1416
1416
  },
1417
1417
  },
1418
- required: ['pageId'],
1418
+ required: ['page_id'],
1419
1419
  },
1420
1420
  readOnlyHint: true,
1421
1421
  },
@@ -1425,12 +1425,12 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1425
1425
  inputSchema: {
1426
1426
  type: 'object',
1427
1427
  properties: {
1428
- postId: {
1428
+ post_id: {
1429
1429
  type: 'number',
1430
1430
  description: 'Post or page ID to analyze',
1431
1431
  },
1432
1432
  },
1433
- required: ['postId'],
1433
+ required: ['post_id'],
1434
1434
  },
1435
1435
  readOnlyHint: true,
1436
1436
  },
@@ -1441,12 +1441,12 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1441
1441
  inputSchema: {
1442
1442
  type: 'object',
1443
1443
  properties: {
1444
- pageId: {
1444
+ page_id: {
1445
1445
  type: 'number',
1446
1446
  description: 'Page ID to analyze',
1447
1447
  },
1448
1448
  },
1449
- required: ['pageId'],
1449
+ required: ['page_id'],
1450
1450
  },
1451
1451
  readOnlyHint: true,
1452
1452
  },
@@ -1456,12 +1456,12 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1456
1456
  inputSchema: {
1457
1457
  type: 'object',
1458
1458
  properties: {
1459
- pageId: {
1459
+ page_id: {
1460
1460
  type: 'number',
1461
1461
  description: 'Page ID to analyze',
1462
1462
  },
1463
1463
  },
1464
- required: ['pageId'],
1464
+ required: ['page_id'],
1465
1465
  },
1466
1466
  readOnlyHint: true,
1467
1467
  },
@@ -1531,7 +1531,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1531
1531
  inputSchema: {
1532
1532
  type: 'object',
1533
1533
  properties: {
1534
- slugOrUrl: {
1534
+ slug_or_url: {
1535
1535
  type: 'string',
1536
1536
  description: 'Plugin slug (from WordPress.org) or ZIP file URL',
1537
1537
  },
@@ -1541,7 +1541,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1541
1541
  enum: ['wordpress.org', 'url'],
1542
1542
  },
1543
1543
  },
1544
- required: ['slugOrUrl'],
1544
+ required: ['slug_or_url'],
1545
1545
  },
1546
1546
  },
1547
1547
  {
@@ -1617,7 +1617,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1617
1617
  type: 'number',
1618
1618
  description: 'Page number',
1619
1619
  },
1620
- perPage: {
1620
+ per_page: {
1621
1621
  type: 'number',
1622
1622
  description: 'Items per page',
1623
1623
  },
@@ -1735,7 +1735,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1735
1735
  type: 'number',
1736
1736
  description: 'Page number',
1737
1737
  },
1738
- perPage: {
1738
+ per_page: {
1739
1739
  type: 'number',
1740
1740
  description: 'Items per page',
1741
1741
  },
@@ -1873,7 +1873,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1873
1873
  type: 'number',
1874
1874
  description: 'Page number',
1875
1875
  },
1876
- perPage: {
1876
+ per_page: {
1877
1877
  type: 'number',
1878
1878
  description: 'Items per page',
1879
1879
  },
@@ -2032,7 +2032,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
2032
2032
  type: 'number',
2033
2033
  description: 'Page number',
2034
2034
  },
2035
- perPage: {
2035
+ per_page: {
2036
2036
  type: 'number',
2037
2037
  description: 'Items per page',
2038
2038
  },
@@ -2133,11 +2133,11 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
2133
2133
  type: 'object',
2134
2134
  description: 'Generic map of taxonomy slug to array of term ids/slugs for custom taxonomies (e.g. product_cat). Example: { "product_cat": [12, "featured"] }.',
2135
2135
  },
2136
- featuredMedia: {
2136
+ featured_media: {
2137
2137
  type: 'number',
2138
2138
  description: 'Attachment ID to set as the featured image.',
2139
2139
  },
2140
- createMissingTerms: {
2140
+ create_missing_terms: {
2141
2141
  type: 'boolean',
2142
2142
  description: 'When true, auto-create taxonomy terms that do not exist by name. Default false (strict: unknown slug returns 400 and the post is rolled back).',
2143
2143
  },
@@ -2210,15 +2210,15 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
2210
2210
  type: 'object',
2211
2211
  description: 'Generic map of taxonomy slug to array of term ids/slugs for custom taxonomies.',
2212
2212
  },
2213
- featuredMedia: {
2213
+ featured_media: {
2214
2214
  type: 'number',
2215
2215
  description: 'Attachment ID to set as the featured image. Pass 0 to clear.',
2216
2216
  },
2217
- createMissingTerms: {
2217
+ create_missing_terms: {
2218
2218
  type: 'boolean',
2219
2219
  description: 'When true, auto-create taxonomy terms that do not exist by name. Default false (strict).',
2220
2220
  },
2221
- appendTerms: {
2221
+ append_terms: {
2222
2222
  type: 'boolean',
2223
2223
  description: 'When true, append to existing taxonomy terms instead of replacing. Default false (replace).',
2224
2224
  },
@@ -2226,11 +2226,11 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
2226
2226
  type: 'object',
2227
2227
  description: 'Post meta data (e.g., { "_et_pb_custom_css": "/* CSS */" })',
2228
2228
  },
2229
- customCss: {
2229
+ custom_css: {
2230
2230
  type: 'string',
2231
2231
  description: 'Custom CSS for page builders (Divi/Elementor). Automatically saved to the appropriate meta field based on detected builder.',
2232
2232
  },
2233
- editTarget: {
2233
+ edit_target: {
2234
2234
  type: 'string',
2235
2235
  description: 'Choose ask, live, or duplicate when the target is an original item.',
2236
2236
  enum: ['ask', 'live', 'duplicate'],
@@ -3073,12 +3073,79 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3073
3073
  },
3074
3074
  ];
3075
3075
  }
3076
+ /**
3077
+ * camelCase → snake_case rename map (mcp-v6.11.1).
3078
+ *
3079
+ * Schemas advertise snake_case only. Handlers can read either form for one
3080
+ * release; camelCase emits a deprecation warning to stderr and will be
3081
+ * removed in mcp-v6.13.
3082
+ *
3083
+ * Mirrors both directions so wordpress-client.ts (which still reads
3084
+ * camelCase fields like data.customCss / data.featuredMedia / args.edit_target
3085
+ * before mapping them to plugin-side snake_case) keeps working unchanged.
3086
+ * The wordpress-client surface is purely internal — its rename can ride
3087
+ * along in a follow-up release without breaking callers.
3088
+ */
3089
+ static CAMEL_TO_SNAKE_PARAMS = {
3090
+ pageId: 'page_id',
3091
+ postId: 'post_id',
3092
+ perPage: 'per_page',
3093
+ editTarget: 'edit_target',
3094
+ originalId: 'original_id',
3095
+ customCss: 'custom_css',
3096
+ skipSecurityCheck: 'skip_security_check',
3097
+ featuredMedia: 'featured_media',
3098
+ createMissingTerms: 'create_missing_terms',
3099
+ appendTerms: 'append_terms',
3100
+ mimeType: 'mime_type',
3101
+ moduleIdentifier: 'module_identifier',
3102
+ diviVersion: 'divi_version',
3103
+ slugOrUrl: 'slug_or_url',
3104
+ };
3105
+ /**
3106
+ * Normalize tool args so handlers see both snake_case (new, advertised in
3107
+ * the schema) and camelCase (legacy, kept working for one release with a
3108
+ * deprecation warning). Mutates the args object in place and returns it.
3109
+ */
3110
+ normalizeArgsForBackCompat(toolName, args) {
3111
+ if (!args || typeof args !== 'object' || Array.isArray(args)) {
3112
+ return args;
3113
+ }
3114
+ for (const [camel, snake] of Object.entries(RespiraWordPressServer.CAMEL_TO_SNAKE_PARAMS)) {
3115
+ const hasCamel = Object.prototype.hasOwnProperty.call(args, camel);
3116
+ const hasSnake = Object.prototype.hasOwnProperty.call(args, snake);
3117
+ if (hasCamel && !hasSnake) {
3118
+ // Caller is on the legacy camelCase path. Mirror to snake_case so
3119
+ // schema-aware downstream code can read it; warn once.
3120
+ args[snake] = args[camel];
3121
+ try {
3122
+ process.stderr.write(`[respira-mcp] deprecated: '${camel}' is renamed to '${snake}', will be removed in mcp-v6.13 (tool: ${toolName})\n`);
3123
+ }
3124
+ catch {
3125
+ // ignore stderr write failures.
3126
+ }
3127
+ }
3128
+ else if (!hasCamel && hasSnake) {
3129
+ // Caller is on the new snake_case path. Mirror to camelCase so the
3130
+ // wordpress-client.ts layer (which still reads camelCase) keeps
3131
+ // working without further surface changes this release.
3132
+ args[camel] = args[snake];
3133
+ }
3134
+ // If both are present, prefer snake_case as source of truth and leave
3135
+ // both in place. If neither, no-op.
3136
+ }
3137
+ return args;
3138
+ }
3076
3139
  async handleToolCall(name, args) {
3077
3140
  if (!this.currentSite) {
3078
3141
  throw new Error('No WordPress site configured');
3079
3142
  }
3080
3143
  // Normalize respira_* ↔ wordpress_* names.
3081
3144
  const { canonical, deprecated } = this.normalizeToolName(name);
3145
+ // mcp-v6.11.1: snake_case sweep across tool catalog. Schemas advertise
3146
+ // snake_case; legacy camelCase still accepted with a deprecation warning
3147
+ // for one release.
3148
+ args = this.normalizeArgsForBackCompat(canonical, args);
3082
3149
  const result = await this.dispatchToolCall(canonical, args);
3083
3150
  if (deprecated && result && typeof result === 'object' && !Array.isArray(result)) {
3084
3151
  return {
@@ -3120,7 +3187,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3120
3187
  return await this.currentSite.getPage(args.id, args.include);
3121
3188
  case 'wordpress_create_page_duplicate':
3122
3189
  return {
3123
- ...(await this.currentSite.duplicatePage(args.originalId, args.suffix, args.include)),
3190
+ ...(await this.currentSite.duplicatePage(args.original_id, args.suffix, args.include)),
3124
3191
  respira_approvals_url: this.currentSite.getApprovalsUrl(),
3125
3192
  };
3126
3193
  case 'wordpress_update_page': {
@@ -3153,7 +3220,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3153
3220
  return await this.currentSite.getPost(args.id, args.include);
3154
3221
  case 'wordpress_create_post_duplicate':
3155
3222
  return {
3156
- ...(await this.currentSite.duplicatePost(args.originalId, args.suffix, args.include)),
3223
+ ...(await this.currentSite.duplicatePost(args.original_id, args.suffix, args.include)),
3157
3224
  respira_approvals_url: this.currentSite.getApprovalsUrl(),
3158
3225
  };
3159
3226
  case 'wordpress_update_post': {
@@ -3183,19 +3250,19 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3183
3250
  case 'wordpress_list_media':
3184
3251
  return await this.currentSite.listMedia(args);
3185
3252
  case 'wordpress_upload_media':
3186
- return await this.currentSite.uploadMedia(args.file, args.filename, args.mimeType, args.title, args.alt, args.caption);
3253
+ return await this.currentSite.uploadMedia(args.file, args.filename, args.mime_type, args.title, args.alt, args.caption);
3187
3254
  case 'wordpress_extract_builder_content':
3188
- return await this.currentSite.extractBuilderContent(args.builder, args.pageId);
3255
+ return await this.currentSite.extractBuilderContent(args.builder, args.page_id);
3189
3256
  case 'wordpress_find_builder_targets':
3190
- return await this.currentSite.findBuilderTargets(args.builder, args.pageId, args.query, args.limit);
3257
+ return await this.currentSite.findBuilderTargets(args.builder, args.page_id, args.query, args.limit);
3191
3258
  case 'wordpress_inject_builder_content':
3192
3259
  return {
3193
- ...(await this.currentSite.injectBuilderContent(args.builder, args.pageId, args.content, args.diviVersion, args.editTarget, args.mode)),
3260
+ ...(await this.currentSite.injectBuilderContent(args.builder, args.page_id, args.content, args.divi_version, args.edit_target, args.mode)),
3194
3261
  respira_approvals_url: this.currentSite.getApprovalsUrl(),
3195
3262
  };
3196
3263
  case 'wordpress_update_module':
3197
3264
  return {
3198
- ...(await this.currentSite.updateModule(args.builder, args.pageId, args.moduleIdentifier, args.updates, args.editTarget)),
3265
+ ...(await this.currentSite.updateModule(args.builder, args.page_id, args.module_identifier, args.updates, args.edit_target)),
3199
3266
  respira_approvals_url: this.currentSite.getApprovalsUrl(),
3200
3267
  };
3201
3268
  case 'wordpress_validate_security':
@@ -3220,25 +3287,25 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3220
3287
  return await this.currentSite.diagnoseConnection({ post_id: args.post_id });
3221
3288
  // Page Speed Analysis
3222
3289
  case 'wordpress_analyze_performance':
3223
- return await this.currentSite.analyzePerformance(args.pageId);
3290
+ return await this.currentSite.analyzePerformance(args.page_id);
3224
3291
  case 'wordpress_get_core_web_vitals':
3225
- return await this.currentSite.getCoreWebVitals(args.pageId);
3292
+ return await this.currentSite.getCoreWebVitals(args.page_id);
3226
3293
  case 'wordpress_analyze_images':
3227
- return await this.currentSite.analyzeImages(args.pageId);
3294
+ return await this.currentSite.analyzeImages(args.page_id);
3228
3295
  // SEO Analysis
3229
3296
  case 'wordpress_analyze_seo':
3230
- return await this.currentSite.analyzeSEO(args.pageId);
3297
+ return await this.currentSite.analyzeSEO(args.page_id);
3231
3298
  case 'wordpress_check_seo_issues':
3232
- return await this.currentSite.checkSEOIssues(args.pageId);
3299
+ return await this.currentSite.checkSEOIssues(args.page_id);
3233
3300
  case 'wordpress_analyze_readability':
3234
- return await this.currentSite.analyzeReadability(args.pageId);
3301
+ return await this.currentSite.analyzeReadability(args.page_id);
3235
3302
  case 'wordpress_analyze_rankmath':
3236
- return await this.currentSite.analyzeRankMath(args.postId);
3303
+ return await this.currentSite.analyzeRankMath(args.post_id);
3237
3304
  // AEO Analysis
3238
3305
  case 'wordpress_analyze_aeo':
3239
- return await this.currentSite.analyzeAEO(args.pageId);
3306
+ return await this.currentSite.analyzeAEO(args.page_id);
3240
3307
  case 'wordpress_check_structured_data':
3241
- return await this.currentSite.checkStructuredData(args.pageId);
3308
+ return await this.currentSite.checkStructuredData(args.page_id);
3242
3309
  // Accessibility
3243
3310
  case 'wordpress_list_accessibility_scans':
3244
3311
  return await this.currentSite.listAccessibilityScans();
@@ -3252,7 +3319,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3252
3319
  case 'wordpress_list_plugins':
3253
3320
  return await this.currentSite.listPlugins();
3254
3321
  case 'wordpress_install_plugin':
3255
- return await this.currentSite.installPlugin(args.slugOrUrl, args.source || 'wordpress.org');
3322
+ return await this.currentSite.installPlugin(args.slug_or_url, args.source || 'wordpress.org');
3256
3323
  case 'wordpress_activate_plugin':
3257
3324
  return await this.currentSite.activatePlugin(args.slug);
3258
3325
  case 'wordpress_deactivate_plugin':
@@ -3367,7 +3434,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3367
3434
  case 'wordpress_restore_snapshot':
3368
3435
  return await this.currentSite.restoreSnapshot(args.snapshot_uuid);
3369
3436
  case 'wordpress_apply_builder_patch':
3370
- return await this.currentSite.applyBuilderPatch(args.builder, args.postId, args.operations, args.include, args.editTarget);
3437
+ return await this.currentSite.applyBuilderPatch(args.builder, args.post_id, args.operations, args.include, args.edit_target);
3371
3438
  case 'woocommerce_list_products':
3372
3439
  return await this.currentSite.woocommerceListProducts(args);
3373
3440
  case 'woocommerce_get_product':