@respira/wordpress-mcp-server 6.19.2 → 6.19.4

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.
@@ -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;IAyIzB;;;;;;;;;OASG;YACW,kBAAkB;IA6FhC,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,aAAa;YAuNP,kBAAkB;YA6BlB,yBAAyB;IASvC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;YAyBd,QAAQ;IAg/EtB;;;;;;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;IA0sB9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAgSzB;;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;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;IAyIzB;;;;;;;;;OASG;YACW,kBAAkB;IA6FhC,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,aAAa;YAuOP,kBAAkB;YA6BlB,yBAAyB;IASvC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;YAyBd,QAAQ;IA28EtB;;;;;;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;IAsvB9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA+SzB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA6UxB,GAAG;CAyCV"}
package/dist/server.js CHANGED
@@ -968,13 +968,30 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
968
968
  }
969
969
  // Per-call watchdog: a single hung handler must never wedge the stdio
970
970
  // loop for every subsequent tool call. If the deadline fires, the race
971
- // rejects with ToolTimeoutError; the losing handler keeps running in
972
- // the background but the client gets a clean structured error and the
973
- // next tool call is free to proceed.
971
+ // rejects with ToolTimeoutError, AND we destroy the WP client's HTTP
972
+ // agents to free the orphaned socket so the next tool call doesn't
973
+ // queue behind it at the agent layer. v6.19.3 bug fc2bfdf3 — Mauricio
974
+ // Piper (Fischers Fritze) reported that after one tool deadlocked
975
+ // every subsequent call, including diagnose_connection, also hung 4
976
+ // min; only killing Claude Desktop recovered. Per-client agents (now
977
+ // bounded at maxSockets:16, keepAlive:false) plus this destroy-on-
978
+ // trip make orphans cost at most one socket for at most the axios
979
+ // 30s response timeout, instead of indefinitely blocking the pool.
974
980
  const toolTimeoutMs = getMaxToolTimeoutMs();
975
981
  let timeoutHandle;
982
+ const siteForAbort = this.currentSite;
976
983
  const toolTimeout = new Promise((_, reject) => {
977
- timeoutHandle = setTimeout(() => reject(new ToolTimeoutError(name, toolTimeoutMs)), toolTimeoutMs);
984
+ timeoutHandle = setTimeout(() => {
985
+ try {
986
+ if (siteForAbort && typeof siteForAbort.abortInFlightRequests === 'function') {
987
+ siteForAbort.abortInFlightRequests();
988
+ }
989
+ }
990
+ catch {
991
+ // Don't block the rejection on cleanup.
992
+ }
993
+ reject(new ToolTimeoutError(name, toolTimeoutMs));
994
+ }, toolTimeoutMs);
978
995
  });
979
996
  try {
980
997
  this.currentSite.setCurrentToolName(name);
@@ -1254,42 +1271,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
1254
1271
  },
1255
1272
  readOnlyHint: true,
1256
1273
  },
