@sema-agent/server 7.129.0 → 7.130.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/MIGRATION.md +37 -0
- package/USAGE.md +38 -2
- package/dist/boot/execution-env.js +1 -0
- package/dist/boot/resolve-spec.js +8 -4
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stage-10-http-server.js +2 -0
- package/dist/capabilities/mcp-probe.js +16 -3
- package/dist/capabilities/scenarios.d.ts +33 -0
- package/dist/capabilities/scenarios.js +15 -0
- package/dist/capabilities/skills.js +1 -1
- package/dist/config-catalog.js +5 -1
- package/dist/config-types.d.ts +3 -0
- package/dist/config.js +7 -1
- package/dist/exec-bounds.d.ts +69 -0
- package/dist/exec-bounds.js +37 -0
- package/dist/hooks/hook-llm.d.ts +11 -1
- package/dist/hooks/hook-llm.js +10 -13
- package/dist/hooks/hook-runner.d.ts +1 -1
- package/dist/http/routes/capabilities.js +20 -2
- package/dist/http/routes/runs.js +12 -4
- package/dist/http/server.d.ts +8 -0
- package/dist/observability/fail-open.d.ts +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/plugins/one-time-migrations.d.ts +12 -1
- package/dist/plugins/one-time-migrations.js +17 -0
- package/dist/plugins/remote-env-adb.js +5 -4
- package/dist/plugins/remote-env-e2b.d.ts +31 -9
- package/dist/plugins/remote-env-e2b.js +77 -26
- package/dist/plugins/remote-env-host.js +7 -5
- package/dist/plugins/remote-env-k8s.d.ts +12 -4
- package/dist/plugins/remote-env-k8s.js +22 -31
- package/dist/plugins/remote-env-local-docker.js +5 -4
- package/dist/plugins/remote-env-ssh.js +5 -4
- package/dist/runtime-caps-resolver.d.ts +7 -0
- package/dist/runtime-caps-resolver.js +1 -0
- package/dist/session-titler.d.ts +4 -1
- package/dist/session-titler.js +13 -3
- package/dist/stored-request-tool-axes-m19.d.ts +29 -0
- package/dist/stored-request-tool-axes-m19.js +27 -0
- package/dist/task-mcp.d.ts +32 -1
- package/dist/task-mcp.js +66 -33
- package/dist/tool-axis-words.d.ts +14 -17
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/package.json +1 -1
package/dist/task-mcp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mcpInjectionDroppedNotice, resolveComplianceDenies } from "@sema-agent/core";
|
|
1
|
+
import { mcpInjectionDroppedNotice, readMcpToolAxes, resolveComplianceDenies } from "@sema-agent/core";
|
|
2
2
|
import { isUsableWireName } from "./wire-name-bound.js";
|
|
3
3
|
import { HttpError, createCodedHttpError } from "./security.js";
|
|
4
4
|
import { GOVERNANCE_HTTP_STATUS } from "./governance-http-status.js";
|
|
@@ -6,8 +6,8 @@ import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } f
|
|
|
6
6
|
import { readMcpSourceLabel } from "./mcp-source-label.js";
|
|
7
7
|
import { declaresMcpAssertionHeader, describeMcpAssertionHeaderNotAString, describeMcpIdentityHeadersConflict, mcpIdentityHeadersIssue, noteMcpAssertionHeaderIgnored, noteMcpAssertionHeaderNotSent, readMcpIdentityHeaders, toCoreMcpServerSpec } from "./mcp-assertion-header.js";
|
|
8
8
|
import { echoKeyName } from "./request-key-closure.js";
|
|
9
|
+
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
9
10
|
import { readMcpToolFaces } from "./mcp-tool-faces.js";
|
|
10
|
-
import { isToolAxisIrreversibility, isToolEffect } from "./tool-axis-words.js";
|
|
11
11
|
export const MAX_REQUEST_MCP_SERVERS = 32;
|
|
12
12
|
export const MAX_REQUEST_MCP_DROP_NAMES = MAX_REQUEST_MCP_SERVERS;
|
|
13
13
|
export const MCP_SERVER_ENTRY_KEYS = ["allowTools", "contentOrigin", "elicitation", "name", "source", "toolAxes", "toolFaces", "transport"];
|
|
@@ -34,7 +34,7 @@ function declaredName(e) {
|
|
|
34
34
|
? String(e.name)
|
|
35
35
|
: MALFORMED_NAME;
|
|
36
36
|
}
|
|
37
|
-
function normalizeServer(e, noteBadOrigin) {
|
|
37
|
+
function normalizeServer(e, noteBadOrigin, storedToolAxes) {
|
|
38
38
|
if (!e || typeof e !== "object")
|
|
39
39
|
return bad("entry");
|
|
40
40
|
const o = e;
|
|
@@ -76,23 +76,20 @@ function normalizeServer(e, noteBadOrigin) {
|
|
|
76
76
|
}
|
|
77
77
|
if (typeof o.elicitation === "boolean")
|
|
78
78
|
spec.elicitation = o.elicitation;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
...(typeof a.egress === "boolean" ? { egress: a.egress } : {}),
|
|
88
|
-
...(isToolAxisIrreversibility(a.irreversibility) ? { irreversibility: a.irreversibility } : {}),
|
|
89
|
-
};
|
|
90
|
-
if (Object.keys(entry).length > 0)
|
|
91
|
-
axes[tool] = entry;
|
|
79
|
+
const axes = readMcpToolAxes(o.toolAxes);
|
|
80
|
+
let judged = axes;
|
|
81
|
+
let toolAxesReread;
|
|
82
|
+
if (!axes.ok && storedToolAxes !== undefined) {
|
|
83
|
+
const again = readMcpToolAxes(storedToolAxes(o.toolAxes));
|
|
84
|
+
if (again.ok) {
|
|
85
|
+
judged = again;
|
|
86
|
+
toolAxesReread = axes.problem.replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
92
87
|
}
|
|
93
|
-
if (Object.keys(axes).length > 0)
|
|
94
|
-
spec.toolAxes = axes;
|
|
95
88
|
}
|
|
89
|
+
if (!judged.ok)
|
|
90
|
+
return bad("toolAxes");
|
|
91
|
+
if (judged.toolAxes !== undefined)
|
|
92
|
+
spec.toolAxes = judged.toolAxes;
|
|
96
93
|
if (o.contentOrigin !== undefined) {
|
|
97
94
|
if (isContentOriginWord(o.contentOrigin))
|
|
98
95
|
spec.contentOrigin = o.contentOrigin;
|
|
@@ -105,29 +102,32 @@ function normalizeServer(e, noteBadOrigin) {
|
|
|
105
102
|
return bad("toolFaces");
|
|
106
103
|
if (faces.faces !== undefined)
|
|
107
104
|
spec.toolFaces = faces.faces;
|
|
108
|
-
return { ok: true, spec };
|
|
105
|
+
return { ok: true, spec, ...(toolAxesReread !== undefined ? { toolAxesReread } : {}) };
|
|
109
106
|
}
|
|
110
107
|
export function validateRequestMcp(raw) {
|
|
111
|
-
if (raw
|
|
112
|
-
return validateRequestMcpEntries([]);
|
|
113
|
-
if (!Array.isArray(raw))
|
|
108
|
+
if (raw !== undefined && raw !== null && !Array.isArray(raw))
|
|
114
109
|
return null;
|
|
115
|
-
|
|
110
|
+
const { ok, dropped, droppedTotal, badContentOrigin } = validateRequestMcpEntries(Array.isArray(raw) ? raw : []);
|
|
111
|
+
return { ok, dropped, droppedTotal, badContentOrigin };
|
|
116
112
|
}
|
|
117
|
-
function validateRequestMcpEntries(raw) {
|
|
113
|
+
function validateRequestMcpEntries(raw, storedToolAxes) {
|
|
118
114
|
const ok = [];
|
|
119
115
|
const dropped = [];
|
|
120
116
|
const badContentOrigin = [];
|
|
117
|
+
const toolAxesReread = [];
|
|
121
118
|
for (const e of raw.slice(0, MAX_REQUEST_MCP_SERVERS)) {
|
|
122
|
-
const v = normalizeServer(e, (server, value) => badContentOrigin.push({ server, value: describeBadContentOrigin(value) }));
|
|
123
|
-
if (v.ok)
|
|
119
|
+
const v = normalizeServer(e, (server, value) => badContentOrigin.push({ server, value: describeBadContentOrigin(value) }), storedToolAxes);
|
|
120
|
+
if (v.ok) {
|
|
124
121
|
ok.push(v.spec);
|
|
122
|
+
if (v.toolAxesReread !== undefined)
|
|
123
|
+
toolAxesReread.push({ server: v.spec.name, problem: v.toolAxesReread });
|
|
124
|
+
}
|
|
125
125
|
else
|
|
126
126
|
dropped.push({ server: declaredName(e), reason: "malformed_entry", field: v.field });
|
|
127
127
|
}
|
|
128
128
|
for (const e of raw.slice(MAX_REQUEST_MCP_SERVERS, MAX_REQUEST_MCP_SERVERS + MAX_REQUEST_MCP_DROP_NAMES))
|
|
129
129
|
dropped.push({ server: declaredName(e), reason: "over_cap" });
|
|
130
|
-
return { ok, dropped, droppedTotal: Math.max(0, raw.length - ok.length), badContentOrigin };
|
|
130
|
+
return { ok, dropped, droppedTotal: Math.max(0, raw.length - ok.length), badContentOrigin, toolAxesReread };
|
|
131
131
|
}
|
|
132
132
|
export function assertRequestMcpUnlocked(bodyMcp, lockedKeys) {
|
|
133
133
|
if (!lockedKeys.has("mcp"))
|
|
@@ -180,6 +180,29 @@ export function requestEntryIdentityRefusal(entry) {
|
|
|
180
180
|
const value = (entry.transport).assertionHeader;
|
|
181
181
|
return { errorCode: "request.field_invalid", message: describeMcpAssertionHeaderNotAString(name, value) };
|
|
182
182
|
}
|
|
183
|
+
export function assertRequestMcpToolAxes(bodyMcp) {
|
|
184
|
+
if (!Array.isArray(bodyMcp))
|
|
185
|
+
return;
|
|
186
|
+
for (let i = 0; i < bodyMcp.length; i++) {
|
|
187
|
+
const refusal = requestEntryToolAxesRefusal(bodyMcp[i], `mcpServers[${i}]`);
|
|
188
|
+
if (refusal !== undefined)
|
|
189
|
+
throw createCodedHttpError(400, refusal.errorCode, refusal.message);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export function requestEntryToolAxesRefusal(entry, at) {
|
|
193
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
194
|
+
return undefined;
|
|
195
|
+
const read = readMcpToolAxes(entry.toolAxes);
|
|
196
|
+
if (read.ok)
|
|
197
|
+
return undefined;
|
|
198
|
+
const server = echoKeyName("", declaredName(entry));
|
|
199
|
+
return {
|
|
200
|
+
errorCode: "request.field_invalid",
|
|
201
|
+
message: `${at}.toolAxes (server "${server}") is not a usable declaration — ${read.problem.replace(CONTROL_AND_BIDI_STRIP_RE, "")}. ` +
|
|
202
|
+
`The whole declaration is refused: the tools that were fine are not kept and nothing is defaulted, because a toolAxes declaration is your own risk-axis judgement for this server's tools ` +
|
|
203
|
+
`(it can move a tool down or up), and a half-applied one would silently differ from what you declared. Fix the entry, or omit the key to declare nothing.`,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
183
206
|
export function deploymentClosedMcpNames(baseline, reservedNames) {
|
|
184
207
|
return new Set([...(baseline ?? []).map((s) => s.name), ...reservedNames]);
|
|
185
208
|
}
|
|
@@ -190,20 +213,20 @@ export function mergeRequestMcp(baseline, gated, reservedNames) {
|
|
|
190
213
|
const add = gated.filter((s) => !closed.has(s.name));
|
|
191
214
|
return add.length === 0 ? baseline : [...(baseline ?? []), ...add];
|
|
192
215
|
}
|
|
193
|
-
export function resolveRequestMcp(baseline, bodyMcp, config, logger, notice) {
|
|
216
|
+
export function resolveRequestMcp(baseline, bodyMcp, config, logger, notice, storedToolAxes) {
|
|
194
217
|
if (bodyMcp === undefined || bodyMcp === null)
|
|
195
218
|
return baseline;
|
|
196
|
-
const { mcp, drops } = classifyRequestMcp(baseline, bodyMcp, config, logger);
|
|
219
|
+
const { mcp, drops } = classifyRequestMcp(baseline, bodyMcp, config, logger, storedToolAxes);
|
|
197
220
|
mintInjectionDropNotices(drops, notice);
|
|
198
221
|
return mcp;
|
|
199
222
|
}
|
|
200
|
-
function classifyRequestMcp(baseline, bodyMcp, config, logger) {
|
|
223
|
+
function classifyRequestMcp(baseline, bodyMcp, config, logger, storedToolAxes) {
|
|
201
224
|
if (bodyMcp !== undefined && bodyMcp !== null && !Array.isArray(bodyMcp)) {
|
|
202
225
|
logger?.warn("mcp_injection_ignored", { reason: "mcpServers not an array" });
|
|
203
226
|
return { mcp: baseline, drops: [] };
|
|
204
227
|
}
|
|
205
228
|
const entries = Array.isArray(bodyMcp) ? bodyMcp : [];
|
|
206
|
-
const outcome = mcpInjectionHonored(config) ? classifyHonored(baseline, entries, config, logger) : classifyGateClosed(baseline, entries);
|
|
229
|
+
const outcome = mcpInjectionHonored(config) ? classifyHonored(baseline, entries, config, logger, storedToolAxes) : classifyGateClosed(baseline, entries);
|
|
207
230
|
if (outcome.drops.length > 0)
|
|
208
231
|
logger?.warn("mcp_injection_dropped", { dropped: outcome.drops, droppedTotal: outcome.droppedTotal });
|
|
209
232
|
return { mcp: outcome.mcp, drops: outcome.drops };
|
|
@@ -215,8 +238,8 @@ function classifyGateClosed(baseline, entries) {
|
|
|
215
238
|
droppedTotal: entries.length,
|
|
216
239
|
};
|
|
217
240
|
}
|
|
218
|
-
function classifyHonored(baseline, entries, config, logger) {
|
|
219
|
-
const v = validateRequestMcpEntries(entries);
|
|
241
|
+
function classifyHonored(baseline, entries, config, logger, storedToolAxes) {
|
|
242
|
+
const v = validateRequestMcpEntries(entries, storedToolAxes);
|
|
220
243
|
if (v.badContentOrigin.length > 0) {
|
|
221
244
|
logger?.warn("mcp_content_class_dropped", {
|
|
222
245
|
entries: v.badContentOrigin,
|
|
@@ -236,6 +259,16 @@ function classifyHonored(baseline, entries, config, logger) {
|
|
|
236
259
|
if (issue !== undefined && mounted.has(declaredName(e)))
|
|
237
260
|
noteMcpAssertionHeaderIgnored(logger, declaredName(e), issue);
|
|
238
261
|
}
|
|
262
|
+
for (const r of v.toolAxesReread) {
|
|
263
|
+
if (!mounted.has(r.server))
|
|
264
|
+
continue;
|
|
265
|
+
logger?.warn("mcp_tool_axes_read_as_before_upgrade", {
|
|
266
|
+
server: r.server,
|
|
267
|
+
migration: "M19",
|
|
268
|
+
problem: r.problem,
|
|
269
|
+
note: "this replayed request was stored before the upgrade and its toolAxes declaration does not pass the engine's reader; while one-time migration M19 is open it is read the way the pre-upgrade server read it (members that did not fit are dropped, a declaration that is not an object counts as none) and the server stays mounted — once the window closes the same request leaves this server unmounted",
|
|
270
|
+
});
|
|
271
|
+
}
|
|
239
272
|
return {
|
|
240
273
|
mcp: mergeRequestMcp(baseline, v.ok.map(toCoreMcpServerSpec), reservedNames),
|
|
241
274
|
drops: [...v.dropped, ...blocked.map((server) => ({ server, reason: "name_reserved_by_deployment" }))],
|
|
@@ -1,30 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* S-249(DT-13)—— per-tool
|
|
2
|
+
* S-249(DT-13)—— per-tool **风险轴覆写**的值域词表与谓词(读者:**A2A 请求腿**;以及一次性迁移 M19 的升级前读法 `stored-request-tool-axes-m19.ts`,随 M19 的登记行退役)。
|
|
3
3
|
*
|
|
4
|
-
* ##
|
|
5
|
-
* `task-mcp.ts` 与 `task-a2a.ts` 各自在请求腿上逐键校验同一个三成员结构
|
|
6
|
-
* (
|
|
7
|
-
* (两文件 × 两个词轴),没有一份钉在 core 的型上。后果不是「今天判错了」,而是**判据比声明窄**这件事
|
|
8
|
-
* 没有任何门看得见 —— core 哪天给 `ToolEffect` 加一个词(或把 `irreversibility` 的两词改成三词,它在
|
|
9
|
-
* `AskOrigin` 那一面**已经**是 `never|maybe|always` 三词了),两条请求腿会把那个**合法**的新词
|
|
10
|
-
* **静默剔除**(MCP 腿)或**整条 server 判假**(A2A 腿),而 tsc 一声不吭。
|
|
4
|
+
* ## 历史与现状
|
|
5
|
+
* 修前 `task-mcp.ts` 与 `task-a2a.ts` 各自在请求腿上逐键校验同一个三成员结构(`effect` / `egress` / `irreversibility`),两个**闭集词表**是手抄的字面串,
|
|
6
|
+
* 没有一份钉在 core 的型上;S-249 把词表收到本文件并**两向钉死在 core 的型上**(core 加词 ⇒ 正向门红;本表留了 core 已没有的词 ⇒ 反向门红),两条腿读同一只谓词。
|
|
11
7
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* 之后 MCP 面的 `toolAxes` 判官整个换给了**引擎**:core 7.51.2 起根导出 `readMcpToolAxes`(整份声明一坏全拒、不默认),部署腿(`web-admin-client/skills-mcp.ts`)
|
|
9
|
+
* 与请求腿(`task-mcp.ts`)都问它,本仓不再有 MCP 面的自铸读者、也不再 import 本文件的谓词。**A2A 面引擎没有对应的判官**(core 暂无 `readA2aToolAxes`),
|
|
10
|
+
* 所以 A2A 请求腿(`task-a2a.ts`)仍用本文件的谓词;引擎一旦给 A2A 也立一只判官,本文件随之退役。另一个读者是 M19 的迁移读者:续跑重放腿把升级前存下的
|
|
11
|
+
* `mcpServers[].toolAxes` 按升级前那只读者的读法重读一次(产物再交引擎的判官),那段读法当年就读本文件的谓词,逐字搬回;它不是 MCP 面的第二只判官。
|
|
15
12
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* A2A
|
|
13
|
+
* 下面的同型钉(A2A 的轴型与 MCP 的轴型逐字同声明)仍有意义:A2A 的词表跟着引擎 MCP 面的轴型走,哪一边加词、改词,另一边在这里当场红。
|
|
14
|
+
*
|
|
15
|
+
* 🔴 **只管值域,不管处置**:A2A 腿对坏值的处置是整条拒(MCP 面是引擎判官的「整份全拒」;两面方向一致),M19 的迁移读者剔坏成员(升级前的读法),判据与理由写在各自的调用点。
|
|
19
16
|
*/
|
|
20
17
|
import type { McpServerSpec, ToolEffect } from "@sema-agent/core";
|
|
21
|
-
/** 一条 per-tool 轴覆写的形。取 MCP 那一面作代表 ——
|
|
18
|
+
/** 一条 per-tool 轴覆写的形。取 MCP 那一面作代表 —— 下面的同型钉证明 A2A 面与它逐字同声明。 */
|
|
22
19
|
export type ToolAxisEntry = NonNullable<McpServerSpec["toolAxes"]>[string];
|
|
23
20
|
/** 轴覆写上那个**两词**的不可逆档(⚠️ 与 `AskOrigin` 面的**三词** `never|maybe|always` 不是同一个词表:
|
|
24
21
|
* 覆写面只收两极,`maybe` 是引擎按工具自己的声明定的,调用方说不出口)。 */
|
|
25
22
|
export type ToolAxisIrreversibility = NonNullable<ToolAxisEntry["irreversibility"]>;
|
|
26
|
-
/** 值是不是一个合法的 `effect` 词(
|
|
23
|
+
/** 值是不是一个合法的 `effect` 词(A2A 请求腿与 M19 的迁移读者读它)。 */
|
|
27
24
|
export declare function isToolEffect(v: unknown): v is ToolEffect;
|
|
28
|
-
/** 值是不是一个合法的**覆写面** `irreversibility` 词(
|
|
25
|
+
/** 值是不是一个合法的**覆写面** `irreversibility` 词(A2A 请求腿与 M19 的迁移读者读它)。 */
|
|
29
26
|
export declare function isToolAxisIrreversibility(v: unknown): v is ToolAxisIrreversibility;
|
|
30
27
|
//# sourceMappingURL=tool-axis-words.d.ts.map
|
|
@@ -27,7 +27,7 @@ export declare function applySkillsManifest(baseline: LoadedSkill[], manifest: {
|
|
|
27
27
|
* Resolve center MCP servers (`/effective.mcp`) into core `McpServerSpec`s + the scenarios each applies to.
|
|
28
28
|
* env-NAME refs → real values from THIS service's env (secret boundary unchanged); a server with any missing
|
|
29
29
|
* ref is skipped (`resolveRefs` → null). `enabled:false` skipped. A server whose `toolFaces` / `toolAxes` declaration is
|
|
30
|
-
* malformed is skipped whole (both are trust-root declarations — see `mcp-tool-faces.ts` / `
|
|
30
|
+
* malformed is skipped whole (both are trust-root declarations — see `mcp-tool-faces.ts` / the engine's `readMcpToolAxes`). Restart-to-apply
|
|
31
31
|
* (filtered per scenario in `resolveSpec` via `mcpForScenario`).
|
|
32
32
|
*/
|
|
33
33
|
export declare function resolveMcpServers(mcp: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.130.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|