@respira/wordpress-mcp-server 6.17.1 → 6.18.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.d.ts.map +1 -1
- package/dist/server.js +194 -3
- package/dist/server.js.map +1 -1
- package/dist/wordpress-client.d.ts +15 -0
- package/dist/wordpress-client.d.ts.map +1 -1
- package/dist/wordpress-client.js +34 -0
- package/dist/wordpress-client.js.map +1 -1
- package/package.json +1 -1
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmBH,OAAO,KAAK,EAAE,mBAAmB,EAAe,MAAM,kBAAkB,CAAC;AAyGzE,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;IAuTvE,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,oBAAoB;IAQ5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IAmBrB,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;YA4MP,kBAAkB;YA6BlB,yBAAyB;IASvC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;YAyBd,QAAQ;IAmxEtB;;;;;;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;YA+Dd,gBAAgB;IAqpB9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoRzB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA6UxB,GAAG;CAyCV"}
|
package/dist/server.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
7
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { execSync } from 'child_process';
|
|
9
10
|
import { readFileSync } from 'fs';
|
|
10
11
|
import { dirname, resolve } from 'path';
|
|
11
12
|
import { fileURLToPath } from 'url';
|
|
@@ -402,6 +403,16 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
|
|
|
402
403
|
- Direct edit mode: when enabled in settings, writes go straight to the original (skip duplicate workflow)
|
|
403
404
|
- respira_validate_security — check content for XSS and security issues before saving
|
|
404
405
|
|
|
406
|
+
## Theme files (CSS-family only, plugin 7.0.42+)
|
|
407
|
+
|
|
408
|
+
For agencies that source-control a shared child theme stylesheet (e.g. vds/css/custom.css) across many sites: write the file on disk, not the Customizer Additional CSS post.
|
|
409
|
+
|
|
410
|
+
- respira_read_theme_file — read a CSS / SCSS / LESS / JSON theme file
|
|
411
|
+
- respira_write_theme_file — replace (or create) a theme file with new contents
|
|
412
|
+
- respira_append_theme_file — append a block to an existing theme file
|
|
413
|
+
|
|
414
|
+
Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of scope. 1 MiB cap per file. Requires the WP edit_themes capability on the user behind the API key.
|
|
415
|
+
|
|
405
416
|
## WooCommerce (when addon installed)
|
|
406
417
|
|
|
407
418
|
21 tools for product management, categories, tags, orders, stock, and sales reports. All prefixed woocommerce_*.`,
|
|
@@ -412,6 +423,29 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
|
|
|
412
423
|
const allowedSitesEnv = process.env.RESPIRA_SITES;
|
|
413
424
|
if (allowedSitesEnv) {
|
|
414
425
|
this.allowedSites = new Set(allowedSitesEnv.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean));
|
|
426
|
+
// v6.17.2: log the filter at startup so a "missing site" never silently
|
|
427
|
+
// confuses the customer again. Previously a stale RESPIRA_SITES env var
|
|
428
|
+
// (typically baked into Claude Desktop's claude_desktop_config.json by
|
|
429
|
+
// the connector deeplink at install time) would silently hide every
|
|
430
|
+
// configured site whose hostname wasn't on the allow-list, and the only
|
|
431
|
+
// way to discover this was to read the source. Now the dropped sites
|
|
432
|
+
// are listed in the bootstrap stderr so the customer sees them on
|
|
433
|
+
// every reconnect.
|
|
434
|
+
const hidden = siteConfigs
|
|
435
|
+
.map((s) => {
|
|
436
|
+
try {
|
|
437
|
+
return new URL(s.url).hostname.toLowerCase();
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
})
|
|
443
|
+
.filter((h) => h !== null && !this.allowedSites.has(h));
|
|
444
|
+
if (hidden.length > 0) {
|
|
445
|
+
console.error(`respira-mcp: RESPIRA_SITES filter active (allowed: ${[...this.allowedSites].join(', ')}). ` +
|
|
446
|
+
`Hidden site${hidden.length === 1 ? '' : 's'}: ${hidden.join(', ')}. ` +
|
|
447
|
+
`To show all, remove or update RESPIRA_SITES in claude_desktop_config.json.`);
|
|
448
|
+
}
|
|
415
449
|
}
|
|
416
450
|
// Initialize WordPress clients for each site
|
|
417
451
|
siteConfigs.forEach((config) => {
|
|
@@ -1816,6 +1850,57 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
|
|
|
1816
1850
|
required: ['content'],
|
|
1817
1851
|
},
|
|
1818
1852
|
},
|
|
1853
|
+
{
|
|
1854
|
+
name: 'wordpress_read_theme_file',
|
|
1855
|
+
description: 'Read the contents of a theme stylesheet on disk (CSS / SCSS / LESS / JSON only). Path is relative to wp-content/themes, e.g. `vds/css/custom.css`. Useful for diffing before a write and for handing the existing stylesheet to an LLM as context. Requires plugin v7.0.42+ and the user behind the API key must have the WP `edit_themes` capability. Returns content, content_md5, byte_size, mtime, theme_role (active_stylesheet | active_template | other), is_writable.',
|
|
1856
|
+
inputSchema: {
|
|
1857
|
+
type: 'object',
|
|
1858
|
+
properties: {
|
|
1859
|
+
relative_path: {
|
|
1860
|
+
type: 'string',
|
|
1861
|
+
description: 'Path relative to wp-content/themes/. Example: `vds/css/custom.css`. No leading slash, no `..` segments. Extension must be one of: css, scss, less, json.',
|
|
1862
|
+
},
|
|
1863
|
+
},
|
|
1864
|
+
required: ['relative_path'],
|
|
1865
|
+
},
|
|
1866
|
+
readOnlyHint: true,
|
|
1867
|
+
},
|
|
1868
|
+
{
|
|
1869
|
+
name: 'wordpress_write_theme_file',
|
|
1870
|
+
description: 'Replace (or create) a theme stylesheet on disk with the given content. CSS / SCSS / LESS / JSON only. Creates any missing parent directories under the theme. Path is relative to wp-content/themes, e.g. `vds/css/custom.css`. Use this when the agency source-controls a shared child theme stylesheet and the agent needs to push CSS edits to the same file their human devs commit, not to the Customizer Additional CSS post. Requires plugin v7.0.42+ and the user behind the API key must have the WP `edit_themes` capability. Hard cap: 1 MiB per file. Returns bytes_written, content_md5, theme_role, mtime.',
|
|
1871
|
+
inputSchema: {
|
|
1872
|
+
type: 'object',
|
|
1873
|
+
properties: {
|
|
1874
|
+
relative_path: {
|
|
1875
|
+
type: 'string',
|
|
1876
|
+
description: 'Path relative to wp-content/themes/. Example: `vds/css/custom.css`. No leading slash, no `..` segments. Extension must be one of: css, scss, less, json.',
|
|
1877
|
+
},
|
|
1878
|
+
content: {
|
|
1879
|
+
type: 'string',
|
|
1880
|
+
description: 'Full file contents. Up to 1 MiB. Existing file is fully replaced.',
|
|
1881
|
+
},
|
|
1882
|
+
},
|
|
1883
|
+
required: ['relative_path', 'content'],
|
|
1884
|
+
},
|
|
1885
|
+
},
|
|
1886
|
+
{
|
|
1887
|
+
name: 'wordpress_append_theme_file',
|
|
1888
|
+
description: 'Append content to a theme stylesheet on disk. CSS / SCSS / LESS / JSON only. Adds a leading newline if the existing file did not end in one so two appended blocks do not fuse on the seam. If the file does not exist yet, behaves identically to write (creates it + any missing parent directories). Path is relative to wp-content/themes, e.g. `vds/css/custom.css`. Useful for adding per-page CSS to a shared theme stylesheet without rewriting the whole file. Requires plugin v7.0.42+ and the user behind the API key must have the WP `edit_themes` capability. Hard cap: post-append file size cannot exceed 1 MiB. Returns appended_bytes, total_bytes, content_md5, theme_role, mtime.',
|
|
1889
|
+
inputSchema: {
|
|
1890
|
+
type: 'object',
|
|
1891
|
+
properties: {
|
|
1892
|
+
relative_path: {
|
|
1893
|
+
type: 'string',
|
|
1894
|
+
description: 'Path relative to wp-content/themes/. Example: `vds/css/custom.css`. No leading slash, no `..` segments. Extension must be one of: css, scss, less, json.',
|
|
1895
|
+
},
|
|
1896
|
+
content: {
|
|
1897
|
+
type: 'string',
|
|
1898
|
+
description: 'Block to append. Up to 1 MiB. Final file size after append cannot exceed 1 MiB.',
|
|
1899
|
+
},
|
|
1900
|
+
},
|
|
1901
|
+
required: ['relative_path', 'content'],
|
|
1902
|
+
},
|
|
1903
|
+
},
|
|
1819
1904
|
{
|
|
1820
1905
|
name: 'wordpress_switch_site',
|
|
1821
1906
|
description: 'Switch to a different WordPress site in the active Respira multi-site configuration.',
|
|
@@ -3844,9 +3929,22 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
|
|
|
3844
3929
|
case 'wordpress_list_sites': {
|
|
3845
3930
|
const allSites = Array.from(this.sites.values());
|
|
3846
3931
|
const visibleSites = allSites.filter((site) => this.isSiteAllowed(site));
|
|
3932
|
+
// v6.17.2: surface hidden sites so the AI can explain
|
|
3933
|
+
// ("baymcp.com is configured but hidden by RESPIRA_SITES") instead
|
|
3934
|
+
// of pretending they don't exist. The filter itself stays in place
|
|
3935
|
+
// for security/scoping; this just makes it visible.
|
|
3936
|
+
const hiddenSites = allSites
|
|
3937
|
+
.filter((site) => !this.isSiteAllowed(site))
|
|
3938
|
+
.map((site) => this.getSiteSummary(site));
|
|
3847
3939
|
return {
|
|
3848
3940
|
sites: visibleSites.map((site) => this.getSiteSummary(site)),
|
|
3849
3941
|
active_site: this.getActiveSiteSummary(),
|
|
3942
|
+
...(hiddenSites.length > 0
|
|
3943
|
+
? {
|
|
3944
|
+
hidden_by_filter: hiddenSites,
|
|
3945
|
+
filter_note: `${hiddenSites.length} site${hiddenSites.length === 1 ? '' : 's'} hidden by RESPIRA_SITES env var (allow-list: ${this.allowedSites ? [...this.allowedSites].join(', ') : 'none'}). To show, remove or update RESPIRA_SITES in claude_desktop_config.json.`,
|
|
3946
|
+
}
|
|
3947
|
+
: {}),
|
|
3850
3948
|
};
|
|
3851
3949
|
}
|
|
3852
3950
|
case 'wordpress_get_active_site':
|
|
@@ -3947,6 +4045,18 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
|
|
|
3947
4045
|
};
|
|
3948
4046
|
case 'wordpress_validate_security':
|
|
3949
4047
|
return await client.validateSecurity(args.content);
|
|
4048
|
+
case 'wordpress_read_theme_file':
|
|
4049
|
+
return await client.readThemeFile(args.relative_path);
|
|
4050
|
+
case 'wordpress_write_theme_file':
|
|
4051
|
+
return {
|
|
4052
|
+
...(await client.writeThemeFile(args.relative_path, args.content)),
|
|
4053
|
+
respira_approvals_url: client.getApprovalsUrl(),
|
|
4054
|
+
};
|
|
4055
|
+
case 'wordpress_append_theme_file':
|
|
4056
|
+
return {
|
|
4057
|
+
...(await client.appendThemeFile(args.relative_path, args.content)),
|
|
4058
|
+
respira_approvals_url: client.getApprovalsUrl(),
|
|
4059
|
+
};
|
|
3950
4060
|
case 'wordpress_switch_site': {
|
|
3951
4061
|
const newSite = this.sites.get(args.site_id);
|
|
3952
4062
|
if (!newSite) {
|
|
@@ -3963,8 +4073,35 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
|
|
|
3963
4073
|
site: this.getSiteSummary(newSite),
|
|
3964
4074
|
};
|
|
3965
4075
|
}
|
|
3966
|
-
case 'wordpress_diagnose_connection':
|
|
3967
|
-
|
|
4076
|
+
case 'wordpress_diagnose_connection': {
|
|
4077
|
+
const siteDiag = await client.diagnoseConnection({ post_id: args.post_id, probe_timeout_ms: args.probe_timeout_ms });
|
|
4078
|
+
// v6.17.2: append process-level runtime info so version drift,
|
|
4079
|
+
// orphan processes, and a stale RESPIRA_SITES filter become
|
|
4080
|
+
// visible from a single tool call rather than requiring shell
|
|
4081
|
+
// access. Origin: cekt-ro 2026-05-20 bug report (5 orphan
|
|
4082
|
+
// processes flapping the tool catalog because npm exec ignored
|
|
4083
|
+
// the @latest pin and bound to a homebrew v6.14.2 install).
|
|
4084
|
+
const binaryPath = process.argv[1] || '<unknown>';
|
|
4085
|
+
return {
|
|
4086
|
+
...siteDiag,
|
|
4087
|
+
runtime: {
|
|
4088
|
+
version: MCP_SERVER_VERSION,
|
|
4089
|
+
pid: process.pid,
|
|
4090
|
+
binary_path: binaryPath,
|
|
4091
|
+
is_global_install: /\/opt\/homebrew\/|\/usr\/local\//.test(binaryPath),
|
|
4092
|
+
sibling_pids: findSiblingProcesses(),
|
|
4093
|
+
uptime_seconds: Math.round(process.uptime()),
|
|
4094
|
+
node_version: process.version,
|
|
4095
|
+
},
|
|
4096
|
+
filter: {
|
|
4097
|
+
RESPIRA_SITES_active: this.allowedSites !== null,
|
|
4098
|
+
allowed_hostnames: this.allowedSites ? [...this.allowedSites] : null,
|
|
4099
|
+
hidden_site_count: this.allowedSites
|
|
4100
|
+
? Array.from(this.sites.values()).filter((s) => !this.isSiteAllowed(s)).length
|
|
4101
|
+
: 0,
|
|
4102
|
+
},
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
3968
4105
|
// v6.17: docs full-text search. See searchDocs() below.
|
|
3969
4106
|
case 'wordpress_search_docs':
|
|
3970
4107
|
return await this.searchDocs(String(args?.query || ''), Number(args?.limit ?? 5));
|
|
@@ -4869,7 +5006,61 @@ Use respira_get_builder_info first to detect which builder is active. Then use t
|
|
|
4869
5006
|
})
|
|
4870
5007
|
.filter((s) => !!s);
|
|
4871
5008
|
const sitesLabel = siteNames.length ? siteNames.join(', ') : 'no sites configured';
|
|
4872
|
-
console.error(`respira-mcp v${MCP_SERVER_VERSION} ready · ${siteNames.length} site${siteNames.length === 1 ? '' : 's'}: ${sitesLabel}`);
|
|
5009
|
+
console.error(`respira-mcp v${MCP_SERVER_VERSION} ready · pid ${process.pid} · ${siteNames.length} site${siteNames.length === 1 ? '' : 's'}: ${sitesLabel}`);
|
|
5010
|
+
// v6.17.2: detect orphan sibling processes from prior reconnect cycles
|
|
5011
|
+
// and warn loudly. The Claude Code / Conductor MCP supervisor sometimes
|
|
5012
|
+
// spawns a new server without reaping the previous one, leaving 5+
|
|
5013
|
+
// parallel pairs alive that flap the tool catalog because each one
|
|
5014
|
+
// ships a different feature set (see cekt-ro bug report 2026-05-20).
|
|
5015
|
+
// We can't kill the siblings ourselves (they belong to the supervisor),
|
|
5016
|
+
// but listing them makes the failure mode obvious instead of invisible.
|
|
5017
|
+
warnAboutSiblingProcesses(findSiblingProcesses());
|
|
5018
|
+
// v6.17.2: print the running binary path so a global homebrew /
|
|
5019
|
+
// /usr/local install accidentally shadowing the `@latest` pin in
|
|
5020
|
+
// claude_desktop_config.json becomes visible. npm exec strips the
|
|
5021
|
+
// @version part of the spec when it resolves the binary, so a
|
|
5022
|
+
// globally-installed older version will silently win over the
|
|
5023
|
+
// registry's latest. Same cekt-ro bug report, bug 2.
|
|
5024
|
+
const binaryPath = process.argv[1] || '<unknown>';
|
|
5025
|
+
const isGlobal = /\/opt\/homebrew\/|\/usr\/local\//.test(binaryPath);
|
|
5026
|
+
if (isGlobal) {
|
|
5027
|
+
console.error(`respira-mcp: running from global install at ${binaryPath}. ` +
|
|
5028
|
+
`If your claude_desktop_config.json pins @latest, npm exec is ignoring the pin and serving this older binary. ` +
|
|
5029
|
+
`To unblock: 'npm uninstall -g @respira/wordpress-mcp-server' (or 'brew uninstall wordpress-mcp-server'), then reconnect.`);
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
/**
|
|
5034
|
+
* v6.17.2: detect orphan wordpress-mcp-server processes from prior
|
|
5035
|
+
* supervisor reconnect cycles. Pure function — does not log. Used by
|
|
5036
|
+
* the boot warning and by respira_diagnose_connection so diagnose
|
|
5037
|
+
* doesn't re-emit a stderr warning on every call.
|
|
5038
|
+
*/
|
|
5039
|
+
function findSiblingProcesses() {
|
|
5040
|
+
try {
|
|
5041
|
+
// ps -eo lists every process; awk filters to ones that contain
|
|
5042
|
+
// 'wordpress-mcp-server' in their command and aren't our own PID.
|
|
5043
|
+
const psOutput = execSync(`ps -eo pid,command 2>/dev/null | awk -v me=${process.pid} '$0 ~ /wordpress-mcp-server/ && $0 !~ /awk/ && $1 != me { print $1 }'`, { encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
5044
|
+
return psOutput
|
|
5045
|
+
? psOutput.split('\n').map((s) => parseInt(s.trim(), 10)).filter((n) => !Number.isNaN(n))
|
|
5046
|
+
: [];
|
|
5047
|
+
}
|
|
5048
|
+
catch {
|
|
5049
|
+
return [];
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
/**
|
|
5053
|
+
* v6.17.2: emit a one-shot stderr warning at boot if any sibling
|
|
5054
|
+
* wordpress-mcp-server processes are running. We can't kill them
|
|
5055
|
+
* (they belong to the Claude Code / Conductor supervisor), but
|
|
5056
|
+
* listing them makes the flapping-tool-catalog failure mode obvious.
|
|
5057
|
+
* See cekt-ro bug report 2026-05-20.
|
|
5058
|
+
*/
|
|
5059
|
+
function warnAboutSiblingProcesses(pids) {
|
|
5060
|
+
if (pids.length > 0) {
|
|
5061
|
+
console.error(`respira-mcp: ${pids.length} other wordpress-mcp-server process${pids.length === 1 ? '' : 'es'} running (pid${pids.length === 1 ? '' : 's'}: ${pids.join(', ')}). ` +
|
|
5062
|
+
`These are likely orphans from prior reconnects and may flap the tool catalog (different versions answering different calls). ` +
|
|
5063
|
+
`To clean: pkill -f wordpress-mcp-server, then reconnect.`);
|
|
4873
5064
|
}
|
|
4874
5065
|
}
|
|
4875
5066
|
//# sourceMappingURL=server.js.map
|