1257
- {
1258
- name: 'wordpress_search_abilities',
1259
- description: 'SEARCH the curated WordPress abilities directory across all known plugins (Elementor, Yoast SEO, WooCommerce, Jetpack, ACF, Akismet, plus Respira\'s own 151 tools — 163 total today, growing weekly via auto-pull). ' +
1260
- 'Use this whenever the user asks "is there an ability that does X", "can WordPress do Y", "what plugins expose Z as MCP", "how do i automate W on my site". ' +
1261
- 'Each result is enriched with per-site context: is_installed (is the plugin active on THIS site), is_inhaled (has the admin opted the ability into Respira\'s MCP surface), install_url (wp.org page when not installed), inhale_admin_url (Respira admin tab when installed but not inhaled), and how_to_invoke (the exact tool call to make when ready). ' +
1262
- 'After finding a matching ability, if is_inhaled is true you can call it via wordpress_invoke_ability. Otherwise surface install_url or inhale_admin_url to the user so they can opt in. ' +
1263
- 'No auth gates: this surface is open to every agent connected to Respira. The directory itself is global; the enrichment is per-site.',
1264
- inputSchema: {
1265
- type: 'object',
1266
- properties: {
1267
- q: {
1268
- type: 'string',
1269
- description: 'Free-text query — matches against ability_name, label, description, plugin_name, category. Optional; an empty query returns the top of the directory.',
1270
- },
1271
- plugin: {
1272
- type: 'string',
1273
- description: 'Restrict to a single plugin by wp.org slug (e.g. "wordpress-seo") or display name ("Yoast SEO"). Optional.',
1274
- },
1275
- category: {
1276
- type: 'string',
1277
- description: 'Restrict to a single category (e.g. "SEO", "E-commerce", "Page Builder", "Custom Fields"). Optional.',
1278
- },
1279
- kind: {
1280
- type: 'string',
1281
- enum: ['read', 'write'],
1282
- description: 'Restrict to read-only or write abilities. Optional.',
1283
- },
1284
- limit: {
1285
- type: 'integer',
1286
- description: 'Max results to return (default 20, max 100).',
1287
- default: 20,
1288
- },
1289
- },
1290
- },
1291
- readOnlyHint: true,
1292
- },
1293
1274
  {
1294
1275
  name: 'wordpress_invoke_ability',
1295
1276
  description: 'Invoke an inhaled WordPress Abilities API ability through the Respira safety wrapper. ' +
@@ -4379,14 +4360,6 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4379
4360
  return await client.getDiviMigrationReadiness();
4380
4361
  case 'wordpress_abilities_gap_report':
4381
4362
  return await client.getAbilitiesGapReport();
4382
- case 'wordpress_search_abilities':
4383
- return await client.searchAbilities({
4384
- q: args?.q,
4385
- plugin: args?.plugin,
4386
- category: args?.category,
4387
- kind: args?.kind,
4388
- limit: args?.limit,
4389
- });
4390
4363
  case 'wordpress_invoke_ability':
4391
4364
  return await client.invokeInhaledAbility(args?.ability, args?.args || {});
4392
4365
  case 'wordpress_list_pages':
@@ -4464,8 +4437,40 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4464
4437
  return await client.deletePost(args.id, args.force);
4465
4438
  case 'wordpress_list_media':
4466
4439
  return await client.listMedia(args);
4467
- case 'wordpress_upload_media':
4440
+ case 'wordpress_upload_media': {
4441
+ // v6.19.4: pre-flight size + transport check before the upload.
4442
+ // The MCP-quality dashboard showed upload_media at 50.7% success,
4443
+ // p95 = 120 seconds, 34.8% of calls timing out >60s — meaning
4444
+ // the watchdog was firing on every other call. Almost all of the
4445
+ // long tail is the agent passing a base64 payload of a large
4446
+ // image without realising the per-call timeout will kill it.
4447
+ // Surface a clear "too large for one-shot upload, use chunked
4448
+ // workflow" error BEFORE the upload starts instead of waiting
4449
+ // 120 seconds to fail.
4450
+ const sizeBytes = typeof args.file === 'string' ? Math.floor(args.file.length * 0.75) : 0;
4451
+ const MAX_SAFE_BYTES = 5 * 1024 * 1024; // 5MB after base64 → ~3.75MB binary
4452
+ const HARD_LIMIT_BYTES = 25 * 1024 * 1024; // 25MB → server-side rejected
4453
+ if (sizeBytes > HARD_LIMIT_BYTES) {
4454
+ return {
4455
+ success: false,
4456
+ error: 'file_too_large',
4457
+ message: `Refused to upload: payload is ~${(sizeBytes / 1024 / 1024).toFixed(1)}MB after base64 decode, hard limit is ${HARD_LIMIT_BYTES / 1024 / 1024}MB per call. WordPress would reject this and your call would time out at ~2 minutes. Resize the image client-side (recommended: 2000px max dimension, <2MB) and retry. For PDFs and other documents, the operator should upload manually via wp-admin Media Library.`,
4458
+ preflight: { size_bytes: sizeBytes, hard_limit_bytes: HARD_LIMIT_BYTES, recommended_max_bytes: MAX_SAFE_BYTES },
4459
+ };
4460
+ }
4461
+ if (sizeBytes > MAX_SAFE_BYTES) {
4462
+ // Soft warning — still attempt the upload, but tell the agent
4463
+ // up front this is high-risk for timeout so it can plan around
4464
+ // it (e.g. set expectations with the user, retry on a smaller
4465
+ // crop, etc) instead of looping.
4466
+ const r = await client.uploadMedia(args.file, args.filename, args.mime_type, args.title, args.alt, args.caption);
4467
+ if (r && typeof r === 'object') {
4468
+ r.preflight_warning = `Large payload (~${(sizeBytes / 1024 / 1024).toFixed(1)}MB after decode). Uploads above ${MAX_SAFE_BYTES / 1024 / 1024}MB succeed in ~50% of calls in current telemetry due to per-call watchdog timeout on slow hosts. If this upload times out, ask the user to crop / compress the image client-side before retrying.`;
4469
+ }
4470
+ return r;
4471
+ }
4468
4472
  return await client.uploadMedia(args.file, args.filename, args.mime_type, args.title, args.alt, args.caption);
4473
+ }
4469
4474
  case 'wordpress_extract_builder_content':
4470
4475
  return await client.extractBuilderContent(args.builder, args.page_id);
4471
4476
  case 'wordpress_find_builder_targets':
@@ -4788,8 +4793,29 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4788
4793
  return await client.callRestV2('POST', `/builder/elements/find/${post_id}`, rest);
4789
4794
  }
