@respira/wordpress-mcp-server 7.5.3 → 7.5.5
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/README.md +24 -1
- package/dist/__tests__/config-subdirectory-sites.test.d.ts +2 -0
- package/dist/__tests__/config-subdirectory-sites.test.d.ts.map +1 -0
- package/dist/__tests__/config-subdirectory-sites.test.js +44 -0
- package/dist/__tests__/config-subdirectory-sites.test.js.map +1 -0
- package/dist/__tests__/tool-filter-context-cache.test.d.ts +2 -0
- package/dist/__tests__/tool-filter-context-cache.test.d.ts.map +1 -0
- package/dist/__tests__/tool-filter-context-cache.test.js +69 -0
- package/dist/__tests__/tool-filter-context-cache.test.js.map +1 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +89 -21
- package/dist/config.js.map +1 -1
- package/dist/elementor-tools.js +2 -2
- package/dist/elementor-tools.js.map +1 -1
- package/dist/server.d.ts +30 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +74 -28
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -428,6 +428,8 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
|
|
|
428
428
|
- Content format: Array of sections containing columns containing widgets
|
|
429
429
|
- Each widget has a widgetType (e.g. "heading", "text-editor", "image", "button") and settings object
|
|
430
430
|
- Dynamic schemas: Respira reads Elementor's control registry at runtime, so it knows the exact settings, types, and valid values for every widget
|
|
431
|
+
- This includes registered third-party/theme widgets. For an existing custom widget: list_elementor_widgets -> get_elementor_widget_schema -> find_element by content/type -> update_element by the returned stable id with only changed settings -> find_element again to verify
|
|
432
|
+
- Never use extract_builder_content + inject_builder_content or rewrite _elementor_data for a single-widget edit. Widget source code is only a fallback when its rendering semantics are not represented in the registered controls
|
|
431
433
|
- Settings validator catches typos: "Unknown setting 'titel'. Did you mean 'title'?"
|
|
432
434
|
- Responsive: settings support _mobile and _tablet suffixes (e.g. "align" for desktop, "align_mobile" for mobile)
|
|
433
435
|
- respira_find_element works with Elementor widget IDs, types, CSS classes, and text content
|
|
@@ -4380,26 +4382,80 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4380
4382
|
* Widget shortcuts are filtered to only the detected builder's element types.
|
|
4381
4383
|
* Core tools (site context, pages, posts, media, menus, etc.) always remain.
|
|
4382
4384
|
*/
|
|
4383
|
-
/** Cached site context
|
|
4385
|
+
/** Cached site context, shared by tool filtering and the WooCommerce/ACF
|
|
4386
|
+
* availability checks below — all three used to independently call
|
|
4387
|
+
* getSiteContext() (getCompactSiteContext() itself just wraps it), meaning
|
|
4388
|
+
* every getTools() invocation made up to three uncached, sequential live
|
|
4389
|
+
* HTTP calls to the customer's own WordPress site before it could return
|
|
4390
|
+
* the tool catalog. Each call carries the wordpress-client retry backoff
|
|
4391
|
+
* (200/600/1800ms across 3 attempts), so a slow or failing site could push
|
|
4392
|
+
* a single tools/list response past 7s — long enough for an MCP client's
|
|
4393
|
+
* own handshake patience to give up and tear down the process, which
|
|
4394
|
+
* looks identical to a crash with zero stderr output (nathankohut.co.uk,
|
|
4395
|
+
* Windows, 2026-07-19). getContextForToolFiltering() below is the only
|
|
4396
|
+
* caller of currentSite.getSiteContext() in this class; everything that
|
|
4397
|
+
* used to call it directly or via getCompactSiteContext() now shares this
|
|
4398
|
+
* one cached, timeout-bounded fetch instead. */
|
|
4384
4399
|
cachedFilterContext = null;
|
|
4385
|
-
static FILTER_CACHE_TTL = 60_000; // 1 minute
|
|
4386
|
-
|
|
4400
|
+
static FILTER_CACHE_TTL = 60_000; // successful fetch: 1 minute
|
|
4401
|
+
static FILTER_CACHE_FAILURE_TTL = 15_000; // failed fetch: retry sooner
|
|
4402
|
+
static FILTER_CONTEXT_TIMEOUT_MS = 2_000;
|
|
4403
|
+
/** Dedupes concurrent callers (e.g. isWooCommerceAddonAvailable() and
|
|
4404
|
+
* isAcfAvailable() both missing a cold cache at once) onto one in-flight
|
|
4405
|
+
* fetch instead of each starting their own. */
|
|
4406
|
+
filterContextFetchInFlight = null;
|
|
4407
|
+
/**
|
|
4408
|
+
* Fetch (or reuse a cached / in-flight) site context for tool-catalog
|
|
4409
|
+
* decisions. Bounded to FILTER_CONTEXT_TIMEOUT_MS regardless of how long
|
|
4410
|
+
* the underlying client's own retry backoff would otherwise take, so a
|
|
4411
|
+
* slow or unreachable site can never block tools/list indefinitely.
|
|
4412
|
+
* Resolves to null (and caches the miss briefly) on timeout or any fetch
|
|
4413
|
+
* error — callers treat null as "show everything / skip context-dependent
|
|
4414
|
+
* gating" rather than failing the whole tool list.
|
|
4415
|
+
*/
|
|
4416
|
+
async getContextForToolFiltering() {
|
|
4387
4417
|
if (!this.currentSite) {
|
|
4388
|
-
return
|
|
4418
|
+
return null;
|
|
4389
4419
|
}
|
|
4390
|
-
let context;
|
|
4391
4420
|
const now = Date.now();
|
|
4392
|
-
if (this.cachedFilterContext
|
|
4393
|
-
|
|
4421
|
+
if (this.cachedFilterContext) {
|
|
4422
|
+
const ttl = this.cachedFilterContext.context
|
|
4423
|
+
? RespiraWordPressServer.FILTER_CACHE_TTL
|
|
4424
|
+
: RespiraWordPressServer.FILTER_CACHE_FAILURE_TTL;
|
|
4425
|
+
if (now - this.cachedFilterContext.timestamp < ttl) {
|
|
4426
|
+
return this.cachedFilterContext.context;
|
|
4427
|
+
}
|
|
4394
4428
|
}
|
|
4395
|
-
|
|
4429
|
+
if (this.filterContextFetchInFlight) {
|
|
4430
|
+
return this.filterContextFetchInFlight;
|
|
4431
|
+
}
|
|
4432
|
+
const fetchPromise = (async () => {
|
|
4433
|
+
let context = null;
|
|
4396
4434
|
try {
|
|
4397
|
-
|
|
4398
|
-
|
|
4435
|
+
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('site context fetch timed out')), RespiraWordPressServer.FILTER_CONTEXT_TIMEOUT_MS));
|
|
4436
|
+
context = await Promise.race([this.currentSite.getSiteContext(), timeout]);
|
|
4399
4437
|
}
|
|
4400
4438
|
catch {
|
|
4401
|
-
|
|
4439
|
+
context = null;
|
|
4402
4440
|
}
|
|
4441
|
+
this.cachedFilterContext = { context, timestamp: now };
|
|
4442
|
+
return context;
|
|
4443
|
+
})();
|
|
4444
|
+
this.filterContextFetchInFlight = fetchPromise;
|
|
4445
|
+
try {
|
|
4446
|
+
return await fetchPromise;
|
|
4447
|
+
}
|
|
4448
|
+
finally {
|
|
4449
|
+
this.filterContextFetchInFlight = null;
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
async filterToolsByContext(tools) {
|
|
4453
|
+
if (!this.currentSite) {
|
|
4454
|
+
return tools; // No site connected — show everything.
|
|
4455
|
+
}
|
|
4456
|
+
const context = await this.getContextForToolFiltering();
|
|
4457
|
+
if (!context) {
|
|
4458
|
+
return tools; // Can't fetch context — show everything.
|
|
4403
4459
|
}
|
|
4404
4460
|
const detectedBuilder = (context?.page_builder?.name || '').toLowerCase();
|
|
4405
4461
|
const hasWooCommerce = Boolean(context?.woocommerce?.active || context?.addons?.woocommerce?.installed);
|
|
@@ -4430,16 +4486,11 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4430
4486
|
});
|
|
4431
4487
|
}
|
|
4432
4488
|
async isWooCommerceAddonAvailable() {
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
}
|
|
4436
|
-
try {
|
|
4437
|
-
const context = await this.currentSite.getSiteContext();
|
|
4438
|
-
return Boolean(context.addons?.woocommerce?.installed && context.addons?.woocommerce?.licensed);
|
|
4439
|
-
}
|
|
4440
|
-
catch {
|
|
4489
|
+
const context = await this.getContextForToolFiltering();
|
|
4490
|
+
if (!context) {
|
|
4441
4491
|
return false;
|
|
4442
4492
|
}
|
|
4493
|
+
return Boolean(context.addons?.woocommerce?.installed && context.addons?.woocommerce?.licensed);
|
|
4443
4494
|
}
|
|
4444
4495
|
/**
|
|
4445
4496
|
* Detect whether ACF is active on the currently selected site.
|
|
@@ -4447,16 +4498,11 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4447
4498
|
* (a Pro tool on a non-Pro site returns PRO_FEATURE_REQUIRED).
|
|
4448
4499
|
*/
|
|
4449
4500
|
async isAcfAvailable() {
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
}
|
|
4453
|
-
try {
|
|
4454
|
-
const context = await this.currentSite.getSiteContext();
|
|
4455
|
-
return Boolean(context.addons?.acf?.installed);
|
|
4456
|
-
}
|
|
4457
|
-
catch {
|
|
4501
|
+
const context = await this.getContextForToolFiltering();
|
|
4502
|
+
if (!context) {
|
|
4458
4503
|
return false;
|
|
4459
4504
|
}
|
|
4505
|
+
return Boolean(context.addons?.acf?.installed);
|
|
4460
4506
|
}
|
|
4461
4507
|
getWooCommerceTools() {
|
|
4462
4508
|
return [
|
|
@@ -6838,7 +6884,7 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
6838
6884
|
},
|
|
6839
6885
|
{
|
|
6840
6886
|
name: 'wordpress_update_element',
|
|
6841
|
-
description: 'Update settings or content on
|
|
6887
|
+
description: 'Update settings or content on one specific element. This is the PRIMARY tool for targeted changes (text, style, image, link, or a custom Elementor widget) and avoids rewriting the full builder document. First use find_element, then target its stable id and send only changed settings. For an Elementor custom/third-party widget, call list_elementor_widgets and get_elementor_widget_schema first; never use extract+inject or rewrite _elementor_data for a one-widget edit. On WPBakery + Uncode, prefer identifier_type "uncode_shortcode_id". Re-read with find_element after writing. Response identifies the resolved live/duplicate target so it cannot be mistaken for a live-page change.\n\nLive-edit confirmation: when respira_allow_direct_edit=1 AND the post is a published original, the first call returns `status: "confirmation_required"` with a `next_call_examples` payload. Re-call with the suggested `edit_target` (and `confirm_live_edit: true` for the live path) to acknowledge. Both params are exposed at the top level here so autonomous flows can complete the handshake through MCP without polluting the `updates` object.',
|
|
6842
6888
|
inputSchema: {
|
|
6843
6889
|
type: 'object',
|
|
6844
6890
|
properties: {
|