@botbotgo/agent-harness 0.0.161 → 0.0.162
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/dist/contracts/runtime.d.ts +2 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/harness/run/governance.js +56 -2
- package/dist/runtime/harness/system/policy-engine.js +69 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -911,3 +911,4 @@ ACP transport notes:
|
|
|
911
911
|
- `serveAcpStdio(runtime)` exposes newline-delimited JSON-RPC over stdio for local IDE, CLI, or subprocess clients.
|
|
912
912
|
- `serveAcpHttp(runtime)` exposes JSON-RPC over HTTP plus SSE runtime events so remote operator surfaces can connect without importing the runtime in-process.
|
|
913
913
|
- `exportRunPackage(...)` and `exportSessionPackage(...)` package stable runtime records, transcript, approvals, events, and artifacts for operator tooling without reaching into persistence internals.
|
|
914
|
+
- `runtime/default.governance.remoteMcp` can now deny or allow specific MCP servers, raise approval requirements by transport, and stamp transport-based risk tiers into runtime governance bundles.
|
package/README.zh.md
CHANGED
|
@@ -870,3 +870,4 @@ ACP transport 说明:
|
|
|
870
870
|
- `serveAcpStdio(runtime)` 提供基于 stdio 的 newline-delimited JSON-RPC,适合本地 IDE、CLI 或子进程客户端。
|
|
871
871
|
- `serveAcpHttp(runtime)` 提供基于 HTTP 的 JSON-RPC 与 SSE runtime events,适合远程 operator surface 或独立控制面接入。
|
|
872
872
|
- `exportRunPackage(...)` 与 `exportSessionPackage(...)` 可把稳定 runtime 记录、transcript、approvals、events 和 artifacts 打包给 operator tooling,而不必直接访问 persistence 内部实现。
|
|
873
|
+
- `runtime/default.governance.remoteMcp` 现在可以按 MCP server 或 transport 做 allow/deny、审批升级,并把 transport 风险等级写进 runtime governance bundles。
|
|
@@ -102,6 +102,8 @@ export type RuntimeGovernanceToolPolicy = {
|
|
|
102
102
|
toolId: string;
|
|
103
103
|
toolType: string;
|
|
104
104
|
category: "local" | "backend" | "mcp" | "provider-native";
|
|
105
|
+
mcpServerRef?: string;
|
|
106
|
+
mcpTransport?: string;
|
|
105
107
|
risk: RuntimeGovernanceRiskLevel;
|
|
106
108
|
requiresApproval: boolean;
|
|
107
109
|
approvalPolicy: "explicit-hitl" | "runtime-default" | "none";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.161";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.161";
|
|
@@ -57,6 +57,25 @@ function readRisk(value) {
|
|
|
57
57
|
function readApprovalPolicy(value) {
|
|
58
58
|
return value === "explicit-hitl" || value === "runtime-default" || value === "none" ? value : undefined;
|
|
59
59
|
}
|
|
60
|
+
function normalizeServerRef(value) {
|
|
61
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const trimmed = value.trim();
|
|
65
|
+
return trimmed.startsWith("mcp/") ? trimmed : `mcp/${trimmed}`;
|
|
66
|
+
}
|
|
67
|
+
function readRemoteMcpMetadata(tool) {
|
|
68
|
+
const config = asObject(tool.config);
|
|
69
|
+
const mcpReference = asObject(config?.mcp);
|
|
70
|
+
const inlineServer = asObject(config?.mcpServer);
|
|
71
|
+
const transport = typeof inlineServer?.transport === "string" && inlineServer.transport.trim().length > 0
|
|
72
|
+
? inlineServer.transport.trim()
|
|
73
|
+
: undefined;
|
|
74
|
+
return {
|
|
75
|
+
...(normalizeServerRef(mcpReference?.serverRef) ? { serverRef: normalizeServerRef(mcpReference?.serverRef) } : {}),
|
|
76
|
+
...(transport ? { transport } : {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
60
79
|
function matchesToolPolicy(rule, policy) {
|
|
61
80
|
const match = asObject(rule.match) ?? rule;
|
|
62
81
|
const toolName = typeof match.toolName === "string" ? match.toolName.trim() : undefined;
|
|
@@ -102,14 +121,49 @@ function applyGovernanceOverrides(binding, policies) {
|
|
|
102
121
|
return merged;
|
|
103
122
|
});
|
|
104
123
|
}
|
|
124
|
+
function applyRemoteMcpGovernance(binding, policies) {
|
|
125
|
+
const governance = asObject(binding.harnessRuntime.governance);
|
|
126
|
+
const remoteMcp = asObject(governance?.remoteMcp);
|
|
127
|
+
if (!remoteMcp) {
|
|
128
|
+
return policies;
|
|
129
|
+
}
|
|
130
|
+
const requireApprovalTransports = new Set(readStringArray(remoteMcp.requireApprovalTransports));
|
|
131
|
+
const riskByTransport = asObject(remoteMcp.riskByTransport);
|
|
132
|
+
const inputRiskHintsByTransport = asObject(remoteMcp.inputRiskHintsByTransport);
|
|
133
|
+
return policies.map((policy) => {
|
|
134
|
+
if (policy.category !== "mcp") {
|
|
135
|
+
return policy;
|
|
136
|
+
}
|
|
137
|
+
const merged = { ...policy };
|
|
138
|
+
const transport = merged.mcpTransport;
|
|
139
|
+
if (transport && requireApprovalTransports.has(transport)) {
|
|
140
|
+
merged.requiresApproval = true;
|
|
141
|
+
if (merged.approvalPolicy === "none") {
|
|
142
|
+
merged.approvalPolicy = "runtime-default";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const transportRisk = transport ? readRisk(riskByTransport?.[transport]) : undefined;
|
|
146
|
+
if (transportRisk) {
|
|
147
|
+
merged.risk = transportRisk;
|
|
148
|
+
}
|
|
149
|
+
const transportHints = transport ? readStringArray(inputRiskHintsByTransport?.[transport]) : [];
|
|
150
|
+
if (transportHints.length > 0) {
|
|
151
|
+
merged.inputRiskHints = Array.from(new Set([...merged.inputRiskHints, ...transportHints]));
|
|
152
|
+
}
|
|
153
|
+
return merged;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
105
156
|
export function buildRuntimeGovernanceBundles(binding) {
|
|
106
|
-
const toolPolicies = applyGovernanceOverrides(binding, getBindingPrimaryTools(binding).map((tool) => {
|
|
157
|
+
const toolPolicies = applyGovernanceOverrides(binding, applyRemoteMcpGovernance(binding, getBindingPrimaryTools(binding).map((tool) => {
|
|
107
158
|
const requiresApproval = toolRequiresRuntimeApproval(tool);
|
|
159
|
+
const remoteMcp = readRemoteMcpMetadata(tool);
|
|
108
160
|
return {
|
|
109
161
|
toolName: tool.name,
|
|
110
162
|
toolId: tool.id,
|
|
111
163
|
toolType: tool.type,
|
|
112
164
|
category: toCategory(tool.type),
|
|
165
|
+
...(remoteMcp.serverRef ? { mcpServerRef: remoteMcp.serverRef } : {}),
|
|
166
|
+
...(remoteMcp.transport ? { mcpTransport: remoteMcp.transport } : {}),
|
|
113
167
|
risk: classifyRisk({
|
|
114
168
|
toolType: tool.type,
|
|
115
169
|
requiresApproval,
|
|
@@ -122,7 +176,7 @@ export function buildRuntimeGovernanceBundles(binding) {
|
|
|
122
176
|
hasInputSchema: typeof tool.inputSchemaRef === "string" && tool.inputSchemaRef.trim().length > 0,
|
|
123
177
|
inputRiskHints: inputHints(binding, tool),
|
|
124
178
|
};
|
|
125
|
-
}));
|
|
179
|
+
})));
|
|
126
180
|
if (toolPolicies.length === 0) {
|
|
127
181
|
return [];
|
|
128
182
|
}
|
|
@@ -12,6 +12,9 @@ export class PolicyEngine {
|
|
|
12
12
|
const governance = typeof binding.harnessRuntime.governance === "object" && binding.harnessRuntime.governance
|
|
13
13
|
? binding.harnessRuntime.governance
|
|
14
14
|
: undefined;
|
|
15
|
+
const remoteMcp = typeof governance?.remoteMcp === "object" && governance.remoteMcp
|
|
16
|
+
? governance.remoteMcp
|
|
17
|
+
: undefined;
|
|
15
18
|
const denyConfig = typeof governance?.deny === "object" && governance.deny
|
|
16
19
|
? governance.deny
|
|
17
20
|
: undefined;
|
|
@@ -38,6 +41,72 @@ export class PolicyEngine {
|
|
|
38
41
|
reasons.push(`runtime governance denied tool access: ${blocked.map((tool) => tool.name).join(", ")}`);
|
|
39
42
|
}
|
|
40
43
|
}
|
|
44
|
+
if (remoteMcp) {
|
|
45
|
+
const normalizeServerRef = (value) => {
|
|
46
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const trimmed = value.trim();
|
|
50
|
+
return trimmed.startsWith("mcp/") ? trimmed : `mcp/${trimmed}`;
|
|
51
|
+
};
|
|
52
|
+
const allowServerRefs = new Set(Array.isArray(remoteMcp.allowServerRefs)
|
|
53
|
+
? remoteMcp.allowServerRefs
|
|
54
|
+
.map((item) => normalizeServerRef(item))
|
|
55
|
+
.filter((item) => Boolean(item))
|
|
56
|
+
: []);
|
|
57
|
+
const denyServerRefs = new Set(Array.isArray(remoteMcp.denyServerRefs)
|
|
58
|
+
? remoteMcp.denyServerRefs
|
|
59
|
+
.map((item) => normalizeServerRef(item))
|
|
60
|
+
.filter((item) => Boolean(item))
|
|
61
|
+
: []);
|
|
62
|
+
const denyTransports = new Set(Array.isArray(remoteMcp.denyTransports)
|
|
63
|
+
? remoteMcp.denyTransports.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim())
|
|
64
|
+
: []);
|
|
65
|
+
const tools = binding.execution?.params?.tools ?? binding.langchainAgentParams?.tools ?? binding.deepAgentParams?.tools ?? [];
|
|
66
|
+
const deniedRemoteTools = tools.flatMap((tool) => {
|
|
67
|
+
if (tool.type !== "mcp") {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const config = typeof tool.config === "object" && tool.config && !Array.isArray(tool.config)
|
|
71
|
+
? tool.config
|
|
72
|
+
: undefined;
|
|
73
|
+
const mcpRef = typeof config?.mcp === "object" && config.mcp && !Array.isArray(config.mcp)
|
|
74
|
+
? config.mcp
|
|
75
|
+
: undefined;
|
|
76
|
+
const inlineMcpServer = typeof config?.mcpServer === "object" && config.mcpServer && !Array.isArray(config.mcpServer)
|
|
77
|
+
? config.mcpServer
|
|
78
|
+
: undefined;
|
|
79
|
+
const serverRef = normalizeServerRef(mcpRef?.serverRef);
|
|
80
|
+
const transport = typeof inlineMcpServer?.transport === "string" && inlineMcpServer.transport.trim().length > 0
|
|
81
|
+
? inlineMcpServer.transport.trim()
|
|
82
|
+
: undefined;
|
|
83
|
+
const serverDenied = serverRef ? denyServerRefs.has(serverRef) || (allowServerRefs.size > 0 && !allowServerRefs.has(serverRef)) : false;
|
|
84
|
+
const transportDenied = transport ? denyTransports.has(transport) : false;
|
|
85
|
+
return serverDenied || transportDenied
|
|
86
|
+
? [{
|
|
87
|
+
toolName: tool.name,
|
|
88
|
+
...(serverRef ? { serverRef } : {}),
|
|
89
|
+
...(transport ? { transport } : {}),
|
|
90
|
+
}]
|
|
91
|
+
: [];
|
|
92
|
+
});
|
|
93
|
+
if (deniedRemoteTools.length > 0) {
|
|
94
|
+
allowed = false;
|
|
95
|
+
const details = deniedRemoteTools.map((tool) => {
|
|
96
|
+
if (tool.serverRef && tool.transport) {
|
|
97
|
+
return `${tool.toolName} (${tool.serverRef}, ${tool.transport})`;
|
|
98
|
+
}
|
|
99
|
+
if (tool.serverRef) {
|
|
100
|
+
return `${tool.toolName} (${tool.serverRef})`;
|
|
101
|
+
}
|
|
102
|
+
if (tool.transport) {
|
|
103
|
+
return `${tool.toolName} (${tool.transport})`;
|
|
104
|
+
}
|
|
105
|
+
return tool.toolName;
|
|
106
|
+
});
|
|
107
|
+
reasons.push(`runtime governance denied remote MCP access: ${details.join(", ")}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
41
110
|
for (const evaluator of getPolicyEvaluators()) {
|
|
42
111
|
const decision = evaluator.evaluate(binding);
|
|
43
112
|
if (!decision) {
|