@respira/wordpress-mcp-server 8.2.3 → 8.2.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.
- package/CHANGELOG.md +12 -0
- package/dist/__tests__/invoke-ability-required-args.test.d.ts +2 -0
- package/dist/__tests__/invoke-ability-required-args.test.d.ts.map +1 -0
- package/dist/__tests__/invoke-ability-required-args.test.js +53 -0
- package/dist/__tests__/invoke-ability-required-args.test.js.map +1 -0
- package/dist/__tests__/oq-put-fallback-callRestV2-integration.test.d.ts +24 -0
- package/dist/__tests__/oq-put-fallback-callRestV2-integration.test.d.ts.map +1 -0
- package/dist/__tests__/oq-put-fallback-callRestV2-integration.test.js +135 -0
- package/dist/__tests__/oq-put-fallback-callRestV2-integration.test.js.map +1 -0
- package/dist/__tests__/transport-mode-observability.test.d.ts +2 -0
- package/dist/__tests__/transport-mode-observability.test.d.ts.map +1 -0
- package/dist/__tests__/transport-mode-observability.test.js +201 -0
- package/dist/__tests__/transport-mode-observability.test.js.map +1 -0
- package/dist/__tests__/woo-card-tools-forward-fse-params.test.js +2 -2
- package/dist/__tests__/woo-card-tools-forward-fse-params.test.js.map +1 -1
- package/dist/config.d.ts +102 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +229 -0
- package/dist/config.js.map +1 -1
- package/dist/server.d.ts +42 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +218 -24
- package/dist/server.js.map +1 -1
- package/dist/wordpress-client.d.ts +17 -0
- package/dist/wordpress-client.d.ts.map +1 -1
- package/dist/wordpress-client.js +43 -0
- package/dist/wordpress-client.js.map +1 -1
- package/package.json +1 -1
- package/skills/design-system-synthesizer/SKILL.md +30 -9
- package/skills/design-system-synthesizer/metadata.json +6 -3
- package/skills/figma-to-elementor/SKILL.md +14 -2
- package/skills/html-to-bricks/SKILL.md +16 -2
- package/skills/html-to-bricks/metadata.json +4 -3
- package/skills/migrate-beaver-builder-to-bricks/SKILL.md +20 -3
- package/skills/migrate-beaver-builder-to-gutenberg/SKILL.md +20 -3
- package/skills/migrate-brizy-to-gutenberg/SKILL.md +20 -3
- package/skills/migrate-divi-to-breakdance/SKILL.md +20 -3
- package/skills/migrate-divi-to-bricks/SKILL.md +20 -3
- package/skills/migrate-divi-to-gutenberg/SKILL.md +20 -3
- package/skills/migrate-elementor-to-breakdance/SKILL.md +21 -4
- package/skills/migrate-elementor-to-bricks/SKILL.md +21 -4
- package/skills/migrate-elementor-to-gutenberg/SKILL.md +21 -4
- package/skills/migrate-elementor-to-oxygen/SKILL.md +21 -4
- package/skills/migrate-oxygen-to-breakdance/SKILL.md +20 -3
- package/skills/migrate-oxygen-to-bricks/SKILL.md +20 -3
- package/skills/migrate-thrive-architect-to-gutenberg/SKILL.md +20 -3
- package/skills/migrate-visual-composer-to-gutenberg/SKILL.md +20 -3
- package/skills/migrate-wpbakery-to-bricks/SKILL.md +20 -3
- package/skills/migrate-wpbakery-to-gutenberg/SKILL.md +20 -3
- package/skills/prime-the-agent/SKILL.md +35 -13
- package/skills/prime-the-agent/metadata.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to Respira WordPress MCP Server will be documented in this f
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [8.2.4] - 2026-08-09
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **A PUT request that a host blocks now retries as POST**, closing the gap for Elementor's global-colors and global-typography routes (403 on hosts like OVH that block PUT) and every other route built on the shared REST client in the same fix: Bricks global classes/theme styles/color palette/typography, FSE templates/patterns/navigation, design tokens, and the v1 custom post-type/taxonomy/ACF surface.
|
|
13
|
+
- **The four WooCommerce card tools' `edit_target` values now match the FSE tool family they actually route through** (`approval`/`live`, the same vocabulary `update_site_template` uses), instead of the page-edit tools' `live`/`duplicate`, which described a mode the FSE layer doesn't implement.
|
|
14
|
+
- **`redeem_token` now activates sites in the running session immediately.** A successful redeem wrote a config file and reported success, but the server kept using its boot-time site list until restarted, and under a sandboxed or read-only config source that restart never comes, so the write was silently a no-op. Sites are now merged into the live list on redeem, a first-ever redeem sets the active site so tools work without a follow-up call, and a malformed or masked key is reported with a reason instead of vanishing.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`diagnose_connection` reports the actual running transport**: bundle, npx cache, global npm install, project-local install, or source checkout, plus a flag when a differently-launched sibling Respira process is detected, closing the observability gap behind three separate "my agent is talking to the wrong site" reports that each turned out to have a different root cause.
|
|
19
|
+
|
|
8
20
|
## [8.2.3] - 2026-08-04
|
|
9
21
|
|
|
10
22
|
### Fixed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoke-ability-required-args.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/invoke-ability-required-args.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { requireAbilityName } from '../server.js';
|
|
4
|
+
// wordpress_invoke_ability has never succeeded once: 36 calls, 36 failures.
|
|
5
|
+
// Most of those are the agent naming an ability that does not exist, fixed
|
|
6
|
+
// plugin-side in 8.6.2. Seven are this: the tool was called without `ability`,
|
|
7
|
+
// the dispatcher passed undefined straight through, JSON dropped the key, and
|
|
8
|
+
// the site answered `rest_missing_callback_param` — a message that names
|
|
9
|
+
// neither the parameter nor the tool's word for it.
|
|
10
|
+
//
|
|
11
|
+
// The contract is worth guarding because it is the one an agent has to guess.
|
|
12
|
+
test('a well-formed ability name passes through, trimmed', () => {
|
|
13
|
+
assert.equal(requireAbilityName({ ability: 'rankmath/get-meta' }), 'rankmath/get-meta');
|
|
14
|
+
assert.equal(requireAbilityName({ ability: ' woocommerce/get-order ' }), 'woocommerce/get-order');
|
|
15
|
+
});
|
|
16
|
+
test('a missing ability names the parameter, the shape, and the way out', () => {
|
|
17
|
+
assert.throws(() => requireAbilityName({ args: { post_id: 1 } }), (err) => {
|
|
18
|
+
assert.match(err.message, /requires `ability`/, 'names the parameter');
|
|
19
|
+
assert.match(err.message, /vendor\/ability-name/, 'gives the shape');
|
|
20
|
+
assert.match(err.message, /wordpress_search_abilities/, 'points at discovery');
|
|
21
|
+
return true;
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
test('undefined, empty and whitespace-only are all refused', () => {
|
|
25
|
+
for (const bad of [undefined, {}, { ability: '' }, { ability: ' ' }, { ability: null }, { ability: 42 }]) {
|
|
26
|
+
assert.throws(() => requireAbilityName(bad), /requires `ability`/, `should refuse ${JSON.stringify(bad)}`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
test('a plausible alias is named in the error, not silently accepted', () => {
|
|
30
|
+
// Silently honouring these would make the tool schema a lie and teach the
|
|
31
|
+
// next agent the wrong contract from a call that worked.
|
|
32
|
+
for (const alias of ['name', 'ability_name', 'abilityName', 'tool', 'slug']) {
|
|
33
|
+
assert.throws(() => requireAbilityName({ [alias]: 'rankmath/get-meta' }), (err) => {
|
|
34
|
+
assert.match(err.message, new RegExp('`' + alias + '`'), `should name ${alias}`);
|
|
35
|
+
assert.match(err.message, /rename it to `ability`/, 'should say what to do');
|
|
36
|
+
return true;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
test('several aliases at once are all named', () => {
|
|
41
|
+
assert.throws(() => requireAbilityName({ name: 'a/b', tool: 'c/d' }), (err) => {
|
|
42
|
+
assert.match(err.message, /`name`/);
|
|
43
|
+
assert.match(err.message, /`tool`/);
|
|
44
|
+
return true;
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
test('an alias holding a non-string is not reported as if it were usable', () => {
|
|
48
|
+
assert.throws(() => requireAbilityName({ name: 12345 }), (err) => {
|
|
49
|
+
assert.doesNotMatch(err.message, /You sent/, 'a numeric alias is not a rename suggestion');
|
|
50
|
+
return true;
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=invoke-ability-required-args.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoke-ability-required-args.test.js","sourceRoot":"","sources":["../../src/__tests__/invoke-ability-required-args.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ticket 2b3a706b — the PUT-blocked-host fallback existed (isRetryableAsPost /
|
|
3
|
+
* PUT_FALLBACK_STATUSES, oq-put-fallback-403.test.ts) but was only wired into
|
|
4
|
+
* the dedicated updatePost/updatePage methods. Every tool routed through the
|
|
5
|
+
* shared `callRestV2`/`callRestV1` REST callers — Elementor global colors and
|
|
6
|
+
* typography (elementor-tools.ts), Bricks global classes/theme styles/color
|
|
7
|
+
* palette/typography (bricks-tools.ts), FSE templates/patterns/navigation and
|
|
8
|
+
* design tokens (server.ts) — emitted a bare PUT with no fallback at all, so a
|
|
9
|
+
* host that blocks PUT (allows only OPTIONS/HEAD/GET/POST, e.g. shared Apache
|
|
10
|
+
* hosting) still got a misleading "site blocked by firewall" 403 even though
|
|
11
|
+
* there is no firewall, just a host that rejects the verb.
|
|
12
|
+
*
|
|
13
|
+
* Reported, root-caused and verified live by O.Q. against
|
|
14
|
+
* linvestisseurantifragile.com (OVH shared hosting): POSTing the identical
|
|
15
|
+
* payloads to the same v2 routes succeeded with 200, proving the server
|
|
16
|
+
* already accepts POST — only the client's method choice needed the fix.
|
|
17
|
+
*
|
|
18
|
+
* These tests run a real HTTP server (no adapter mocking) so the real
|
|
19
|
+
* interceptor chain runs, including the `.response`-stripping codedError()
|
|
20
|
+
* step that made a status-only predicate a no-op the first time around
|
|
21
|
+
* (see oq-put-fallback-403.test.ts).
|
|
22
|
+
*/
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=oq-put-fallback-callRestV2-integration.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oq-put-fallback-callRestV2-integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/oq-put-fallback-callRestV2-integration.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ticket 2b3a706b — the PUT-blocked-host fallback existed (isRetryableAsPost /
|
|
3
|
+
* PUT_FALLBACK_STATUSES, oq-put-fallback-403.test.ts) but was only wired into
|
|
4
|
+
* the dedicated updatePost/updatePage methods. Every tool routed through the
|
|
5
|
+
* shared `callRestV2`/`callRestV1` REST callers — Elementor global colors and
|
|
6
|
+
* typography (elementor-tools.ts), Bricks global classes/theme styles/color
|
|
7
|
+
* palette/typography (bricks-tools.ts), FSE templates/patterns/navigation and
|
|
8
|
+
* design tokens (server.ts) — emitted a bare PUT with no fallback at all, so a
|
|
9
|
+
* host that blocks PUT (allows only OPTIONS/HEAD/GET/POST, e.g. shared Apache
|
|
10
|
+
* hosting) still got a misleading "site blocked by firewall" 403 even though
|
|
11
|
+
* there is no firewall, just a host that rejects the verb.
|
|
12
|
+
*
|
|
13
|
+
* Reported, root-caused and verified live by O.Q. against
|
|
14
|
+
* linvestisseurantifragile.com (OVH shared hosting): POSTing the identical
|
|
15
|
+
* payloads to the same v2 routes succeeded with 200, proving the server
|
|
16
|
+
* already accepts POST — only the client's method choice needed the fix.
|
|
17
|
+
*
|
|
18
|
+
* These tests run a real HTTP server (no adapter mocking) so the real
|
|
19
|
+
* interceptor chain runs, including the `.response`-stripping codedError()
|
|
20
|
+
* step that made a status-only predicate a no-op the first time around
|
|
21
|
+
* (see oq-put-fallback-403.test.ts).
|
|
22
|
+
*/
|
|
23
|
+
import { test, after } from 'node:test';
|
|
24
|
+
import assert from 'node:assert/strict';
|
|
25
|
+
import http from 'node:http';
|
|
26
|
+
import { WordPressClient } from '../wordpress-client.js';
|
|
27
|
+
const servers = [];
|
|
28
|
+
after(() => {
|
|
29
|
+
for (const s of servers)
|
|
30
|
+
s.close();
|
|
31
|
+
});
|
|
32
|
+
// Apache-style "Forbidden" page for a method the host rejects at the edge —
|
|
33
|
+
// HTML, not JSON, which is what makes handleError() treat it as
|
|
34
|
+
// "not WordPress" and stamp error.name = 'http_403_non_wordpress'.
|
|
35
|
+
const APACHE_403_HTML = '<!DOCTYPE html><html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1></body></html>';
|
|
36
|
+
/**
|
|
37
|
+
* A fake site: GET /wp-json/respira/v2/status negotiates v2 (200 JSON).
|
|
38
|
+
* PUT to `blockedPath` is rejected exactly like a host that disallows the
|
|
39
|
+
* verb: HTML body, 403 status, no WordPress JSON envelope. POST to the same
|
|
40
|
+
* path succeeds with a JSON body, mirroring O.Q.'s manual proof.
|
|
41
|
+
*/
|
|
42
|
+
async function startBlockedPutSite(blockedPath) {
|
|
43
|
+
const hits = [];
|
|
44
|
+
const server = http.createServer((req, res) => {
|
|
45
|
+
hits.push({ method: req.method || '', url: req.url || '' });
|
|
46
|
+
const path = (req.url || '').split('?')[0];
|
|
47
|
+
if (req.method === 'GET' && path === '/wp-json/respira/v2/status') {
|
|
48
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
49
|
+
res.end(JSON.stringify({ status: 'ok' }));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (req.method === 'PUT' && path === blockedPath) {
|
|
53
|
+
res.writeHead(403, { 'Content-Type': 'text/html' });
|
|
54
|
+
res.end(APACHE_403_HTML);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (req.method === 'POST' && path === blockedPath) {
|
|
58
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
59
|
+
res.end(JSON.stringify({ success: true, via: 'POST' }));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
63
|
+
res.end(JSON.stringify({ code: 'rest_no_route', message: 'No route' }));
|
|
64
|
+
});
|
|
65
|
+
servers.push(server);
|
|
66
|
+
await new Promise((resolve) => server.listen(0, resolve));
|
|
67
|
+
const { port } = server.address();
|
|
68
|
+
const client = new WordPressClient({
|
|
69
|
+
id: 'oq-blocked-put',
|
|
70
|
+
name: 'oq-blocked-put',
|
|
71
|
+
url: `http://127.0.0.1:${port}`,
|
|
72
|
+
apiKey: 'respira_test_key',
|
|
73
|
+
});
|
|
74
|
+
return { client, hits };
|
|
75
|
+
}
|
|
76
|
+
test('callRestV2 PUT on a host that blocks PUT retries as POST transparently (Elementor global colors)', async () => {
|
|
77
|
+
const { client, hits } = await startBlockedPutSite('/wp-json/respira/v2/elementor/global-colors');
|
|
78
|
+
const result = await client.callRestV2('PUT', '/elementor/global-colors', {
|
|
79
|
+
colors: [{ _id: 'brand_primary', title: 'Brand Primary', color: '#FF5733' }],
|
|
80
|
+
});
|
|
81
|
+
assert.deepEqual(result, { success: true, via: 'POST' });
|
|
82
|
+
const put = hits.filter((h) => h.method === 'PUT' && h.url.includes('/elementor/global-colors'));
|
|
83
|
+
const post = hits.filter((h) => h.method === 'POST' && h.url.includes('/elementor/global-colors'));
|
|
84
|
+
assert.equal(put.length, 1, 'PUT was attempted first');
|
|
85
|
+
assert.equal(post.length, 1, 'POST fallback fired exactly once');
|
|
86
|
+
});
|
|
87
|
+
test('callRestV2 PUT on a host that blocks PUT retries as POST transparently (Elementor global typography)', async () => {
|
|
88
|
+
const { client, hits } = await startBlockedPutSite('/wp-json/respira/v2/elementor/global-typography');
|
|
89
|
+
const result = await client.callRestV2('PUT', '/elementor/global-typography', {
|
|
90
|
+
typography: [{ _id: 'heading_primary', title: 'Heading Primary' }],
|
|
91
|
+
});
|
|
92
|
+
assert.deepEqual(result, { success: true, via: 'POST' });
|
|
93
|
+
// Scoped to the target path: a background, non-blocking checkCompatibility()
|
|
94
|
+
// POST to /server/compatibility also hits this same fake server and would
|
|
95
|
+
// otherwise inflate an unscoped count.
|
|
96
|
+
assert.equal(hits.filter((h) => h.method === 'POST' && h.url.includes('/elementor/global-typography')).length, 1);
|
|
97
|
+
});
|
|
98
|
+
test('callRestV2 PUT on a host that blocks PUT retries as POST transparently (Bricks theme styles, a sibling call site)', async () => {
|
|
99
|
+
const { client, hits } = await startBlockedPutSite('/wp-json/respira/v2/bricks/theme-styles');
|
|
100
|
+
const result = await client.callRestV2('PUT', '/bricks/theme-styles', { value: { spacing: 'md' } });
|
|
101
|
+
assert.deepEqual(result, { success: true, via: 'POST' });
|
|
102
|
+
assert.equal(hits.filter((h) => h.method === 'POST' && h.url.includes('/bricks/theme-styles')).length, 1);
|
|
103
|
+
});
|
|
104
|
+
test('callRestV2 does NOT retry a real WordPress permission 403 as POST', async () => {
|
|
105
|
+
const hits = [];
|
|
106
|
+
const server = http.createServer((req, res) => {
|
|
107
|
+
hits.push({ method: req.method || '', url: req.url || '' });
|
|
108
|
+
const path = (req.url || '').split('?')[0];
|
|
109
|
+
if (req.method === 'GET' && path === '/wp-json/respira/v2/status') {
|
|
110
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
111
|
+
res.end(JSON.stringify({ status: 'ok' }));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (req.method === 'PUT' && path === '/wp-json/respira/v2/elementor/global-colors') {
|
|
115
|
+
// A genuine WordPress permission decision: JSON body, not HTML.
|
|
116
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
117
|
+
res.end(JSON.stringify({ code: 'rest_cannot_edit', message: 'Sorry, you are not allowed to edit this.' }));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
121
|
+
res.end(JSON.stringify({ code: 'rest_no_route' }));
|
|
122
|
+
});
|
|
123
|
+
servers.push(server);
|
|
124
|
+
await new Promise((resolve) => server.listen(0, resolve));
|
|
125
|
+
const { port } = server.address();
|
|
126
|
+
const client = new WordPressClient({
|
|
127
|
+
id: 'oq-real-403',
|
|
128
|
+
name: 'oq-real-403',
|
|
129
|
+
url: `http://127.0.0.1:${port}`,
|
|
130
|
+
apiKey: 'respira_test_key',
|
|
131
|
+
});
|
|
132
|
+
await assert.rejects(() => client.callRestV2('PUT', '/elementor/global-colors', { colors: [] }), /not allowed to edit/);
|
|
133
|
+
assert.equal(hits.filter((h) => h.method === 'POST' && h.url.includes('/elementor/global-colors')).length, 0, 'no POST retry for a real WP permission denial');
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=oq-put-fallback-callRestV2-integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oq-put-fallback-callRestV2-integration.test.js","sourceRoot":"","sources":["../../src/__tests__/oq-put-fallback-callRestV2-integration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport-mode-observability.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/transport-mode-observability.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* describeEffectiveConfig answers "which config won". This is the other half:
|
|
3
|
+
* which INSTALL won, and is a second one running.
|
|
4
|
+
*
|
|
5
|
+
* Desmond (c9a95152) installed the .mcpb desktop bundle while an older npx
|
|
6
|
+
* entry stayed in his client config. Both were real Respira servers. The
|
|
7
|
+
* client kept talking to the old one for three weeks and nothing anywhere
|
|
8
|
+
* said which install was answering.
|
|
9
|
+
*
|
|
10
|
+
* These tests pin four things:
|
|
11
|
+
* 1. each install shape is recognised from its launch path alone, because a
|
|
12
|
+
* sibling process only ever gives us a command line, never an env;
|
|
13
|
+
* 2. the bundle is recognised even though its command line does not contain
|
|
14
|
+
* the string 'wordpress-mcp-server' — the exact blind spot that made
|
|
15
|
+
* Desmond's second server invisible to the old sibling scan;
|
|
16
|
+
* 3. two different launch modes running at once reads as a duplicate client
|
|
17
|
+
* entry, while two identical ones stays honestly ambiguous (orphans from
|
|
18
|
+
* a reconnect look the same from here);
|
|
19
|
+
* 4. nothing token-shaped reaches the output, which gets pasted into
|
|
20
|
+
* support threads.
|
|
21
|
+
*/
|
|
22
|
+
import { describe, it } from 'node:test';
|
|
23
|
+
import assert from 'node:assert/strict';
|
|
24
|
+
import { classifyLaunchMode, collapseLauncherProcesses, describeDuplicateEntries, describeTransportMode, redactSecrets, } from '../config.js';
|
|
25
|
+
const MCPB_PATH = '/Users/d/Library/Application Support/Claude/Claude Extensions/local.mcpb.respira-wordpress/server/index.js';
|
|
26
|
+
const NPX_PATH = '/Users/d/.npm/_npx/8f3a21/node_modules/@respira/wordpress-mcp-server/dist/index.js';
|
|
27
|
+
describe('classifyLaunchMode', () => {
|
|
28
|
+
it('recognises the desktop bundle from its path, with no env to help', () => {
|
|
29
|
+
assert.equal(classifyLaunchMode(MCPB_PATH).mode, 'mcpb_bundle');
|
|
30
|
+
});
|
|
31
|
+
it('trusts the connection source the bundle manifest sets', () => {
|
|
32
|
+
const { mode, evidence } = classifyLaunchMode('/wherever/index.js', {
|
|
33
|
+
RESPIRA_CONNECTION_SOURCE: 'claude_desktop_mcpb',
|
|
34
|
+
});
|
|
35
|
+
assert.equal(mode, 'mcpb_bundle');
|
|
36
|
+
assert.match(evidence.join(' '), /claude_desktop_mcpb/);
|
|
37
|
+
});
|
|
38
|
+
it('recognises an npx one-off run', () => {
|
|
39
|
+
assert.equal(classifyLaunchMode(NPX_PATH).mode, 'npx_cache');
|
|
40
|
+
});
|
|
41
|
+
it('recognises a global install (homebrew and nvm)', () => {
|
|
42
|
+
assert.equal(classifyLaunchMode('/opt/homebrew/lib/node_modules/@respira/wordpress-mcp-server/dist/index.js').mode, 'global_npm_install');
|
|
43
|
+
assert.equal(classifyLaunchMode('/Users/d/.nvm/versions/node/v22.3.0/lib/node_modules/@respira/wordpress-mcp-server/dist/index.js').mode, 'global_npm_install');
|
|
44
|
+
});
|
|
45
|
+
it('recognises a project-local install and a source checkout', () => {
|
|
46
|
+
assert.equal(classifyLaunchMode('/Users/d/app/node_modules/@respira/wordpress-mcp-server/dist/index.js').mode, 'project_local_install');
|
|
47
|
+
assert.equal(classifyLaunchMode('/Users/d/code/respira-wordpress/mcp-server/dist/index.js').mode, 'source_checkout');
|
|
48
|
+
});
|
|
49
|
+
it('says unknown rather than guessing', () => {
|
|
50
|
+
assert.equal(classifyLaunchMode('/opt/custom-wrapper/run.js').mode, 'unknown');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* Captured from a real Claude Desktop machine. ONE npx entry, three processes.
|
|
55
|
+
* If these count as three servers, the duplicate warning fires on every
|
|
56
|
+
* healthy install and everyone learns to ignore it.
|
|
57
|
+
*/
|
|
58
|
+
const NPX_LAUNCHER_CHAIN = [
|
|
59
|
+
{
|
|
60
|
+
pid: 72496,
|
|
61
|
+
ppid: 1,
|
|
62
|
+
command: '/Applications/Claude.app/Contents/Helpers/disclaimer /Users/d/.nvm/versions/node/v20.19.5/bin/npx -y @respira/wordpress-mcp-server@latest',
|
|
63
|
+
},
|
|
64
|
+
{ pid: 72497, ppid: 72496, command: 'npm exec @respira/wordpress-mcp-server@latest' },
|
|
65
|
+
{
|
|
66
|
+
pid: 73159,
|
|
67
|
+
ppid: 72497,
|
|
68
|
+
command: 'node /Users/d/.npm/_npx/9fa6fea18a2d317a/node_modules/.bin/wordpress-mcp-server',
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
describe('collapseLauncherProcesses', () => {
|
|
72
|
+
it("keeps only the leaf of one npx entry's three-process chain", () => {
|
|
73
|
+
const kept = collapseLauncherProcesses(999, 1, NPX_LAUNCHER_CHAIN);
|
|
74
|
+
assert.deepEqual(kept.map((s) => s.pid), [73159]);
|
|
75
|
+
});
|
|
76
|
+
it('drops this process\'s own launchers when we are the leaf', () => {
|
|
77
|
+
const kept = collapseLauncherProcesses(73159, 72497, NPX_LAUNCHER_CHAIN.slice(0, 2));
|
|
78
|
+
assert.deepEqual(kept, []);
|
|
79
|
+
});
|
|
80
|
+
it('keeps two genuinely unrelated servers', () => {
|
|
81
|
+
const kept = collapseLauncherProcesses(1, 0, [
|
|
82
|
+
{ pid: 10, ppid: 1, command: `node ${NPX_PATH}` },
|
|
83
|
+
{ pid: 20, ppid: 1, command: `node ${MCPB_PATH}` },
|
|
84
|
+
]);
|
|
85
|
+
assert.equal(kept.length, 2);
|
|
86
|
+
});
|
|
87
|
+
it('does not hang on a ppid cycle', () => {
|
|
88
|
+
const kept = collapseLauncherProcesses(1, 0, [
|
|
89
|
+
{ pid: 10, ppid: 20, command: 'node a/wordpress-mcp-server/dist/index.js' },
|
|
90
|
+
{ pid: 20, ppid: 10, command: 'node b/wordpress-mcp-server/dist/index.js' },
|
|
91
|
+
]);
|
|
92
|
+
assert.ok(Array.isArray(kept));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe('describeDuplicateEntries', () => {
|
|
96
|
+
it("flags Desmond's case: a bundle and an npx entry running at once", () => {
|
|
97
|
+
const d = describeDuplicateEntries('mcpb_bundle', [{ pid: 4242, ppid: 1, command: `node ${NPX_PATH}` }], true);
|
|
98
|
+
assert.equal(d.suspected, true);
|
|
99
|
+
assert.equal(d.confidence, 'high');
|
|
100
|
+
assert.match(String(d.message), /lists Respira twice/);
|
|
101
|
+
assert.equal(d.siblings[0].launch_mode, 'npx_cache');
|
|
102
|
+
});
|
|
103
|
+
it('stays ambiguous when the siblings were launched the same way', () => {
|
|
104
|
+
const d = describeDuplicateEntries('npx_cache', [{ pid: 1, ppid: 1, command: `node ${NPX_PATH}` }], true);
|
|
105
|
+
assert.equal(d.suspected, false);
|
|
106
|
+
assert.equal(d.confidence, 'low');
|
|
107
|
+
assert.match(String(d.message), /orphans/);
|
|
108
|
+
});
|
|
109
|
+
it('reports a clean single server when nothing else is running', () => {
|
|
110
|
+
const d = describeDuplicateEntries('mcpb_bundle', [], true);
|
|
111
|
+
assert.equal(d.suspected, false);
|
|
112
|
+
assert.equal(d.sibling_count, 0);
|
|
113
|
+
});
|
|
114
|
+
it('admits it knows nothing when the process scan could not run', () => {
|
|
115
|
+
const d = describeDuplicateEntries('unknown', [], false);
|
|
116
|
+
assert.equal(d.server_detectable, false);
|
|
117
|
+
assert.equal(d.suspected, null);
|
|
118
|
+
assert.match(String(d.message), /Check your client config by hand/);
|
|
119
|
+
});
|
|
120
|
+
it('always states the limit that a non-running duplicate is invisible', () => {
|
|
121
|
+
for (const scanned of [true, false]) {
|
|
122
|
+
const d = describeDuplicateEntries('npx_cache', [], scanned);
|
|
123
|
+
assert.match(d.limits.join(' '), /not currently running cannot be seen/);
|
|
124
|
+
assert.match(d.limits.join(' '), /deliberately does not read it/);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe('describeTransportMode', () => {
|
|
129
|
+
it('names the protocol as stdio and rules out the remote connector', () => {
|
|
130
|
+
const t = describeTransportMode({ binaryPath: NPX_PATH, env: {} });
|
|
131
|
+
assert.equal(t.protocol, 'stdio');
|
|
132
|
+
assert.match(t.notes.join(' '), /remote HTTP connector runs inside the WordPress plugin/);
|
|
133
|
+
});
|
|
134
|
+
it('promotes a single readable mode string', () => {
|
|
135
|
+
const t = describeTransportMode({ binaryPath: MCPB_PATH, env: {} });
|
|
136
|
+
assert.equal(t.transport_mode, 'mcpb_bundle');
|
|
137
|
+
assert.equal(t.label, 'Claude Desktop .mcpb bundle');
|
|
138
|
+
});
|
|
139
|
+
it('reports the connection source verbatim, or says it is unset', () => {
|
|
140
|
+
assert.equal(describeTransportMode({ binaryPath: NPX_PATH, env: { RESPIRA_CONNECTION_SOURCE: 'dashboard_export' } }).connection_source, 'dashboard_export');
|
|
141
|
+
assert.equal(describeTransportMode({ binaryPath: NPX_PATH, env: {} }).connection_source, 'unset');
|
|
142
|
+
});
|
|
143
|
+
it('warns when a global install is quietly outranking a pinned @latest', () => {
|
|
144
|
+
const t = describeTransportMode({
|
|
145
|
+
binaryPath: '/opt/homebrew/lib/node_modules/@respira/wordpress-mcp-server/dist/index.js',
|
|
146
|
+
env: {},
|
|
147
|
+
});
|
|
148
|
+
assert.equal(t.is_global_install, true);
|
|
149
|
+
assert.match(t.notes.join(' '), /npm exec prefers the global binary/);
|
|
150
|
+
});
|
|
151
|
+
it('notices a bundle running without the setup code the bundle normally passes', () => {
|
|
152
|
+
const t = describeTransportMode({ binaryPath: MCPB_PATH, env: {} });
|
|
153
|
+
assert.match(t.notes.join(' '), /setup code may not have been saved/);
|
|
154
|
+
});
|
|
155
|
+
it('carries the duplicate signal through', () => {
|
|
156
|
+
const t = describeTransportMode({
|
|
157
|
+
binaryPath: MCPB_PATH,
|
|
158
|
+
env: {},
|
|
159
|
+
siblings: [{ pid: 9, ppid: 1, command: `node ${NPX_PATH}` }],
|
|
160
|
+
selfPid: 1000,
|
|
161
|
+
selfPpid: 1,
|
|
162
|
+
});
|
|
163
|
+
assert.equal(t.duplicate_entries.suspected, true);
|
|
164
|
+
});
|
|
165
|
+
it('does not cry wolf on a healthy npx install seen from its own leaf', () => {
|
|
166
|
+
const t = describeTransportMode({
|
|
167
|
+
binaryPath: NPX_PATH,
|
|
168
|
+
env: {},
|
|
169
|
+
siblings: NPX_LAUNCHER_CHAIN.slice(0, 2),
|
|
170
|
+
selfPid: 73159,
|
|
171
|
+
selfPpid: 72497,
|
|
172
|
+
});
|
|
173
|
+
assert.equal(t.duplicate_entries.suspected, false);
|
|
174
|
+
assert.equal(t.duplicate_entries.sibling_count, 0);
|
|
175
|
+
});
|
|
176
|
+
it('never emits a token — this output gets pasted into support threads', () => {
|
|
177
|
+
const serialised = JSON.stringify(describeTransportMode({
|
|
178
|
+
binaryPath: NPX_PATH,
|
|
179
|
+
env: {},
|
|
180
|
+
siblings: [
|
|
181
|
+
{ pid: 7, ppid: 1, command: 'node /x/wordpress-mcp-server/dist/index.js --token respira_site_ABCDEF123456' },
|
|
182
|
+
],
|
|
183
|
+
selfPid: 1000,
|
|
184
|
+
selfPpid: 1,
|
|
185
|
+
}));
|
|
186
|
+
assert.doesNotMatch(serialised, /ABCDEF123456/);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('redactSecrets', () => {
|
|
190
|
+
it('masks site tokens, oauth tokens and base64 config blobs', () => {
|
|
191
|
+
assert.equal(redactSecrets('key respira_site_abc123def'), 'key respira_site_***');
|
|
192
|
+
assert.equal(redactSecrets('key rsp_at_abc123def'), 'key rsp_at_***');
|
|
193
|
+
assert.match(redactSecrets('RESPIRA_CONFIG_B64=eyJzaXRlcyI6W3sidXJsIjoiaHR0cHM6Ly9hIn1dfQ=='), /\*\*\*/);
|
|
194
|
+
assert.match(redactSecrets('node run eyJzaXRlcyI6W3sidXJsIjoiaHR0cHM6Ly9hIn1dfQ=='), /<base64-config-redacted>/);
|
|
195
|
+
});
|
|
196
|
+
it('leaves an ordinary command line alone', () => {
|
|
197
|
+
const plain = 'node /usr/local/lib/node_modules/@respira/wordpress-mcp-server/dist/index.js';
|
|
198
|
+
assert.equal(redactSecrets(plain), plain);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
//# sourceMappingURL=transport-mode-observability.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport-mode-observability.test.js","sourceRoot":"","sources":["../../src/__tests__/transport-mode-observability.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,8 +44,8 @@ describe('Woo card tools expose the FSE parameters they already forward', () =>
|
|
|
44
44
|
it(`${tool} declares verification_marker`, () => {
|
|
45
45
|
assert.match(schemaOf(tool), /verification_marker:/);
|
|
46
46
|
});
|
|
47
|
-
it(`${tool} constrains edit_target to
|
|
48
|
-
assert.match(schemaOf(tool), /enum: \['
|
|
47
|
+
it(`${tool} constrains edit_target to approval or live, matching update_site_template`, () => {
|
|
48
|
+
assert.match(schemaOf(tool), /enum: \['approval', 'live'\]/);
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
it('says direct edit is refused rather than silently downgraded without the site option', () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"woo-card-tools-forward-fse-params.test.js","sourceRoot":"","sources":["../../src/__tests__/woo-card-tools-forward-fse-params.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;;;;;;;;;;;;;;;;GAiBG;AAEH,6EAA6E;AAC7E,6EAA6E;AAC7E,gEAAgE;AAChE,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAC;AAEnE,MAAM,UAAU,GAAG;IACjB,0CAA0C;IAC1C,wCAAwC;IACxC,4BAA4B;IAC5B,iCAAiC;CAClC,CAAC;AAEF,mFAAmF;AACnF,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC;IAC9C,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,QAAQ,IAAI,YAAY,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAC5C,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,QAAQ,CAAC,+DAA+D,EAAE,GAAG,EAAE;IAC7E,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,EAAE,CAAC,GAAG,IAAI,uBAAuB,EAAE,GAAG,EAAE;YACtC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,GAAG,IAAI,+BAA+B,EAAE,GAAG,EAAE;YAC9C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,sBAAsB,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"woo-card-tools-forward-fse-params.test.js","sourceRoot":"","sources":["../../src/__tests__/woo-card-tools-forward-fse-params.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;;;;;;;;;;;;;;;;GAiBG;AAEH,6EAA6E;AAC7E,6EAA6E;AAC7E,gEAAgE;AAChE,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAC;AAEnE,MAAM,UAAU,GAAG;IACjB,0CAA0C;IAC1C,wCAAwC;IACxC,4BAA4B;IAC5B,iCAAiC;CAClC,CAAC;AAEF,mFAAmF;AACnF,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC;IAC9C,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,QAAQ,IAAI,YAAY,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAC5C,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,QAAQ,CAAC,+DAA+D,EAAE,GAAG,EAAE;IAC7E,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,EAAE,CAAC,GAAG,IAAI,uBAAuB,EAAE,GAAG,EAAE;YACtC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,GAAG,IAAI,+BAA+B,EAAE,GAAG,EAAE;YAC9C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,sBAAsB,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,GAAG,IAAI,4EAA4E,EAAE,GAAG,EAAE;YAC3F,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,8BAA8B,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;IACL,CAAC;IAED,EAAE,CAAC,qFAAqF,EAAE,GAAG,EAAE;QAC7F,MAAM,CAAC,GAAG,QAAQ,CAAC,0CAA0C,CAAC,CAAC;QAC/D,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,2BAA2B,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,yCAAyC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,GAAG,QAAQ,CAAC,4BAA4B,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,yBAAyB,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -145,4 +145,106 @@ export declare function describeEffectiveConfig(sites: Array<{
|
|
|
145
145
|
id?: string;
|
|
146
146
|
url?: string;
|
|
147
147
|
}>): Record<string, unknown>;
|
|
148
|
+
/**
|
|
149
|
+
* How this process was launched.
|
|
150
|
+
*
|
|
151
|
+
* describeEffectiveConfig answers "which config won". This answers the other
|
|
152
|
+
* half of the same question: "which INSTALL is answering". Desmond installed
|
|
153
|
+
* the .mcpb desktop bundle while an older npx entry stayed in his client
|
|
154
|
+
* config. Both were valid Respira servers. The client picked one, kept talking
|
|
155
|
+
* to it for three weeks, and nothing anywhere said which one that was.
|
|
156
|
+
*
|
|
157
|
+
* The launch modes are distinguishable from inside the process because each
|
|
158
|
+
* one leaves a different argv[1]:
|
|
159
|
+
*
|
|
160
|
+
* mcpb_bundle .../Claude Extensions/<id>/server/index.js
|
|
161
|
+
* plus RESPIRA_CONNECTION_SOURCE=claude_desktop_mcpb,
|
|
162
|
+
* which the bundle manifest sets explicitly.
|
|
163
|
+
* npx_cache .../_npx/<hash>/node_modules/.bin/... (one-off run)
|
|
164
|
+
* global_npm_install /opt/homebrew/lib/node_modules/..., nvm, AppData\Roaming\npm
|
|
165
|
+
* project_local_install ./node_modules/@respira/wordpress-mcp-server/...
|
|
166
|
+
* source_checkout .../mcp-server/dist/index.js
|
|
167
|
+
*/
|
|
168
|
+
export type RespiraLaunchMode = 'mcpb_bundle' | 'npx_cache' | 'global_npm_install' | 'project_local_install' | 'source_checkout' | 'unknown';
|
|
169
|
+
/**
|
|
170
|
+
* Classify one launch path.
|
|
171
|
+
*
|
|
172
|
+
* `env` is optional on purpose. For THIS process we pass process.env and can
|
|
173
|
+
* use the connection-source hint the bundle manifest sets. For a sibling
|
|
174
|
+
* process we only have its command line from the process table, never its
|
|
175
|
+
* environment, so the path alone has to carry the classification.
|
|
176
|
+
*/
|
|
177
|
+
export declare function classifyLaunchMode(binaryPath: string, env?: NodeJS.ProcessEnv): {
|
|
178
|
+
mode: RespiraLaunchMode;
|
|
179
|
+
evidence: string[];
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Strip anything token-shaped out of text that goes into a tool response.
|
|
183
|
+
*
|
|
184
|
+
* Process command lines are usually boring, but a user who wired their own
|
|
185
|
+
* launcher can put anything on it, and this output is written to be pasted
|
|
186
|
+
* into a support thread.
|
|
187
|
+
*/
|
|
188
|
+
export declare function redactSecrets(text: string): string;
|
|
189
|
+
export interface SiblingServerProcess {
|
|
190
|
+
pid: number;
|
|
191
|
+
ppid?: number;
|
|
192
|
+
command: string;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Collapse a process tree down to one entry per logical MCP server.
|
|
196
|
+
*
|
|
197
|
+
* Measured on a real Claude Desktop machine, ONE `npx -y
|
|
198
|
+
* @respira/wordpress-mcp-server@latest` entry produces three processes:
|
|
199
|
+
*
|
|
200
|
+
* 72496 Claude.app/Contents/Helpers/disclaimer .../npx -y @respira/...
|
|
201
|
+
* 72497 └─ npm exec @respira/wordpress-mcp-server@latest
|
|
202
|
+
* 73159 └─ node .../_npx/<hash>/node_modules/.bin/wordpress-mcp-server
|
|
203
|
+
*
|
|
204
|
+
* Counting those as three servers would report a duplicate entry on a
|
|
205
|
+
* perfectly healthy machine, which is worse than reporting nothing: a warning
|
|
206
|
+
* that cries wolf on every install teaches people to ignore it. Only the leaf
|
|
207
|
+
* is a server. Anything that is an ancestor of this process, or of another
|
|
208
|
+
* matched process, is a launcher and gets dropped.
|
|
209
|
+
*
|
|
210
|
+
* Uses only the matched processes plus this process's own parent, because
|
|
211
|
+
* every launcher in the chain matches the same filter that found the leaf.
|
|
212
|
+
*/
|
|
213
|
+
export declare function collapseLauncherProcesses(selfPid: number, selfPpid: number, siblings: SiblingServerProcess[]): SiblingServerProcess[];
|
|
214
|
+
/**
|
|
215
|
+
* What can honestly be said about a SECOND Respira entry in the client.
|
|
216
|
+
*
|
|
217
|
+
* The short answer is: not much, directly. MCP gives a server no view of the
|
|
218
|
+
* client's server list. A server sees its own stdio pipe and nothing else, so
|
|
219
|
+
* a duplicate entry that is configured but not currently running is invisible
|
|
220
|
+
* from here, and always will be.
|
|
221
|
+
*
|
|
222
|
+
* What IS observable is the process table. When a client has two Respira
|
|
223
|
+
* entries it starts both, so two Respira MCP servers run side by side under
|
|
224
|
+
* the same user. If they were launched different ways (a bundle and an npx
|
|
225
|
+
* one-off, which is exactly Desmond's case) that is close to conclusive: one
|
|
226
|
+
* client config listing Respira twice is by far the likeliest explanation, and
|
|
227
|
+
* only one of the two is answering the tool calls the user sees.
|
|
228
|
+
*
|
|
229
|
+
* Two same-mode processes are weaker evidence: orphans left behind by a
|
|
230
|
+
* supervisor reconnect look identical from here (see the cekt-ro report), so
|
|
231
|
+
* that case is reported as ambiguous rather than as a duplicate entry.
|
|
232
|
+
*/
|
|
233
|
+
export declare function describeDuplicateEntries(ownMode: RespiraLaunchMode, siblings: SiblingServerProcess[], processScanWorked: boolean): Record<string, unknown>;
|
|
234
|
+
/**
|
|
235
|
+
* The full transport picture for diagnose_connection.
|
|
236
|
+
*
|
|
237
|
+
* `protocol` is always stdio and says so plainly. The remote HTTP connector
|
|
238
|
+
* (claude.ai, native MCP v2) is served by the WordPress plugin and never runs
|
|
239
|
+
* this code, so if this field is visible at all, the answer to "am I on the
|
|
240
|
+
* remote connector" is no.
|
|
241
|
+
*/
|
|
242
|
+
export declare function describeTransportMode(options: {
|
|
243
|
+
binaryPath?: string;
|
|
244
|
+
env?: NodeJS.ProcessEnv;
|
|
245
|
+
siblings?: SiblingServerProcess[];
|
|
246
|
+
processScanWorked?: boolean;
|
|
247
|
+
selfPid?: number;
|
|
248
|
+
selfPpid?: number;
|
|
249
|
+
}): Record<string, unknown>;
|
|
148
250
|
//# sourceMappingURL=config.d.ts.map
|