@respira/wordpress-mcp-server 6.11.13 → 6.13.0

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
@@ -58,6 +58,43 @@ class ToolTimeoutError extends Error {
58
58
  function getMaxToolTimeoutMs() {
59
59
  return Math.max(5000, parseInt(process.env.RESPIRA_MAX_TOOL_TIMEOUT_MS || '120000', 10));
60
60
  }
61
+ /**
62
+ * v6.12.0: Tool names that do NOT receive the optional site_id input parameter.
63
+ * These tools operate on global Respira config or pre-site setup state, so a
64
+ * per-call site override doesn't apply:
65
+ * - list_sites: returns all configured sites
66
+ * - get_active_site: returns the global current site (a per-call override
67
+ * would be meaningless here; if you want a specific site's summary, look
68
+ * at list_sites)
69
+ * - switch_site: already takes site_id with the explicit semantics of
70
+ * flipping the global current site (different intent from per-call override)
71
+ * - redeem_token: pre-site setup; no site exists yet
72
+ */
73
+ const SITE_AGNOSTIC_TOOLS = new Set([
74
+ 'wordpress_list_sites',
75
+ 'wordpress_get_active_site',
76
+ 'wordpress_switch_site',
77
+ 'wordpress_redeem_token',
78
+ 'respira_list_sites',
79
+ 'respira_get_active_site',
80
+ 'respira_switch_site',
81
+ 'respira_redeem_token',
82
+ ]);
83
+ /**
84
+ * v6.12.0: Schema fragment auto-injected into every non-agnostic tool's
85
+ * inputSchema.properties. Lets callers override the active site on a single
86
+ * tool call without flipping the global current site.
87
+ *
88
+ * Solves the Cowork multi-chat contamination: when Cowork runs many chat
89
+ * sessions through one shared MCP server process, switch_site mutates the
90
+ * global current site for all sessions. Passing site_id per call sidesteps
91
+ * the shared state entirely. T.S. (studioscaler) reported the symptom
92
+ * 2026-05-16 across his multi-site workflow.
93
+ */
94
+ const SITE_ID_PROPERTY = {
95
+ type: 'string',
96
+ description: 'Optional. Override the active site for this single call without changing the global current site. Use this when running multiple Cowork chats against different WordPress sites in parallel so each chat can pin its own target site per tool call.',
97
+ };
61
98
  export class RespiraWordPressServer {
62
99
  server;
63
100
  currentSite = null;
@@ -359,6 +396,35 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
359
396
  }
360
397
  return this.getSiteSummary(this.currentSite);
361
398
  }
399
+ /**
400
+ * Resolve which WordPressClient should service this tool call.
401
+ *
402
+ * Order:
403
+ * 1) explicit args.site_id (per-call override; introduced in v6.12.0)
404
+ * 2) the global current site (`this.currentSite`)
405
+ *
406
+ * Throws if neither is set, or if the supplied site_id is unknown / not
407
+ * allowed for this MCP configuration group.
408
+ *
409
+ * @since 6.12.0
410
+ */
411
+ resolveClient(args) {
412
+ if (args && typeof args.site_id === 'string' && args.site_id.length > 0) {
413
+ const explicit = this.sites.get(args.site_id);
414
+ if (!explicit) {
415
+ const available = Array.from(this.sites.keys()).join(', ') || '(none configured)';
416
+ throw new Error(`Site with ID "${args.site_id}" not found in this MCP configuration. Available: ${available}`);
417
+ }
418
+ if (!this.isSiteAllowed(explicit)) {
419
+ throw new Error(`Site "${args.site_id}" is not in this MCP configuration group.`);
420
+ }
421
+ return explicit;
422
+ }
423
+ if (!this.currentSite) {
424
+ throw new Error('No active site. Either pass site_id on this tool call or run respira_switch_site / respira_redeem_token first.');
425
+ }
426
+ return this.currentSite;
427
+ }
362
428
  /** Check if a site is visible given RESPIRA_SITES filtering. */
363
429
  isSiteAllowed(site) {
364
430
  if (!this.allowedSites)
@@ -1182,7 +1248,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1182
1248
  },
1183
1249
  {
1184
1250
  name: 'wordpress_find_builder_targets',
1185
- description: 'List all editable builder modules/blocks on a page with their types, labels, and text content — without returning the full builder JSON. Use this for a quick overview of what is on a page. For finding a specific element by text or class, use find_element instead.',
1251
+ description: 'List all editable builder modules/blocks on a page with their types, labels, and text content — without returning the full builder JSON. Use this for a quick overview of what is on a page. For finding a specific element by text or class, use find_element instead. Returns `total_matches` (true total independent of limit), `offset`, `has_more`, and `next_offset` for pagination. WPBakery / Uncode targets now surface a populated `label` / `admin_label` / `text` so you do not have to grep the raw shortcode preview.',
1186
1252
  inputSchema: {
1187
1253
  type: 'object',
1188
1254
  properties: {
@@ -1200,16 +1266,59 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
1200
1266
  },
1201
1267
  limit: {
1202
1268
  type: 'number',
1203
- description: 'Maximum number of matches to return (default 10)',
1269
+ description: 'Maximum number of matches to return per page (default 10). The full match count is always returned as `total_matches` independent of this limit.',
1270
+ },
1271
+ offset: {
1272
+ type: 'number',
1273
+ description: 'Skip the first N matches before slicing the page. Defaults to 0. Paginate by passing the previous response\'s `next_offset` value.',
1204
1274
  },
1205
1275
  },
1206
1276
  required: ['builder', 'page_id'],
1207
1277
  },