4790
4795
  case 'wordpress_update_element': {
4796
+ // v6.19.4: surface the new plugin-side write_was_noop warning
4797
+ // prominently in the tool result. The plugin v7.0.61+ adds
4798
+ // `write_diag` + `warning` keys to the response when post_content
4799
+ // didn't change despite a successful write. Hoist them to the
4800
+ // top of the response payload (above `changes`) and prepend the
4801
+ // warning_hint to the message so the agent sees it before any
4802
+ // other content. The dashboard shows 81% in-session retry rate
4803
+ // on this tool — exactly the silent-fail signature where agents
4804
+ // ignore deeply-nested signals and just retry. Front-load.
4791
4805
  const { post_id, ...rest } = args;
4792
- return await client.callRestV2('POST', `/builder/elements/update/${post_id}`, rest);
4806
+ const r = await client.callRestV2('POST', `/builder/elements/update/${post_id}`, rest);
4807
+ if (r && typeof r === 'object' && r.warning === 'write_was_noop') {
4808
+ const hint = r.warning_hint || 'Write succeeded at the API layer but post_content did not change. Verify with respira_extract_builder_content and escalate to the user instead of retrying.';
4809
+ const original = typeof r.message === 'string' ? r.message : '';
4810
+ r.message = `⚠️ NO-OP DETECTED — ${hint}\n\nOriginal message: ${original}`.trim();
4811
+ // Also surface as a top-level error-shaped field so MCP clients
4812
+ // that branch on `r.success === false` don't silently treat
4813
+ // this as a clean success. Keep `success: true` so existing
4814
+ // call sites don't break, but make the agent-visible payload
4815
+ // unambiguous.
4816
+ r.agent_must_escalate = true;
4817
+ }
4818
+ return r;
4793
4819
  }
4794
4820
  case 'wordpress_move_element': {
4795
4821
  const { post_id, ...rest } = args;
@@ -4921,22 +4947,30 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
4921
4947
  },
4922
4948
  {
4923
4949
  name: 'wordpress_move_element',
4924
- description: 'Move an element to a different container or position within the page.',
4950
+ description: 'Move an element to a different container or position within the page. Locate the element using the same identifier_type/identifier_value pattern as respira_find_element / respira_remove_element. The destination is a container PATH (e.g. "0.1" for sections[0].rows[1], or "root"/empty for top level), not a container element id.',
4925
4951
  inputSchema: {
4926
4952
  type: 'object',
4927
4953
  properties: {
4928
4954
  post_id: { type: 'number', description: 'Page/post ID' },
4929
- element_id: { type: 'string', description: 'Element ID to move' },
4930
- target_container_id: {
4955
+ identifier_type: {
4956
+ type: 'string',
4957
+ enum: ['id', 'css_class', 'text', 'widget_type', 'global_id', 'admin_label', 'type', 'path'],
4958
+ description: 'How to locate the element to move (same enum as respira_find_element).',
4959
+ },
4960
+ identifier_value: {
4931
4961
  type: 'string',
4932
- description: 'ID of the container to move into',
4962
+ description: 'Value matching identifier_type.',
4963
+ },
4964
+ target_container_path: {
4965
+ type: 'string',
4966
+ description: 'Dot-separated container path inside the target page (e.g. "0.1.0"). Pass "root" or "" to move to the top level.',
4933
4967
  },
4934
4968
  position: {
4935
4969
  type: 'number',
4936
- description: 'Position index within the target container (0-based)',
4970
+ description: 'Position index within the target container (0-based). Pass -1 to append.',
4937
4971
  },
4938
4972
  },
4939
- required: ['post_id', 'element_id', 'target_container_id', 'position'],
4973
+ required: ['post_id', 'identifier_type', 'identifier_value', 'target_container_path', 'position'],
4940
4974
  },
4941
4975
  },
4942
4976
  {
@@ -5002,19 +5036,22 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
5002
5036
  },
5003
5037
  {
5004
5038
  name: 'wordpress_reorder_elements',
5005
- description: 'Reorder child elements within a container.',
5039
+ description: 'Reorder child elements within a container. The container is identified by its PATH inside the page (e.g. "0.1" for sections[0].rows[1], or "root"/"" for the top level).',
5006
5040
  inputSchema: {
5007
5041
  type: 'object',
5008
5042
  properties: {
5009
5043
  post_id: { type: 'number', description: 'Page/post ID' },
5010
- container_id: { type: 'string', description: 'Container element ID' },
5044
+ container_path: {
5045
+ type: 'string',
5046
+ description: 'Dot-separated container path (e.g. "0.1.0"). Pass "root" or "" for the top-level container.',
5047
+ },
5011
5048
  new_order: {
5012
5049
  type: 'array',
5013
- description: 'Array of element IDs in the desired order',
5050
+ description: 'Array of element IDs in the desired order.',
5014
5051
  items: { type: 'string' },
5015
5052
  },
5016
5053
  },
5017
- required: ['post_id', 'container_id', 'new_order'],
5054
+ required: ['post_id', 'container_path', 'new_order'],
5018
5055
  },
5019
5056
  },
5020
5057
  // --- Composite Tools ---