@respira/wordpress-mcp-server 6.19.7 → 6.19.9
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/__tests__/default-site-routing-notice.test.d.ts +17 -0
- package/dist/__tests__/default-site-routing-notice.test.d.ts.map +1 -0
- package/dist/__tests__/default-site-routing-notice.test.js +142 -0
- package/dist/__tests__/default-site-routing-notice.test.js.map +1 -0
- package/dist/server.d.ts +29 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +93 -1
- package/dist/server.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +10 -5
- package/dist/setup.js.map +1 -1
- package/dist/usage-emitter.d.ts +1 -0
- package/dist/usage-emitter.d.ts.map +1 -1
- package/dist/usage-emitter.js +1 -1
- package/dist/usage-emitter.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for buildDefaultSiteRoutingNotice — the multi-site safety notice that
|
|
3
|
+
* surfaces on the response envelope when a WRITE tool runs against the default
|
|
4
|
+
* site because the caller omitted site_id on an account with more than one
|
|
5
|
+
* site connected.
|
|
6
|
+
*
|
|
7
|
+
* Background: Claude Desktop frequently omits site_id and doesn't reliably call
|
|
8
|
+
* respira_switch_site, so an edit meant for site B silently lands on the
|
|
9
|
+
* default (first) site. C.J. 2026-05-29 reported exactly this ("the token for
|
|
10
|
+
* the new site is resolving to the first one"). The notice makes the
|
|
11
|
+
* wrong-site write visible the moment it happens.
|
|
12
|
+
*
|
|
13
|
+
* The method is private; the test reaches it through `as any` since `private`
|
|
14
|
+
* is a compile-time-only constraint and these run against compiled JS.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=default-site-routing-notice.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-site-routing-notice.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/default-site-routing-notice.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for buildDefaultSiteRoutingNotice — the multi-site safety notice that
|
|
3
|
+
* surfaces on the response envelope when a WRITE tool runs against the default
|
|
4
|
+
* site because the caller omitted site_id on an account with more than one
|
|
5
|
+
* site connected.
|
|
6
|
+
*
|
|
7
|
+
* Background: Claude Desktop frequently omits site_id and doesn't reliably call
|
|
8
|
+
* respira_switch_site, so an edit meant for site B silently lands on the
|
|
9
|
+
* default (first) site. C.J. 2026-05-29 reported exactly this ("the token for
|
|
10
|
+
* the new site is resolving to the first one"). The notice makes the
|
|
11
|
+
* wrong-site write visible the moment it happens.
|
|
12
|
+
*
|
|
13
|
+
* The method is private; the test reaches it through `as any` since `private`
|
|
14
|
+
* is a compile-time-only constraint and these run against compiled JS.
|
|
15
|
+
*/
|
|
16
|
+
import { test } from 'node:test';
|
|
17
|
+
import assert from 'node:assert/strict';
|
|
18
|
+
import { RespiraWordPressServer } from '../server.js';
|
|
19
|
+
const SITE_A = {
|
|
20
|
+
id: 'thegoldenrhodes-com',
|
|
21
|
+
name: 'The Golden Rhodes',
|
|
22
|
+
url: 'https://thegoldenrhodes.com',
|
|
23
|
+
apiKey: 'respira_token_a',
|
|
24
|
+
default: true,
|
|
25
|
+
};
|
|
26
|
+
const SITE_B = {
|
|
27
|
+
id: 'shivamandir-org',
|
|
28
|
+
name: 'Shivamandir',
|
|
29
|
+
url: 'https://shivamandir.org',
|
|
30
|
+
apiKey: 'respira_token_b',
|
|
31
|
+
};
|
|
32
|
+
function notice(server, name, args) {
|
|
33
|
+
return server.buildDefaultSiteRoutingNotice(name, args);
|
|
34
|
+
}
|
|
35
|
+
test('multi-site + write tool + no site_id → notice naming the default site', () => {
|
|
36
|
+
const server = new RespiraWordPressServer([SITE_A, SITE_B]);
|
|
37
|
+
const result = notice(server, 'respira_update_element', { element_id: 'abc', content: 'hi' });
|
|
38
|
+
assert.ok(result, 'expected a routing notice');
|
|
39
|
+
assert.match(result, /The Golden Rhodes/);
|
|
40
|
+
assert.match(result, /thegoldenrhodes\.com/);
|
|
41
|
+
assert.match(result, /default site/);
|
|
42
|
+
// It should point the caller at both escape hatches.
|
|
43
|
+
assert.match(result, /site_id/);
|
|
44
|
+
assert.match(result, /respira_switch_site/);
|
|
45
|
+
});
|
|
46
|
+
test('explicit site_id suppresses the notice (the call is already targeted)', () => {
|
|
47
|
+
const server = new RespiraWordPressServer([SITE_A, SITE_B]);
|
|
48
|
+
const result = notice(server, 'respira_update_element', { site_id: 'shivamandir-org', element_id: 'abc' });
|
|
49
|
+
assert.equal(result, null);
|
|
50
|
+
});
|
|
51
|
+
test('read tools never get a notice, even on a multi-site account', () => {
|
|
52
|
+
const server = new RespiraWordPressServer([SITE_A, SITE_B]);
|
|
53
|
+
assert.equal(notice(server, 'respira_list_pages', {}), null);
|
|
54
|
+
assert.equal(notice(server, 'respira_read_page', { page_id: 5 }), null);
|
|
55
|
+
assert.equal(notice(server, 'respira_get_site_context', {}), null);
|
|
56
|
+
});
|
|
57
|
+
test('single-site accounts never get a notice (no wrong-site risk)', () => {
|
|
58
|
+
const server = new RespiraWordPressServer([SITE_A]);
|
|
59
|
+
assert.equal(notice(server, 'respira_update_element', { element_id: 'abc' }), null);
|
|
60
|
+
});
|
|
61
|
+
test('write tool with no args at all on multi-site still warns', () => {
|
|
62
|
+
const server = new RespiraWordPressServer([SITE_A, SITE_B]);
|
|
63
|
+
const result = notice(server, 'respira_delete_page', undefined);
|
|
64
|
+
assert.ok(result, 'expected a routing notice when args is undefined');
|
|
65
|
+
assert.match(result, /The Golden Rhodes/);
|
|
66
|
+
});
|
|
67
|
+
test('the deprecated wordpress_* alias is classified the same as respira_*', () => {
|
|
68
|
+
const server = new RespiraWordPressServer([SITE_A, SITE_B]);
|
|
69
|
+
const result = notice(server, 'wordpress_update_page', { page_id: 1 });
|
|
70
|
+
assert.ok(result, 'wordpress_* write alias should also warn');
|
|
71
|
+
});
|
|
72
|
+
// ── RESPIRA_REQUIRE_SITE_ID hard guard (v6.19.8) ──────────────────────────
|
|
73
|
+
// enforceSiteScopeForWrites throws when the flag is on, the account is
|
|
74
|
+
// multi-site, the tool is a write, and no site_id was passed. It's the
|
|
75
|
+
// prevention counterpart to the visibility notice above.
|
|
76
|
+
function enforce(server, name, args) {
|
|
77
|
+
server.enforceSiteScopeForWrites(name, args);
|
|
78
|
+
}
|
|
79
|
+
function withFlag(value, fn) {
|
|
80
|
+
const prev = process.env.RESPIRA_REQUIRE_SITE_ID;
|
|
81
|
+
if (value === undefined)
|
|
82
|
+
delete process.env.RESPIRA_REQUIRE_SITE_ID;
|
|
83
|
+
else
|
|
84
|
+
process.env.RESPIRA_REQUIRE_SITE_ID = value;
|
|
85
|
+
try {
|
|
86
|
+
fn();
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
if (prev === undefined)
|
|
90
|
+
delete process.env.RESPIRA_REQUIRE_SITE_ID;
|
|
91
|
+
else
|
|
92
|
+
process.env.RESPIRA_REQUIRE_SITE_ID = prev;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
test('guard off by default: multi-site write without site_id does not throw', () => {
|
|
96
|
+
withFlag(undefined, () => {
|
|
97
|
+
const server = new RespiraWordPressServer([SITE_A, SITE_B]);
|
|
98
|
+
assert.doesNotThrow(() => enforce(server, 'respira_inject_builder_content', { page_id: 9, builder: 'elementor', content: {} }));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
test('guard on: multi-site write without site_id throws respira_site_id_required', () => {
|
|
102
|
+
withFlag('1', () => {
|
|
103
|
+
const server = new RespiraWordPressServer([SITE_A, SITE_B]);
|
|
104
|
+
let thrown = null;
|
|
105
|
+
try {
|
|
106
|
+
enforce(server, 'respira_inject_builder_content', { page_id: 9, builder: 'elementor', content: {} });
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
thrown = e;
|
|
110
|
+
}
|
|
111
|
+
assert.ok(thrown, 'expected the guard to throw');
|
|
112
|
+
assert.equal(thrown.name, 'respira_site_id_required');
|
|
113
|
+
assert.match(thrown.message, /thegoldenrhodes-com/);
|
|
114
|
+
assert.match(thrown.message, /shivamandir-org/);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
test('guard on: explicit site_id is allowed through', () => {
|
|
118
|
+
withFlag('1', () => {
|
|
119
|
+
const server = new RespiraWordPressServer([SITE_A, SITE_B]);
|
|
120
|
+
assert.doesNotThrow(() => enforce(server, 'respira_inject_builder_content', { site_id: 'shivamandir-org', page_id: 9, builder: 'elementor', content: {} }));
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
test('guard on: read tools are never blocked', () => {
|
|
124
|
+
withFlag('true', () => {
|
|
125
|
+
const server = new RespiraWordPressServer([SITE_A, SITE_B]);
|
|
126
|
+
assert.doesNotThrow(() => enforce(server, 'respira_read_page', { page_id: 5 }));
|
|
127
|
+
assert.doesNotThrow(() => enforce(server, 'respira_list_pages', {}));
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
test('guard on: single-site account is never blocked', () => {
|
|
131
|
+
withFlag('1', () => {
|
|
132
|
+
const server = new RespiraWordPressServer([SITE_A]);
|
|
133
|
+
assert.doesNotThrow(() => enforce(server, 'respira_inject_builder_content', { page_id: 9, builder: 'elementor', content: {} }));
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
test('guard on: site-agnostic tools (switch_site) are never blocked', () => {
|
|
137
|
+
withFlag('1', () => {
|
|
138
|
+
const server = new RespiraWordPressServer([SITE_A, SITE_B]);
|
|
139
|
+
assert.doesNotThrow(() => enforce(server, 'respira_switch_site', { site_id: 'shivamandir-org' }));
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
//# sourceMappingURL=default-site-routing-notice.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-site-routing-notice.test.js","sourceRoot":"","sources":["../../src/__tests__/default-site-routing-notice.test.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -89,6 +89,35 @@ export declare class RespiraWordPressServer {
|
|
|
89
89
|
* a structured error so the AI can explain the failure plainly.
|
|
90
90
|
*/
|
|
91
91
|
private redeemInstallToken;
|
|
92
|
+
/**
|
|
93
|
+
* When a WRITE tool runs against the default site because the caller
|
|
94
|
+
* omitted site_id and the account has more than one site connected,
|
|
95
|
+
* surface a one-line routing notice on the response. This is the
|
|
96
|
+
* wrong-site window: Claude Desktop frequently omits site_id and doesn't
|
|
97
|
+
* reliably call respira_switch_site, so an edit meant for site B silently
|
|
98
|
+
* lands on the default (first) site. Naming the acted-on site in the
|
|
99
|
+
* envelope makes that visible the moment the write happens, instead of
|
|
100
|
+
* after the user notices the wrong page changed. C.J. 2026-05-29 reported
|
|
101
|
+
* exactly this ("the token for the new site is resolving to the first one").
|
|
102
|
+
*
|
|
103
|
+
* Read tools and explicit-site_id calls get no notice — only the silent
|
|
104
|
+
* default-routing case on a multi-site account is worth flagging.
|
|
105
|
+
*/
|
|
106
|
+
private buildDefaultSiteRoutingNotice;
|
|
107
|
+
/**
|
|
108
|
+
* Opt-in strict scoping for write tools. When RESPIRA_REQUIRE_SITE_ID is
|
|
109
|
+
* truthy and the account has more than one site connected, a WRITE tool
|
|
110
|
+
* called without an explicit site_id throws instead of silently using the
|
|
111
|
+
* default site. Read tools, single-site accounts, site-agnostic tools, and
|
|
112
|
+
* calls that already carry site_id are never blocked.
|
|
113
|
+
*
|
|
114
|
+
* The thrown error carries name `respira_site_id_required` so the
|
|
115
|
+
* admin/mcp-quality dashboard groups it distinctly, and the message lists
|
|
116
|
+
* the connected sites so the agent can immediately retry with the right id.
|
|
117
|
+
*
|
|
118
|
+
* @since 6.19.8
|
|
119
|
+
*/
|
|
120
|
+
private enforceSiteScopeForWrites;
|
|
92
121
|
private withSiteContext;
|
|
93
122
|
private setupHandlers;
|
|
94
123
|
private attachUpdateNotice;
|
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;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;
|
|
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;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,6BAA6B;IA0BrC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IA+BjC,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,aAAa;YA2OP,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;IA2xB9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoUzB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA6UxB,GAAG;CAyCV"}
|
package/dist/server.js
CHANGED
|
@@ -16,7 +16,7 @@ import { RespiraVersionChecker } from './version-checker.js';
|
|
|
16
16
|
import { getBricksTools, dispatchBricksTool } from './bricks-tools.js';
|
|
17
17
|
import { getElementorTools, dispatchElementorTool } from './elementor-tools.js';
|
|
18
18
|
import { getAcfTools, ACF_TOOL_NAMES } from './acf-tools.js';
|
|
19
|
-
import { getUsageEmitter } from './usage-emitter.js';
|
|
19
|
+
import { getUsageEmitter, deriveToolKind } from './usage-emitter.js';
|
|
20
20
|
/**
|
|
21
21
|
* Read the server version from package.json at module load time so the
|
|
22
22
|
* MCP handshake, the `instructions` block, and the version-checker all
|
|
@@ -928,6 +928,83 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
928
928
|
message: `Connected ${sites.length} site${sites.length === 1 ? '' : 's'} via the Respira Cowork token. The config was written to ~/.respira/config.json. Restart this Cowork chat (or open a new one) so the MCP server picks up the new sites.`,
|
|
929
929
|
};
|
|
930
930
|
}
|
|
931
|
+
/**
|
|
932
|
+
* When a WRITE tool runs against the default site because the caller
|
|
933
|
+
* omitted site_id and the account has more than one site connected,
|
|
934
|
+
* surface a one-line routing notice on the response. This is the
|
|
935
|
+
* wrong-site window: Claude Desktop frequently omits site_id and doesn't
|
|
936
|
+
* reliably call respira_switch_site, so an edit meant for site B silently
|
|
937
|
+
* lands on the default (first) site. Naming the acted-on site in the
|
|
938
|
+
* envelope makes that visible the moment the write happens, instead of
|
|
939
|
+
* after the user notices the wrong page changed. C.J. 2026-05-29 reported
|
|
940
|
+
* exactly this ("the token for the new site is resolving to the first one").
|
|
941
|
+
*
|
|
942
|
+
* Read tools and explicit-site_id calls get no notice — only the silent
|
|
943
|
+
* default-routing case on a multi-site account is worth flagging.
|
|
944
|
+
*/
|
|
945
|
+
buildDefaultSiteRoutingNotice(name, args) {
|
|
946
|
+
if (this.sites.size <= 1 || !this.currentSite) {
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
const hasExplicitSiteId = args && typeof args === 'object' && typeof args.site_id === 'string' && args.site_id.length > 0;
|
|
950
|
+
if (hasExplicitSiteId) {
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
if (deriveToolKind(this.normalizeToolName(name).canonical) !== 'write') {
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
const site = this.currentSite;
|
|
957
|
+
let host = site.getSiteId();
|
|
958
|
+
try {
|
|
959
|
+
host = new URL(site.getSiteUrl()).host;
|
|
960
|
+
}
|
|
961
|
+
catch {
|
|
962
|
+
// fall back to the site id
|
|
963
|
+
}
|
|
964
|
+
return (`Wrote to ${site.getSiteName()} (${host}) — your default site, because no site_id was given. ` +
|
|
965
|
+
`${this.sites.size} sites are connected. To target a different site, pass site_id on the tool call ` +
|
|
966
|
+
`or call respira_switch_site first.`);
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Opt-in strict scoping for write tools. When RESPIRA_REQUIRE_SITE_ID is
|
|
970
|
+
* truthy and the account has more than one site connected, a WRITE tool
|
|
971
|
+
* called without an explicit site_id throws instead of silently using the
|
|
972
|
+
* default site. Read tools, single-site accounts, site-agnostic tools, and
|
|
973
|
+
* calls that already carry site_id are never blocked.
|
|
974
|
+
*
|
|
975
|
+
* The thrown error carries name `respira_site_id_required` so the
|
|
976
|
+
* admin/mcp-quality dashboard groups it distinctly, and the message lists
|
|
977
|
+
* the connected sites so the agent can immediately retry with the right id.
|
|
978
|
+
*
|
|
979
|
+
* @since 6.19.8
|
|
980
|
+
*/
|
|
981
|
+
enforceSiteScopeForWrites(name, args) {
|
|
982
|
+
const flag = (process.env.RESPIRA_REQUIRE_SITE_ID || '').trim().toLowerCase();
|
|
983
|
+
const enabled = flag === '1' || flag === 'true' || flag === 'yes' || flag === 'on';
|
|
984
|
+
if (!enabled) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (this.sites.size <= 1) {
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
const canonical = this.normalizeToolName(name).canonical;
|
|
991
|
+
if (SITE_AGNOSTIC_TOOLS.has(canonical)) {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (deriveToolKind(canonical) !== 'write') {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
const hasExplicitSiteId = args && typeof args === 'object' && typeof args.site_id === 'string' && args.site_id.length > 0;
|
|
998
|
+
if (hasExplicitSiteId) {
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
const available = Array.from(this.sites.keys()).join(', ') || '(none configured)';
|
|
1002
|
+
const err = new Error(`RESPIRA_REQUIRE_SITE_ID is enabled and this is a multi-site configuration, so write tools must name their target. ` +
|
|
1003
|
+
`Pass site_id on this ${canonical} call. Available site_id values: ${available}. ` +
|
|
1004
|
+
`(This guard prevents an omitted site_id from silently writing to the default site.)`);
|
|
1005
|
+
err.name = 'respira_site_id_required';
|
|
1006
|
+
throw err;
|
|
1007
|
+
}
|
|
931
1008
|
withSiteContext(result, args) {
|
|
932
1009
|
const site = this.getActiveSiteSummary(args);
|
|
933
1010
|
if (!site) {
|
|
@@ -1018,6 +1095,10 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
1018
1095
|
};
|
|
1019
1096
|
}
|
|
1020
1097
|
const resultWithSite = this.withSiteContext(result, args);
|
|
1098
|
+
const routingNotice = this.buildDefaultSiteRoutingNotice(name, args);
|
|
1099
|
+
if (routingNotice && resultWithSite && typeof resultWithSite === 'object' && !Array.isArray(resultWithSite)) {
|
|
1100
|
+
resultWithSite.routing_notice = routingNotice;
|
|
1101
|
+
}
|
|
1021
1102
|
const resultWithNotice = await this.attachUpdateNotice(resultWithSite);
|
|
1022
1103
|
const resultWithVersionWarning = this.attachVersionWarning(resultWithNotice);
|
|
1023
1104
|
return {
|
|
@@ -4318,6 +4399,17 @@ Allowlist: css, scss, less, json. PHP / JS theme writes are intentionally out of
|
|
|
4318
4399
|
return envelope;
|
|
4319
4400
|
}
|
|
4320
4401
|
async dispatchToolCall(name, args) {
|
|
4402
|
+
// v6.19.8: opt-in strict site scoping. When RESPIRA_REQUIRE_SITE_ID is set
|
|
4403
|
+
// and the account has >1 site connected, a WRITE tool called WITHOUT an
|
|
4404
|
+
// explicit site_id is refused instead of silently routing to the default
|
|
4405
|
+
// (first) site. This is the prevention counterpart to the
|
|
4406
|
+
// buildDefaultSiteRoutingNotice visibility notice: a notice tells you the
|
|
4407
|
+
// write hit the wrong site AFTER it landed; this stops it landing at all.
|
|
4408
|
+
// T.S. (studioscaler) needs hard per-session isolation in a multi-tenant
|
|
4409
|
+
// Cowork setup where switch_site mutates global state across sessions.
|
|
4410
|
+
// Default off, so single-site and switch_site-based multi-site users are
|
|
4411
|
+
// unaffected.
|
|
4412
|
+
this.enforceSiteScopeForWrites(name, args);
|
|
4321
4413
|
// v6.12.0: per-call site resolution. Agnostic tools work without a
|
|
4322
4414
|
// resolved client; everything else requires either args.site_id or a
|
|
4323
4415
|
// global currentSite. resolveClient throws cleanly if neither is set.
|