1208
1278
  readOnlyHint: true,
1209
1279
  },
1280
+ {
1281
+ name: 'wordpress_get_page_outline',
1282
+ description: 'Row-level outline of a builder page. Lighter than extract_builder_content; one entry per top-level row with { index, type, kind, primary_heading, child_count, child_types } so you can answer "what is the structure of this page" without walking the full tree client-side. Works on every supported builder; adapters with a dedicated outline implementation (WPBakery + Uncode today) return a richer per-row child-type histogram.',
1283
+ inputSchema: {
1284
+ type: 'object',
1285
+ properties: {
1286
+ builder: {
1287
+ type: 'string',
1288
+ description: 'Builder name. Optional — auto-detected from the active site builder when omitted.',
1289
+ },
1290
+ page_id: {
1291
+ type: 'number',
1292
+ description: 'Page or post ID',
1293
+ },
1294
+ },
1295
+ required: ['page_id'],
1296
+ },
1297
+ readOnlyHint: true,
1298
+ },
1299
+ {
1300
+ name: 'wordpress_get_builder_inline_schemas',
1301
+ description: 'Per-shortcode / per-block inline attribute schemas for the requested builder. Currently populated for WPBakery + Uncode + TagDiv via vc_map() at runtime (cached as a transient for 1h). Other builders return `schemas: {}` + `supported: false` + a structured hint. Use this to discover what attributes a specific `vc_*` / `uncode_*` shortcode accepts before writing.',
1302
+ inputSchema: {
1303
+ type: 'object',
1304
+ properties: {
1305
+ builder: {
1306
+ type: 'string',
1307
+ description: 'Builder name. Optional — auto-detected from the active site builder when omitted. Pass "wpbakery" explicitly for non-WPBakery sites that want to inspect the catalog anyway.',
1308
+ },
1309
+ types: {
1310
+ type: 'array',
1311
+ items: { type: 'string' },
1312
+ description: 'Optional filter — only return schemas for these shortcode types (e.g. ["vc_custom_heading", "vc_button"]). Omit to return all registered schemas.',
1313
+ },
1314
+ },
1315
+ required: [],
1316
+ },
1317
+ readOnlyHint: true,
1318
+ },
1210
1319
  {
1211
1320
  name: 'wordpress_inject_builder_content',
1212
- 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, or pass mode="replace" AND confirm_replace=true to overwrite. The gate prevents silent data loss.',
1321
+ 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.',
1213
1322
  inputSchema: {
1214
1323
  type: 'object',
1215
1324
  properties: {
@@ -2851,6 +2960,18 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
2851
2960
  if (await this.isAcfAvailable()) {
2852
2961
  tools.push(...getAcfTools());
2853
2962
  }
2963
+ // v6.12.0: inject optional site_id into every non-agnostic tool schema.
2964
+ // Agnostic tools (list_sites, get_active_site, switch_site, redeem_token)
2965
+ // skip the injection because they operate on global state.
2966
+ for (const t of tools) {
2967
+ if (SITE_AGNOSTIC_TOOLS.has(t.name))
2968
+ continue;
2969
+ if (!t.inputSchema || t.inputSchema.type !== 'object')
2970
+ continue;
2971
+ const props = (t.inputSchema.properties ||= {});
2972
+ if (!props.site_id)
2973
+ props.site_id = SITE_ID_PROPERTY;
2974
+ }
2854
2975
  // Generate respira_* aliases for all wordpress_* tools.
2855
2976
  const allTools = this.generateDualTools(tools);
2856
2977
  // Context-aware tool filtering: expose only relevant tools based on
@@ -3318,9 +3439,11 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3318
3439
  return args;
3319
3440
  }
3320
3441
  async handleToolCall(name, args) {
3321
- if (!this.currentSite) {
3322
- throw new Error('No WordPress site configured');
3323
- }
3442
+ // v6.12.0: relaxed top-of-handler guard. dispatchToolCall / resolveClient
3443
+ // now decides whether a site is needed for THIS call (some tools are
3444
+ // site-agnostic, others accept a per-call site_id override). The old
3445
+ // eager `if (!this.currentSite) throw` blocked the new "call with just
3446
+ // site_id on a server with no default site" path.
3324
3447
  // Normalize respira_* ↔ wordpress_* names.
3325
3448
  const { canonical, deprecated } = this.normalizeToolName(name);
3326
3449
  // mcp-v6.11.1: snake_case sweep across tool catalog. Schemas advertise
@@ -3338,14 +3461,15 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3338
3461
  return result;
3339
3462
  }
3340
3463
  async dispatchToolCall(name, args) {
3341
- if (!this.currentSite) {
3342
- throw new Error('No WordPress site configured');
3343
- }
3464
+ // v6.12.0: per-call site resolution. Agnostic tools work without a
3465
+ // resolved client; everything else requires either args.site_id or a
3466
+ // global currentSite. resolveClient throws cleanly if neither is set.
3467
+ const client = SITE_AGNOSTIC_TOOLS.has(name) ? this.currentSite : this.resolveClient(args);
3344
3468
  switch (name) {
3345
3469
  case 'wordpress_get_site_context':
3346
3470
  return args.detail === 'full'
3347
- ? await this.currentSite.getSiteContext()
3348
- : await this.currentSite.getCompactSiteContext();
3471
+ ? await client.getSiteContext()
3472
+ : await client.getCompactSiteContext();
3349
3473
  case 'wordpress_list_sites': {
3350
3474
  const allSites = Array.from(this.sites.values());
3351
3475
  const visibleSites = allSites.filter((site) => this.isSiteAllowed(site));
@@ -3359,21 +3483,21 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3359
3483
  site: this.getActiveSiteSummary(),
3360
3484
  };
3361
3485
  case 'wordpress_get_theme_docs':
3362
- return await this.currentSite.getThemeDocs();
3486
+ return await client.getThemeDocs();
3363
3487
  case 'wordpress_get_builder_info':
3364
- return await this.currentSite.getBuilderInfo({ debug: Boolean(args?.debug) });
3488
+ return await client.getBuilderInfo({ debug: Boolean(args?.debug) });
3365
3489
  case 'wordpress_list_pages':
3366
- return await this.currentSite.listPages(args);
3490
+ return await client.listPages(args);
3367
3491
  case 'wordpress_read_page':
3368
- return await this.currentSite.getPage(args.id, args.include);
3492
+ return await client.getPage(args.id, args.include);
3369
3493
  case 'wordpress_create_page_duplicate':
3370
3494
  return {
3371
- ...(await this.currentSite.duplicatePage(args.original_id, args.suffix, args.include)),
3372
- respira_approvals_url: this.currentSite.getApprovalsUrl(),
3495
+ ...(await client.duplicatePage(args.original_id, args.suffix, args.include)),
3496
+ respira_approvals_url: client.getApprovalsUrl(),
3373
3497
  };
3374
3498
  case 'wordpress_update_page': {
3375
- const approvalsUrl = this.currentSite.getApprovalsUrl();
3376
- const page = await this.currentSite.updatePage(args.id, args);
3499
+ const approvalsUrl = client.getApprovalsUrl();
3500
+ const page = await client.updatePage(args.id, args);
3377
3501
  // Check if Respira created a duplicate
3378
3502
  if (page.__respira_duplicate_info) {
3379
3503
  const info = page.__respira_duplicate_info;
@@ -3394,19 +3518,19 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3394
3518
  };
3395
3519
  }
3396
3520
  case 'wordpress_delete_page':
3397
- return await this.currentSite.deletePage(args.id, args.force);
3521
+ return await client.deletePage(args.id, args.force);
3398
3522
  case 'wordpress_list_posts':
3399
- return await this.currentSite.listPosts(args);
3523
+ return await client.listPosts(args);
3400
3524
  case 'wordpress_read_post':
3401
- return await this.currentSite.getPost(args.id, args.include);
3525
+ return await client.getPost(args.id, args.include);
3402
3526
  case 'wordpress_create_post_duplicate':
3403
3527
  return {
3404
- ...(await this.currentSite.duplicatePost(args.original_id, args.suffix, args.include)),
3405
- respira_approvals_url: this.currentSite.getApprovalsUrl(),
3528
+ ...(await client.duplicatePost(args.original_id, args.suffix, args.include)),
3529
+ respira_approvals_url: client.getApprovalsUrl(),
3406
3530
  };
3407
3531
  case 'wordpress_update_post': {
3408
- const approvalsUrl = this.currentSite.getApprovalsUrl();
3409
- const post = await this.currentSite.updatePost(args.id, args);
3532
+ const approvalsUrl = client.getApprovalsUrl();
3533
+ const post = await client.updatePost(args.id, args);
3410
3534
  // Check if Respira created a duplicate
3411
3535
  if (post.__respira_duplicate_info) {
3412
3536
  const info = post.__respira_duplicate_info;
@@ -3427,27 +3551,31 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3427
3551
  };
3428
3552
  }
3429
3553
  case 'wordpress_delete_post':
3430
- return await this.currentSite.deletePost(args.id, args.force);
3554
+ return await client.deletePost(args.id, args.force);
3431
3555
  case 'wordpress_list_media':
3432
- return await this.currentSite.listMedia(args);
3556
+ return await client.listMedia(args);
3433
3557
  case 'wordpress_upload_media':
3434
- return await this.currentSite.uploadMedia(args.file, args.filename, args.mime_type, args.title, args.alt, args.caption);
3558
+ return await client.uploadMedia(args.file, args.filename, args.mime_type, args.title, args.alt, args.caption);
3435
3559
  case 'wordpress_extract_builder_content':
3436
- return await this.currentSite.extractBuilderContent(args.builder, args.page_id);
3560
+ return await client.extractBuilderContent(args.builder, args.page_id);
3437
3561
  case 'wordpress_find_builder_targets':
3438
- return await this.currentSite.findBuilderTargets(args.builder, args.page_id, args.query, args.limit);
3562
+ return await client.findBuilderTargets(args.builder, args.page_id, args.query, args.limit, args.offset);
3563
+ case 'wordpress_get_page_outline':
3564
+ return await client.getPageOutline(args.builder, args.page_id);
3565
+ case 'wordpress_get_builder_inline_schemas':
3566
+ return await client.getBuilderInlineSchemas(args.builder, args.types);
3439
3567
  case 'wordpress_inject_builder_content':
3440
3568
  return {
3441
- ...(await this.currentSite.injectBuilderContent(args.builder, args.page_id, args.content, args.divi_version, args.edit_target, args.mode, args.confirm_replace)),
3442
- respira_approvals_url: this.currentSite.getApprovalsUrl(),
3569
+ ...(await client.injectBuilderContent(args.builder, args.page_id, args.content, args.divi_version, args.edit_target, args.mode, args.confirm_replace)),
3570
+ respira_approvals_url: client.getApprovalsUrl(),
3443
3571
  };
3444
3572
  case 'wordpress_update_module':
3445
3573
  return {
3446
- ...(await this.currentSite.updateModule(args.builder, args.page_id, args.module_identifier, args.updates, args.edit_target)),
3447
- respira_approvals_url: this.currentSite.getApprovalsUrl(),
3574
+ ...(await client.updateModule(args.builder, args.page_id, args.module_identifier, args.updates, args.edit_target)),
3575
+ respira_approvals_url: client.getApprovalsUrl(),
3448
3576
  };
3449
3577
  case 'wordpress_validate_security':
3450
- return await this.currentSite.validateSecurity(args.content);
3578
+ return await client.validateSecurity(args.content);
3451
3579
  case 'wordpress_switch_site': {
3452
3580
  const newSite = this.sites.get(args.site_id);
3453
3581
  if (!newSite) {
@@ -3465,7 +3593,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3465
3593
  };
3466
3594
  }
3467
3595
  case 'wordpress_diagnose_connection':
3468
- return await this.currentSite.diagnoseConnection({ post_id: args.post_id });
3596
+ return await client.diagnoseConnection({ post_id: args.post_id });
3469
3597
  // Canonical name (normalizeToolName rewrites respira_* → wordpress_*
3470
3598
  // before this switch runs). The early-route guard in setRequestHandler
3471
3599
  // catches the redeem call before we ever land here when no site is
@@ -3475,245 +3603,245 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3475
3603
  return await this.redeemInstallToken(String(args.token || ''));
3476
3604
  // Page Speed Analysis
3477
3605
  case 'wordpress_analyze_performance':
3478
- return await this.currentSite.analyzePerformance(args.page_id);
3606
+ return await client.analyzePerformance(args.page_id);
3479
3607
  case 'wordpress_get_core_web_vitals':
3480
- return await this.currentSite.getCoreWebVitals(args.page_id);
3608
+ return await client.getCoreWebVitals(args.page_id);
3481
3609
  case 'wordpress_analyze_images':
3482
- return await this.currentSite.analyzeImages(args.page_id);
3610
+ return await client.analyzeImages(args.page_id);
3483
3611
  // SEO Analysis
3484
3612
  case 'wordpress_analyze_seo':
3485
- return await this.currentSite.analyzeSEO(args.page_id);
3613
+ return await client.analyzeSEO(args.page_id);
3486
3614
  case 'wordpress_check_seo_issues':
3487
- return await this.currentSite.checkSEOIssues(args.page_id);
3615
+ return await client.checkSEOIssues(args.page_id);
3488
3616
  case 'wordpress_analyze_readability':
3489
- return await this.currentSite.analyzeReadability(args.page_id);
3617
+ return await client.analyzeReadability(args.page_id);
3490
3618
  case 'wordpress_analyze_rankmath':
3491
- return await this.currentSite.analyzeRankMath(args.post_id);
3619
+ return await client.analyzeRankMath(args.post_id);
3492
3620
  // AEO Analysis
3493
3621
  case 'wordpress_analyze_aeo':
3494
- return await this.currentSite.analyzeAEO(args.page_id);
3622
+ return await client.analyzeAEO(args.page_id);
3495
3623
  case 'wordpress_check_structured_data':
3496
- return await this.currentSite.checkStructuredData(args.page_id);
3624
+ return await client.checkStructuredData(args.page_id);
3497
3625
  // Accessibility
3498
3626
  case 'wordpress_list_accessibility_scans':
3499
- return await this.currentSite.listAccessibilityScans();
3627
+ return await client.listAccessibilityScans();
3500
3628
  case 'wordpress_get_accessibility_scan':
3501
- return await this.currentSite.getAccessibilityScan(args.scan_id);
3629
+ return await client.getAccessibilityScan(args.scan_id);
3502
3630
  case 'wordpress_scan_page_accessibility':
3503
- return await this.currentSite.scanPageAccessibility(args.page_id, args.standard);
3631
+ return await client.scanPageAccessibility(args.page_id, args.standard);
3504
3632
  case 'wordpress_apply_accessibility_fixes':
3505
- return await this.currentSite.applyAccessibilityFixes(args.scan_id, args.rule_ids);
3633
+ return await client.applyAccessibilityFixes(args.scan_id, args.rule_ids);
3506
3634
  // Plugin Management (EXPERIMENTAL)
3507
3635
  case 'wordpress_list_plugins':
3508
- return await this.currentSite.listPlugins();
3636
+ return await client.listPlugins();
3509
3637
  case 'wordpress_install_plugin':
3510
- return await this.currentSite.installPlugin(args.slug_or_url, args.source || 'wordpress.org', args.approval_token);
3638
+ return await client.installPlugin(args.slug_or_url, args.source || 'wordpress.org', args.approval_token);
3511
3639
  case 'wordpress_activate_plugin':
3512
- return await this.currentSite.activatePlugin(args.slug, args.approval_token);
3640
+ return await client.activatePlugin(args.slug, args.approval_token);
3513
3641
  case 'wordpress_deactivate_plugin':
3514
- return await this.currentSite.deactivatePlugin(args.slug, args.approval_token);
3642
+ return await client.deactivatePlugin(args.slug, args.approval_token);
3515
3643
  case 'wordpress_update_plugin':
3516
- return await this.currentSite.updatePlugin(args.slug, args.approval_token);
3644
+ return await client.updatePlugin(args.slug, args.approval_token);
3517
3645
  case 'wordpress_delete_plugin':
3518
- return await this.currentSite.deletePlugin(args.slug, args.approval_token);
3646
+ return await client.deletePlugin(args.slug, args.approval_token);
3519
3647
  // Users Management
3520
3648
  case 'wordpress_list_users':
3521
- return await this.currentSite.listUsers(args);
3649
+ return await client.listUsers(args);
3522
3650
  case 'wordpress_get_user':
3523
- return await this.currentSite.getUser(args.id);
3651
+ return await client.getUser(args.id);
3524
3652
  case 'wordpress_create_user':
3525
- return await this.currentSite.createUser(args);
3653
+ return await client.createUser(args);
3526
3654
  case 'wordpress_update_user':
3527
- return await this.currentSite.updateUser(args.id, args);
3655
+ return await client.updateUser(args.id, args);
3528
3656
  case 'wordpress_delete_user':
3529
- return await this.currentSite.deleteUser(args.id, args.reassign);
3657
+ return await client.deleteUser(args.id, args.reassign);
3530
3658
  // Comments
3531
3659
  case 'wordpress_list_comments':
3532
- return await this.currentSite.listComments(args);
3660
+ return await client.listComments(args);
3533
3661
  case 'wordpress_get_comment':
3534
- return await this.currentSite.getComment(args.id);
3662
+ return await client.getComment(args.id);
3535
3663
  case 'wordpress_create_comment':
3536
- return await this.currentSite.createComment(args);
3664
+ return await client.createComment(args);
3537
3665
  case 'wordpress_update_comment':
3538
- return await this.currentSite.updateComment(args.id, args);
3666
+ return await client.updateComment(args.id, args);
3539
3667
  case 'wordpress_delete_comment':
3540
- return await this.currentSite.deleteComment(args.id);
3668
+ return await client.deleteComment(args.id);
3541
3669
  // Taxonomies
3542
3670
  case 'wordpress_list_taxonomies':
3543
- return await this.currentSite.listTaxonomies();
3671
+ return await client.listTaxonomies();
3544
3672
  case 'wordpress_get_taxonomy':
3545
- return await this.currentSite.getTaxonomy(args.taxonomy);
3673
+ return await client.getTaxonomy(args.taxonomy);
3546
3674
  case 'wordpress_list_terms':
3547
- return await this.currentSite.listTerms(args.taxonomy, args);
3675
+ return await client.listTerms(args.taxonomy, args);
3548
3676
  case 'wordpress_get_term':
3549
- return await this.currentSite.getTerm(args.taxonomy, args.id);
3677
+ return await client.getTerm(args.taxonomy, args.id);
3550
3678
  case 'wordpress_create_term':
3551
- return await this.currentSite.createTerm(args.taxonomy, args);
3679
+ return await client.createTerm(args.taxonomy, args);
3552
3680
  case 'wordpress_update_term':
3553
- return await this.currentSite.updateTerm(args.taxonomy, args.id, args);
3681
+ return await client.updateTerm(args.taxonomy, args.id, args);
3554
3682
  case 'wordpress_delete_term':
3555
- return await this.currentSite.deleteTerm(args.taxonomy, args.id);
3683
+ return await client.deleteTerm(args.taxonomy, args.id);
3556
3684
  // Custom Post Types
3557
3685
  case 'wordpress_list_post_types':
3558
- return await this.currentSite.listPostTypes();
3686
+ return await client.listPostTypes();
3559
3687
  case 'wordpress_get_post_type':
3560
- return await this.currentSite.getPostType(args.type);
3688
+ return await client.getPostType(args.type);
3561
3689
  case 'wordpress_list_custom_posts':
3562
- return await this.currentSite.listCustomPosts(args.type, args);
3690
+ return await client.listCustomPosts(args.type, args);
3563
3691
  case 'wordpress_get_custom_post':
3564
- return await this.currentSite.getCustomPost(args.type, args.id, args.include);
3692
+ return await client.getCustomPost(args.type, args.id, args.include);
3565
3693
  case 'wordpress_create_custom_post':
3566
- return await this.currentSite.createCustomPost(args.type, args);
3694
+ return await client.createCustomPost(args.type, args);
3567
3695
  case 'wordpress_update_custom_post':
3568
- return await this.currentSite.updateCustomPost(args.type, args.id, args);
3696
+ return await client.updateCustomPost(args.type, args.id, args);
3569
3697
  case 'wordpress_delete_custom_post':
3570
- return await this.currentSite.deleteCustomPost(args.type, args.id);
3698
+ return await client.deleteCustomPost(args.type, args.id);
3571
3699
  // Options
3572
3700
  case 'wordpress_list_options':
3573
- return await this.currentSite.listOptions(args.search);
3701
+ return await client.listOptions(args.search);
3574
3702
  case 'wordpress_get_option':
3575
- return await this.currentSite.getOption(args.option);
3703
+ return await client.getOption(args.option);
3576
3704
  case 'wordpress_update_option':
3577
- return await this.currentSite.updateOption(args.option, args.value);
3705
+ return await client.updateOption(args.option, args.value);
3578
3706
  case 'wordpress_delete_option':
3579
- return await this.currentSite.deleteOption(args.option);
3707
+ return await client.deleteOption(args.option);
3580
3708
  // Media enhancements
3581
3709
  case 'wordpress_get_media':
3582
- return await this.currentSite.getMedia(args.id);
3710
+ return await client.getMedia(args.id);
3583
3711
  case 'wordpress_update_media':
3584
- return await this.currentSite.updateMedia(args.id, args);
3712
+ return await client.updateMedia(args.id, args);
3585
3713
  case 'wordpress_update_media_batch':
3586
- return await this.currentSite.updateMediaBatch(args.items);
3714
+ return await client.updateMediaBatch(args.items);
3587
3715
  case 'wordpress_delete_media':
3588
- return await this.currentSite.deleteMedia(args.id);
3716
+ return await client.deleteMedia(args.id);
3589
3717
  // Menu Management
3590
3718
  case 'wordpress_list_menus':
3591
- return await this.currentSite.listMenus();
3719
+ return await client.listMenus();
3592
3720
  case 'wordpress_get_menu':
3593
- return await this.currentSite.getMenu(args.id);
3721
+ return await client.getMenu(args.id);
3594
3722
  case 'wordpress_create_menu':
3595
- return await this.currentSite.createMenu(args);
3723
+ return await client.createMenu(args);
3596
3724
  case 'wordpress_update_menu':
3597
- return await this.currentSite.updateMenu(args.id, args);
3725
+ return await client.updateMenu(args.id, args);
3598
3726
  case 'wordpress_delete_menu':
3599
- return await this.currentSite.deleteMenu(args.id);
3727
+ return await client.deleteMenu(args.id);
3600
3728
  // Menu Locations
3601
3729
  case 'wordpress_list_menu_locations':
3602
- return await this.currentSite.listMenuLocations();
3730
+ return await client.listMenuLocations();
3603
3731
  case 'wordpress_assign_menu_location':
3604
- return await this.currentSite.assignMenuToLocation(args.location, args.menu_id);
3732
+ return await client.assignMenuToLocation(args.location, args.menu_id);
3605
3733
  // Menu Items
3606
3734
  case 'wordpress_list_menu_items':
3607
- return await this.currentSite.listMenuItems(args.menu_id);
3735
+ return await client.listMenuItems(args.menu_id);
3608
3736
  case 'wordpress_create_menu_item':
3609
- return await this.currentSite.createMenuItem(args.menu_id, args);
3737
+ return await client.createMenuItem(args.menu_id, args);
3610
3738
  case 'wordpress_get_menu_item':
3611
- return await this.currentSite.getMenuItem(args.item_id);
3739
+ return await client.getMenuItem(args.item_id);
3612
3740
  case 'wordpress_update_menu_item':
3613
- return await this.currentSite.updateMenuItem(args.item_id, args);
3741
+ return await client.updateMenuItem(args.item_id, args);
3614
3742
  case 'wordpress_delete_menu_item':
3615
- return await this.currentSite.deleteMenuItem(args.item_id);
3743
+ return await client.deleteMenuItem(args.item_id);
3616
3744
  case 'wordpress_list_snapshots':
3617
- return await this.currentSite.listSnapshots(args);
3745
+ return await client.listSnapshots(args);
3618
3746
  case 'wordpress_get_snapshot':
3619
- return await this.currentSite.getSnapshot(args.snapshot_uuid);
3747
+ return await client.getSnapshot(args.snapshot_uuid);
3620
3748
  case 'wordpress_diff_snapshots':
3621
- return await this.currentSite.diffSnapshots(args.snapshot_uuid_a, args.snapshot_uuid_b);
3749
+ return await client.diffSnapshots(args.snapshot_uuid_a, args.snapshot_uuid_b);
3622
3750
  case 'wordpress_restore_snapshot':
3623
- return await this.currentSite.restoreSnapshot(args.snapshot_uuid);
3751
+ return await client.restoreSnapshot(args.snapshot_uuid);
3624
3752
  case 'wordpress_apply_builder_patch':
3625
- return await this.currentSite.applyBuilderPatch(args.builder, args.post_id, args.operations, args.include, args.edit_target);
3753
+ return await client.applyBuilderPatch(args.builder, args.post_id, args.operations, args.include, args.edit_target);
3626
3754
  case 'woocommerce_list_products':
3627
- return await this.currentSite.woocommerceListProducts(args);
3755
+ return await client.woocommerceListProducts(args);
3628
3756
  case 'woocommerce_get_product':
3629
- return await this.currentSite.woocommerceGetProduct(args.id);
3757
+ return await client.woocommerceGetProduct(args.id);
3630
3758
  case 'woocommerce_create_product':
3631
- return await this.currentSite.woocommerceCreateProduct(args);
3759
+ return await client.woocommerceCreateProduct(args);
3632
3760
  case 'woocommerce_update_product': {
3633
3761
  const { id, ...payload } = args;
3634
- return await this.currentSite.woocommerceUpdateProduct(id, payload);
3762
+ return await client.woocommerceUpdateProduct(id, payload);
3635
3763
  }
3636
3764
  case 'woocommerce_duplicate_product':
3637
- return await this.currentSite.woocommerceDuplicateProduct(args.id);
3765
+ return await client.woocommerceDuplicateProduct(args.id);
3638
3766
  case 'woocommerce_list_categories':
3639
- return await this.currentSite.woocommerceListCategories(args);
3767
+ return await client.woocommerceListCategories(args);
3640
3768
  case 'woocommerce_get_category':
3641
- return await this.currentSite.woocommerceGetCategory(args.id);
3769
+ return await client.woocommerceGetCategory(args.id);
3642
3770
  case 'woocommerce_create_category':
3643
- return await this.currentSite.woocommerceCreateCategory(args);
3771
+ return await client.woocommerceCreateCategory(args);
3644
3772
  case 'woocommerce_update_category': {
3645
3773
  const { id, ...payload } = args;
3646
- return await this.currentSite.woocommerceUpdateCategory(id, payload);
3774
+ return await client.woocommerceUpdateCategory(id, payload);
3647
3775
  }
3648
3776
  case 'woocommerce_delete_category':
3649
- return await this.currentSite.woocommerceDeleteCategory(args.id);
3777
+ return await client.woocommerceDeleteCategory(args.id);
3650
3778
  case 'woocommerce_list_tags':
3651
- return await this.currentSite.woocommerceListTags(args);
3779
+ return await client.woocommerceListTags(args);
3652
3780
  case 'woocommerce_get_tag':
3653
- return await this.currentSite.woocommerceGetTag(args.id);
3781
+ return await client.woocommerceGetTag(args.id);
3654
3782
  case 'woocommerce_create_tag':
3655
- return await this.currentSite.woocommerceCreateTag(args);
3783
+ return await client.woocommerceCreateTag(args);
3656
3784
  case 'woocommerce_update_tag': {
3657
3785
  const { id, ...payload } = args;
3658
- return await this.currentSite.woocommerceUpdateTag(id, payload);
3786
+ return await client.woocommerceUpdateTag(id, payload);
3659
3787
  }
3660
3788
  case 'woocommerce_delete_tag':
3661
- return await this.currentSite.woocommerceDeleteTag(args.id);
3789
+ return await client.woocommerceDeleteTag(args.id);
3662
3790
  case 'woocommerce_list_orders':
3663
- return await this.currentSite.woocommerceListOrders(args);
3791
+ return await client.woocommerceListOrders(args);
3664
3792
  case 'woocommerce_get_order':
3665
- return await this.currentSite.woocommerceGetOrder(args.id);
3793
+ return await client.woocommerceGetOrder(args.id);
3666
3794
  case 'woocommerce_update_order_status':
3667
- return await this.currentSite.woocommerceUpdateOrderStatus(args.id, args.status);
3795
+ return await client.woocommerceUpdateOrderStatus(args.id, args.status);
3668
3796
  case 'woocommerce_get_stock_status':
3669
- return await this.currentSite.woocommerceGetStockStatus();
3797
+ return await client.woocommerceGetStockStatus();
3670
3798
  case 'woocommerce_update_stock': {
3671
3799
  const { id, ...payload } = args;
3672
- return await this.currentSite.woocommerceUpdateStock(id, payload);
3800
+ return await client.woocommerceUpdateStock(id, payload);
3673
3801
  }
3674
3802
  case 'woocommerce_sales_report':
3675
- return await this.currentSite.woocommerceSalesReport(args);
3803
+ return await client.woocommerceSalesReport(args);
3676
3804
  // --- v5.2.0 Elemental tools ---
3677
3805
  case 'wordpress_find_element': {
3678
3806
  const { post_id, ...rest } = args;
3679
- return await this.currentSite.callRestV2('POST', `/builder/elements/find/${post_id}`, rest);
3807
+ return await client.callRestV2('POST', `/builder/elements/find/${post_id}`, rest);
3680
3808
  }
3681
3809
  case 'wordpress_update_element': {
3682
3810
  const { post_id, ...rest } = args;
3683
- return await this.currentSite.callRestV2('POST', `/builder/elements/update/${post_id}`, rest);
3811
+ return await client.callRestV2('POST', `/builder/elements/update/${post_id}`, rest);
3684
3812
  }
3685
3813
  case 'wordpress_move_element': {
3686
3814
  const { post_id, ...rest } = args;
3687
- return await this.currentSite.callRestV2('POST', `/builder/elements/move/${post_id}`, rest);
3815
+ return await client.callRestV2('POST', `/builder/elements/move/${post_id}`, rest);
3688
3816
  }
3689
3817
  case 'wordpress_duplicate_element': {
3690
3818
  const { post_id, ...rest } = args;
3691
- return await this.currentSite.callRestV2('POST', `/builder/elements/duplicate/${post_id}`, rest);
3819
+ return await client.callRestV2('POST', `/builder/elements/duplicate/${post_id}`, rest);
3692
3820
  }
3693
3821
  case 'wordpress_remove_element': {
3694
3822
  const { post_id, ...rest } = args;
3695
- return await this.currentSite.callRestV2('POST', `/builder/elements/remove/${post_id}`, rest);
3823
+ return await client.callRestV2('POST', `/builder/elements/remove/${post_id}`, rest);
3696
3824
  }
3697
3825
  case 'wordpress_batch_update': {
3698
3826
  const { post_id, ...rest } = args;
3699
- return await this.currentSite.callRestV2('POST', `/builder/elements/batch/${post_id}`, rest);
3827
+ return await client.callRestV2('POST', `/builder/elements/batch/${post_id}`, rest);
3700
3828
  }
3701
3829
  case 'wordpress_reorder_elements': {
3702
3830
  const { post_id, ...rest } = args;
3703
- return await this.currentSite.callRestV2('POST', `/builder/elements/reorder/${post_id}`, rest);
3831
+ return await client.callRestV2('POST', `/builder/elements/reorder/${post_id}`, rest);
3704
3832
  }
3705
3833
  case 'wordpress_build_page':
3706
- return await this.currentSite.callRestV2('POST', '/builder/build', args);
3834
+ return await client.callRestV2('POST', '/builder/build', args);
3707
3835
  case 'wordpress_convert_html_to_builder':
3708
- return await this.currentSite.callRestV2('POST', '/builder/convert', args);
3836
+ return await client.callRestV2('POST', '/builder/convert', args);
3709
3837
  case 'wordpress_bulk_pages_operation':
3710
- return await this.currentSite.callRestV2('POST', '/builder/bulk', args);
3838
+ return await client.callRestV2('POST', '/builder/bulk', args);
3711
3839
  case 'wordpress_search_stock_images':
3712
- return await this.currentSite.callRestV2('GET', '/stock-images/search', args);
3840
+ return await client.callRestV2('GET', '/stock-images/search', args);
3713
3841
  case 'wordpress_sideload_image':
3714
- return await this.currentSite.callRestV2('POST', '/stock-images/sideload', args);
3842
+ return await client.callRestV2('POST', '/stock-images/sideload', args);
3715
3843
  case 'wordpress_get_server_compatibility':
3716
- return await this.currentSite.callRestV2('GET', '/server/compatibility');
3844
+ return await client.callRestV2('GET', '/server/compatibility');
3717
3845
  // Widget shortcuts and Bricks tools — dynamic dispatch.
3718
3846
  default: {
3719
3847
  // Check if this is a Bricks deep tool.
@@ -3745,7 +3873,7 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
3745
3873
  throw new Error(`Widget shortcut "${shortcutName}" requires a post_id parameter.`);
3746
3874
  }
3747
3875
  const { post_id: _pid, ...settings } = args;
3748
- return await this.currentSite.callRestV2('POST', `/builder/widget/${shortcutName}/${postId}`, settings);
3876
+ return await client.callRestV2('POST', `/builder/widget/${shortcutName}/${postId}`, settings);
3749
3877
  }
3750
3878
  // Return isError result instead of throwing — unknown tool is an execution
3751
3879
  // error, not a protocol error. This lets LLMs self-correct gracefully.