@agent-vm/openclaw-mcp-portal-plugin 0.0.69 → 0.0.71
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 +11 -14
- package/dist/index.d.ts +27 -57
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +213 -443
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +10 -2
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
1
|
import { loadMcpConfig, loadMcpPortalConfig, mcpPortalCallRequiresApproval, resolveMcpPortalProfile } from "@agent-vm/config-contracts";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { createPortalCore, createUpstreamMcpClientRuntime, listPortalCoreToolDescriptors, redactCredentialText, resolveUpstreamServers } from "@agent-vm/mcp-portal/core";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { spawn } from "node:child_process";
|
|
7
6
|
//#region src/before-prompt-build-handler.ts
|
|
8
7
|
function createBeforePromptBuildHandler(props) {
|
|
9
8
|
return async (_event, context) => {
|
|
@@ -24,26 +23,6 @@ function createBeforePromptBuildHandler(props) {
|
|
|
24
23
|
}
|
|
25
24
|
//#endregion
|
|
26
25
|
//#region src/portal-tool-policy.ts
|
|
27
|
-
function encodePortalServerNameSegment(value) {
|
|
28
|
-
const encodedCharacters = [];
|
|
29
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
30
|
-
const character = value.charAt(index);
|
|
31
|
-
if (/^[A-Za-z0-9]$/u.test(character)) encodedCharacters.push(character);
|
|
32
|
-
else encodedCharacters.push(`_${character.charCodeAt(0).toString(16).padStart(2, "0")}_`);
|
|
33
|
-
}
|
|
34
|
-
return encodedCharacters.join("");
|
|
35
|
-
}
|
|
36
|
-
function portalServerNameForAgent(agentId) {
|
|
37
|
-
return `mcp_portal_${encodePortalServerNameSegment(agentId)}`;
|
|
38
|
-
}
|
|
39
|
-
function materializedPortalToolNames(serverName) {
|
|
40
|
-
return [
|
|
41
|
-
`${serverName}__mcp_portal_list`,
|
|
42
|
-
`${serverName}__mcp_portal_search`,
|
|
43
|
-
`${serverName}__mcp_portal_describe`,
|
|
44
|
-
`${serverName}__mcp_portal_call`
|
|
45
|
-
];
|
|
46
|
-
}
|
|
47
26
|
function profileAllowsPortalCall(profile, call) {
|
|
48
27
|
if (!profile.enabledNamespaces.includes(call.namespace)) return false;
|
|
49
28
|
const enabledTools = profile.enabledToolsByNamespace[call.namespace] ?? [];
|
|
@@ -55,17 +34,16 @@ function profileRequiresPortalApproval(profile, call) {
|
|
|
55
34
|
}
|
|
56
35
|
//#endregion
|
|
57
36
|
//#region src/before-tool-call-handler.ts
|
|
58
|
-
|
|
59
|
-
function isObjectRecord$1(value) {
|
|
37
|
+
function isObjectRecord$2(value) {
|
|
60
38
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
61
39
|
}
|
|
62
40
|
function parseCallRequest(value) {
|
|
63
|
-
if (!isObjectRecord$
|
|
41
|
+
if (!isObjectRecord$2(value)) return null;
|
|
64
42
|
const id = value.id;
|
|
65
43
|
const namespace = value.namespace;
|
|
66
44
|
const toolName = value.toolName;
|
|
67
45
|
const argumentsValue = value.arguments;
|
|
68
|
-
if (typeof id !== "string" || typeof namespace !== "string" || typeof toolName !== "string" || !isObjectRecord$
|
|
46
|
+
if (typeof id !== "string" || typeof namespace !== "string" || typeof toolName !== "string" || !isObjectRecord$2(argumentsValue)) return null;
|
|
69
47
|
return {
|
|
70
48
|
arguments: argumentsValue,
|
|
71
49
|
id,
|
|
@@ -73,9 +51,6 @@ function parseCallRequest(value) {
|
|
|
73
51
|
toolName
|
|
74
52
|
};
|
|
75
53
|
}
|
|
76
|
-
function portalAgentIdFromToolName(toolName, agentIds) {
|
|
77
|
-
return agentIds.find((agentId) => toolName.startsWith(`${portalServerNameForAgent(agentId)}__mcp_portal_`)) ?? null;
|
|
78
|
-
}
|
|
79
54
|
function parseCallRequests(params) {
|
|
80
55
|
const calls = params.calls;
|
|
81
56
|
if (!Array.isArray(calls)) return null;
|
|
@@ -87,28 +62,15 @@ function parseCallRequests(params) {
|
|
|
87
62
|
}
|
|
88
63
|
return parsedCalls;
|
|
89
64
|
}
|
|
90
|
-
function errorMessage$1(error) {
|
|
91
|
-
return error instanceof Error ? error.message : String(error);
|
|
92
|
-
}
|
|
93
65
|
function createBeforeToolCallHandler(props) {
|
|
94
66
|
return async (event, context) => {
|
|
95
|
-
|
|
96
|
-
const agentId = portalAgentIdFromToolName(event.toolName, Object.keys(portalConfig.agents));
|
|
97
|
-
if (agentId === null) return;
|
|
98
|
-
const portalUnavailableReason = props.runtimeState.getPortalUnavailableReason();
|
|
99
|
-
if (portalUnavailableReason !== null) return {
|
|
100
|
-
block: true,
|
|
101
|
-
blockReason: `mcp-portal: portal subprocess unavailable (${portalUnavailableReason}).`
|
|
102
|
-
};
|
|
67
|
+
if (event.toolName !== "mcp_portal_call") return;
|
|
103
68
|
if (context.agentId === void 0) return {
|
|
104
69
|
block: true,
|
|
105
70
|
blockReason: `mcp-portal: missing OpenClaw agent context for ${event.toolName}.`
|
|
106
71
|
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
blockReason: `mcp-portal: tool ${event.toolName} is not assigned to agent ${context.agentId}.`
|
|
110
|
-
};
|
|
111
|
-
if (!event.toolName.endsWith("__mcp_portal_call")) return;
|
|
72
|
+
const portalConfig = await props.runtimeState.loadPortalConfig();
|
|
73
|
+
const agentId = context.agentId;
|
|
112
74
|
const agent = portalConfig.agents[agentId];
|
|
113
75
|
if (agent === void 0) return {
|
|
114
76
|
block: true,
|
|
@@ -126,29 +88,6 @@ function createBeforeToolCallHandler(props) {
|
|
|
126
88
|
};
|
|
127
89
|
const approvalCalls = calls.filter((call) => profileRequiresPortalApproval(profile, call));
|
|
128
90
|
if (approvalCalls.length === 0) return;
|
|
129
|
-
const token = signApprovalToken({
|
|
130
|
-
agentId,
|
|
131
|
-
calls: approvalCalls.map((call) => ({
|
|
132
|
-
argumentsHash: hashCallArguments(call.arguments),
|
|
133
|
-
namespace: call.namespace,
|
|
134
|
-
toolName: call.toolName
|
|
135
|
-
})),
|
|
136
|
-
expiresAtMs: Date.now() + approvalTokenTtlMs,
|
|
137
|
-
key: props.runtimeState.getKeyRegistry().getKey(agentId)
|
|
138
|
-
});
|
|
139
|
-
try {
|
|
140
|
-
event.params.portalApprovalToken = token;
|
|
141
|
-
} catch (error) {
|
|
142
|
-
props.logger?.warn?.(`[mcp-portal] could not attach server-side approval token: ${errorMessage$1(error)}`);
|
|
143
|
-
return {
|
|
144
|
-
block: true,
|
|
145
|
-
blockReason: "mcp-portal: could not attach server-side approval token."
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
if (event.params.portalApprovalToken !== token) return {
|
|
149
|
-
block: true,
|
|
150
|
-
blockReason: "mcp-portal: could not attach server-side approval token."
|
|
151
|
-
};
|
|
152
91
|
const toolNames = approvalCalls.map((call) => `${call.namespace}.${call.toolName}`).toSorted().join(", ");
|
|
153
92
|
return { requireApproval: {
|
|
154
93
|
description: `Allow MCP Portal batch for agent ${agentId}: ${toolNames}.`,
|
|
@@ -161,300 +100,86 @@ function createBeforeToolCallHandler(props) {
|
|
|
161
100
|
};
|
|
162
101
|
}
|
|
163
102
|
//#endregion
|
|
164
|
-
//#region src/
|
|
165
|
-
const
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
|
|
103
|
+
//#region src/effective-config-manifest.ts
|
|
104
|
+
const effectiveConfigManifestFileName = "mcp-portal-effective-manifest.json";
|
|
105
|
+
function isObjectRecord$1(value) {
|
|
106
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
107
|
+
}
|
|
108
|
+
function isSafeManifestFileName(value) {
|
|
109
|
+
return value.length > 0 && !value.includes("/") && !value.includes("\\");
|
|
110
|
+
}
|
|
111
|
+
function parseEffectiveConfigManifest(value) {
|
|
112
|
+
if (!isObjectRecord$1(value)) throw new Error("MCP Portal effective config manifest must be an object.");
|
|
113
|
+
if (value.schemaVersion !== 1) throw new Error("MCP Portal effective config manifest must use schemaVersion 1.");
|
|
114
|
+
if (typeof value.mcpConfigFile !== "string" || !isSafeManifestFileName(value.mcpConfigFile)) throw new Error("MCP Portal effective config manifest must contain a safe mcpConfigFile.");
|
|
115
|
+
if (typeof value.portalConfigFile !== "string" || !isSafeManifestFileName(value.portalConfigFile)) throw new Error("MCP Portal effective config manifest must contain a safe portalConfigFile.");
|
|
169
116
|
return {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
117
|
+
mcpConfigFile: value.mcpConfigFile,
|
|
118
|
+
portalConfigFile: value.portalConfigFile
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function isMissingFileError(error) {
|
|
122
|
+
return isObjectRecord$1(error) && typeof error.code === "string" && (error.code === "ENOENT" || error.code === "ENOTDIR");
|
|
123
|
+
}
|
|
124
|
+
async function resolveEffectiveConfigPaths(configDir) {
|
|
125
|
+
const manifestPath = join(configDir, effectiveConfigManifestFileName);
|
|
126
|
+
let manifestText;
|
|
127
|
+
try {
|
|
128
|
+
manifestText = await readFile(manifestPath, "utf8");
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (isMissingFileError(error)) return {
|
|
131
|
+
mcpConfigPath: join(configDir, "mcp.config.jsonc"),
|
|
132
|
+
portalConfigPath: join(configDir, "mcp-portal.config.jsonc")
|
|
133
|
+
};
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
const manifest = parseEffectiveConfigManifest(JSON.parse(manifestText));
|
|
137
|
+
return {
|
|
138
|
+
mcpConfigPath: join(configDir, manifest.mcpConfigFile),
|
|
139
|
+
portalConfigPath: join(configDir, manifest.portalConfigFile)
|
|
177
140
|
};
|
|
178
141
|
}
|
|
179
142
|
//#endregion
|
|
180
143
|
//#region src/portal-config.ts
|
|
181
|
-
const
|
|
182
|
-
const portalPluginConfigSchema = z.object({
|
|
183
|
-
binPath: z.string().min(1).default(defaultPortalBinPath),
|
|
184
|
-
configDir: z.string().min(1).optional()
|
|
185
|
-
}).strict();
|
|
144
|
+
const portalPluginConfigSchema = z.object({ configDir: z.string().min(1) }).strict();
|
|
186
145
|
function parsePortalConfig(value) {
|
|
187
146
|
return portalPluginConfigSchema.parse(value ?? {});
|
|
188
147
|
}
|
|
189
148
|
//#endregion
|
|
190
149
|
//#region src/portal-plugin-runtime-state.ts
|
|
191
150
|
function createPortalPluginRuntimeState(props) {
|
|
192
|
-
let
|
|
151
|
+
let loadedPortalConfig = null;
|
|
193
152
|
let portalConfigPromise = null;
|
|
194
153
|
let portalUnavailableReason = null;
|
|
195
154
|
const loadPortalConfigFile = props.loadPortalConfig ?? loadMcpPortalConfig;
|
|
196
|
-
const portalConfigPath = join(props.configDir, "mcp-portal.config.jsonc");
|
|
197
155
|
function loadPortalConfig() {
|
|
198
156
|
if (portalConfigPromise !== null) return portalConfigPromise;
|
|
199
|
-
const nextPromise = loadPortalConfigFile(portalConfigPath).
|
|
157
|
+
const nextPromise = resolveEffectiveConfigPaths(props.configDir).then((effectiveConfigPaths) => loadPortalConfigFile(effectiveConfigPaths.portalConfigPath)).then((portalConfig) => {
|
|
158
|
+
loadedPortalConfig = portalConfig;
|
|
159
|
+
return portalConfig;
|
|
160
|
+
}).catch((error) => {
|
|
200
161
|
if (portalConfigPromise === nextPromise) portalConfigPromise = null;
|
|
201
162
|
throw error;
|
|
202
163
|
});
|
|
203
164
|
portalConfigPromise = nextPromise;
|
|
204
|
-
return
|
|
165
|
+
return portalConfigPromise;
|
|
205
166
|
}
|
|
206
167
|
return {
|
|
207
168
|
configDir: props.configDir,
|
|
169
|
+
getLoadedPortalConfig: () => loadedPortalConfig,
|
|
208
170
|
getPortalUnavailableReason: () => portalUnavailableReason,
|
|
209
|
-
getKeyRegistry: () => {
|
|
210
|
-
if (keyRegistry === null) throw new Error("MCP Portal HMAC key registry is not initialized.");
|
|
211
|
-
return keyRegistry;
|
|
212
|
-
},
|
|
213
171
|
loadPortalConfig,
|
|
214
172
|
markPortalAvailable: () => {
|
|
215
173
|
portalUnavailableReason = null;
|
|
216
174
|
},
|
|
217
175
|
markPortalUnavailable: (reason) => {
|
|
218
176
|
portalUnavailableReason = reason;
|
|
219
|
-
},
|
|
220
|
-
setKeyRegistry: (registry) => {
|
|
221
|
-
keyRegistry = registry;
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
//#endregion
|
|
226
|
-
//#region src/portal-subprocess-supervisor.ts
|
|
227
|
-
const defaultBackoffSteps = [
|
|
228
|
-
200,
|
|
229
|
-
400,
|
|
230
|
-
800,
|
|
231
|
-
1600,
|
|
232
|
-
3200,
|
|
233
|
-
5e3
|
|
234
|
-
];
|
|
235
|
-
const inheritedPortalEnvNames = [
|
|
236
|
-
"HOME",
|
|
237
|
-
"PATH",
|
|
238
|
-
"TEMP",
|
|
239
|
-
"TMP",
|
|
240
|
-
"TMPDIR"
|
|
241
|
-
];
|
|
242
|
-
function createPortalSubprocessEnv(hmacEnv, portalEnv = {}) {
|
|
243
|
-
const env = {};
|
|
244
|
-
for (const name of inheritedPortalEnvNames) {
|
|
245
|
-
const value = process.env[name];
|
|
246
|
-
if (value !== void 0) env[name] = value;
|
|
247
|
-
}
|
|
248
|
-
return {
|
|
249
|
-
...env,
|
|
250
|
-
...portalEnv,
|
|
251
|
-
...hmacEnv
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
function logSubprocessOutput(props) {
|
|
255
|
-
const text = String(props.chunk);
|
|
256
|
-
for (const line of text.split(/\r?\n/u)) {
|
|
257
|
-
if (line.length === 0) continue;
|
|
258
|
-
const message = `[mcp-portal ${props.streamName}] ${line}`;
|
|
259
|
-
if (props.streamName === "stderr") props.logger.warn(message);
|
|
260
|
-
else props.logger.info(message);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
function delay(ms) {
|
|
264
|
-
return new Promise((resolve) => {
|
|
265
|
-
setTimeout(resolve, ms);
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
async function waitForExit(child, timeoutMs) {
|
|
269
|
-
return new Promise((resolve) => {
|
|
270
|
-
let settled = false;
|
|
271
|
-
const timer = setTimeout(() => {
|
|
272
|
-
if (settled) return;
|
|
273
|
-
settled = true;
|
|
274
|
-
child.off("exit", handleExit);
|
|
275
|
-
resolve(false);
|
|
276
|
-
}, timeoutMs);
|
|
277
|
-
const handleExit = () => {
|
|
278
|
-
if (settled) return;
|
|
279
|
-
settled = true;
|
|
280
|
-
clearTimeout(timer);
|
|
281
|
-
resolve(true);
|
|
282
|
-
};
|
|
283
|
-
child.once("exit", handleExit);
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
async function waitForHealthAttempt(props) {
|
|
287
|
-
if (Date.now() - props.startedAt > props.timeoutMs) {
|
|
288
|
-
const message = props.lastError instanceof Error ? props.lastError.message : String(props.lastError);
|
|
289
|
-
throw new Error(`Timed out waiting for MCP Portal health: ${message}`);
|
|
290
|
-
}
|
|
291
|
-
try {
|
|
292
|
-
const response = await props.fetchFn(`http://${props.host}:${String(props.port)}/health`);
|
|
293
|
-
if (response.ok) return;
|
|
294
|
-
await delay(props.intervalMs);
|
|
295
|
-
return waitForHealthAttempt({
|
|
296
|
-
...props,
|
|
297
|
-
lastError: /* @__PURE__ */ new Error(`health returned ${String(response.status)}`)
|
|
298
|
-
});
|
|
299
|
-
} catch (error) {
|
|
300
|
-
await delay(props.intervalMs);
|
|
301
|
-
return waitForHealthAttempt({
|
|
302
|
-
...props,
|
|
303
|
-
lastError: error
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
async function waitForHealth(props) {
|
|
308
|
-
const startedAt = Date.now();
|
|
309
|
-
return waitForHealthAttempt({
|
|
310
|
-
...props,
|
|
311
|
-
lastError: void 0,
|
|
312
|
-
startedAt
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
function errorMessage(error) {
|
|
316
|
-
return error instanceof Error ? error.message : String(error);
|
|
317
|
-
}
|
|
318
|
-
function createPortalSubprocessSupervisor(props) {
|
|
319
|
-
const spawnFn = props.spawnFn ?? ((command, args, options) => spawn(command, [...args], options));
|
|
320
|
-
const fetchFn = props.fetchFn ?? fetch;
|
|
321
|
-
const healthPollIntervalMs = props.healthPollIntervalMs ?? 200;
|
|
322
|
-
const healthTimeoutMs = props.healthTimeoutMs ?? 1e4;
|
|
323
|
-
const stopGraceMs = props.stopGraceMs ?? 5e3;
|
|
324
|
-
const maxRestarts = props.maxRestarts ?? 5;
|
|
325
|
-
const backoffSteps = props.backoffSteps ?? defaultBackoffSteps;
|
|
326
|
-
let child = null;
|
|
327
|
-
let stopping = false;
|
|
328
|
-
let restartCount = 0;
|
|
329
|
-
const spawnChild = () => {
|
|
330
|
-
const nextChild = spawnFn(props.binPath, ["--config-dir", props.configDir], {
|
|
331
|
-
env: createPortalSubprocessEnv(props.hmacEnv, props.portalEnv),
|
|
332
|
-
stdio: [
|
|
333
|
-
"ignore",
|
|
334
|
-
"pipe",
|
|
335
|
-
"pipe"
|
|
336
|
-
]
|
|
337
|
-
});
|
|
338
|
-
let autoRestartEnabled = false;
|
|
339
|
-
let failureHandled = false;
|
|
340
|
-
let rejectEarlyFailure;
|
|
341
|
-
const earlyFailure = new Promise((_resolve, reject) => {
|
|
342
|
-
rejectEarlyFailure = reject;
|
|
343
|
-
});
|
|
344
|
-
const rejectBeforeHealth = (error) => {
|
|
345
|
-
if (rejectEarlyFailure === void 0) throw new Error("MCP Portal early-failure rejector was not initialized.");
|
|
346
|
-
rejectEarlyFailure(error);
|
|
347
|
-
};
|
|
348
|
-
child = nextChild;
|
|
349
|
-
nextChild.stdout?.on("data", (chunk) => {
|
|
350
|
-
logSubprocessOutput({
|
|
351
|
-
chunk,
|
|
352
|
-
logger: props.logger,
|
|
353
|
-
streamName: "stdout"
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
nextChild.stdout?.on("error", (error) => {
|
|
357
|
-
props.logger.warn(`[mcp-portal stdout] stream error: ${error.message}`);
|
|
358
|
-
});
|
|
359
|
-
nextChild.stderr?.on("data", (chunk) => {
|
|
360
|
-
logSubprocessOutput({
|
|
361
|
-
chunk,
|
|
362
|
-
logger: props.logger,
|
|
363
|
-
streamName: "stderr"
|
|
364
|
-
});
|
|
365
|
-
});
|
|
366
|
-
nextChild.stderr?.on("error", (error) => {
|
|
367
|
-
props.logger.warn(`[mcp-portal stderr] stream error: ${error.message}`);
|
|
368
|
-
});
|
|
369
|
-
nextChild.on("error", (error) => {
|
|
370
|
-
props.logger.error(`[mcp-portal] subprocess spawn failed: ${error.message}`);
|
|
371
|
-
if (failureHandled) return;
|
|
372
|
-
failureHandled = true;
|
|
373
|
-
if (child === nextChild) child = null;
|
|
374
|
-
if (stopping) return;
|
|
375
|
-
if (autoRestartEnabled) scheduleRestart();
|
|
376
|
-
else rejectBeforeHealth(error);
|
|
377
|
-
});
|
|
378
|
-
nextChild.on("exit", (code, signal) => {
|
|
379
|
-
if (failureHandled) return;
|
|
380
|
-
failureHandled = true;
|
|
381
|
-
if (child === nextChild) child = null;
|
|
382
|
-
if (stopping) return;
|
|
383
|
-
if (autoRestartEnabled) scheduleRestart();
|
|
384
|
-
else rejectBeforeHealth(/* @__PURE__ */ new Error(`MCP Portal subprocess exited before health check completed (code=${String(code)} signal=${String(signal)}).`));
|
|
385
|
-
});
|
|
386
|
-
return {
|
|
387
|
-
child: nextChild,
|
|
388
|
-
earlyFailure,
|
|
389
|
-
enableAutoRestart: () => {
|
|
390
|
-
autoRestartEnabled = true;
|
|
391
|
-
}
|
|
392
|
-
};
|
|
393
|
-
};
|
|
394
|
-
const spawnChildAndWaitForHealth = async () => {
|
|
395
|
-
const spawnedChild = spawnChild();
|
|
396
|
-
try {
|
|
397
|
-
await Promise.race([waitForHealth({
|
|
398
|
-
fetchFn,
|
|
399
|
-
host: props.host,
|
|
400
|
-
intervalMs: healthPollIntervalMs,
|
|
401
|
-
port: props.port,
|
|
402
|
-
timeoutMs: healthTimeoutMs
|
|
403
|
-
}), spawnedChild.earlyFailure]);
|
|
404
|
-
} catch (error) {
|
|
405
|
-
if (child === spawnedChild.child) {
|
|
406
|
-
child = null;
|
|
407
|
-
if (!spawnedChild.child.killed) spawnedChild.child.kill("SIGTERM");
|
|
408
|
-
}
|
|
409
|
-
throw error;
|
|
410
|
-
}
|
|
411
|
-
spawnedChild.enableAutoRestart();
|
|
412
|
-
restartCount = 0;
|
|
413
|
-
props.logger.info("[mcp-portal] subprocess is healthy.");
|
|
414
|
-
};
|
|
415
|
-
const scheduleRestart = async () => {
|
|
416
|
-
restartCount += 1;
|
|
417
|
-
if (restartCount > maxRestarts) {
|
|
418
|
-
props.logger.error("[mcp-portal] subprocess restart limit exhausted.");
|
|
419
|
-
props.onFatal?.("backoff-exhausted");
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
const backoffMs = backoffSteps[Math.min(restartCount - 1, backoffSteps.length - 1)] ?? backoffSteps[backoffSteps.length - 1] ?? 5e3;
|
|
423
|
-
props.logger.warn(`[mcp-portal] subprocess exited; restarting in ${String(backoffMs)}ms.`);
|
|
424
|
-
await delay(backoffMs);
|
|
425
|
-
if (stopping) return;
|
|
426
|
-
try {
|
|
427
|
-
await spawnChildAndWaitForHealth();
|
|
428
|
-
} catch (error) {
|
|
429
|
-
props.logger.error(`[mcp-portal] subprocess restart failed: ${errorMessage(error)}`);
|
|
430
|
-
if (!stopping) await scheduleRestart();
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
return {
|
|
434
|
-
isAlive: () => child !== null && !child.killed,
|
|
435
|
-
start: async () => {
|
|
436
|
-
stopping = false;
|
|
437
|
-
await spawnChildAndWaitForHealth();
|
|
438
|
-
},
|
|
439
|
-
stop: async () => {
|
|
440
|
-
stopping = true;
|
|
441
|
-
const activeChild = child;
|
|
442
|
-
child = null;
|
|
443
|
-
if (activeChild === null || activeChild.killed) return;
|
|
444
|
-
activeChild.kill("SIGTERM");
|
|
445
|
-
if (!await waitForExit(activeChild, stopGraceMs) && !activeChild.killed) activeChild.kill("SIGKILL");
|
|
446
177
|
}
|
|
447
178
|
};
|
|
448
179
|
}
|
|
449
180
|
//#endregion
|
|
450
181
|
//#region src/plugin-registration.ts
|
|
451
182
|
const pluginId = "mcp-portal";
|
|
452
|
-
const onePasswordCliEnvNames = [
|
|
453
|
-
"OP_SERVICE_ACCOUNT_TOKEN",
|
|
454
|
-
"OP_ACCOUNT",
|
|
455
|
-
"OP_CONNECT_HOST",
|
|
456
|
-
"OP_CONNECT_TOKEN"
|
|
457
|
-
];
|
|
458
183
|
function hasFunction(value) {
|
|
459
184
|
return typeof value === "function";
|
|
460
185
|
}
|
|
@@ -467,80 +192,16 @@ function isUnknownArray(value) {
|
|
|
467
192
|
function getObjectProperty(value, property) {
|
|
468
193
|
return isObjectRecord(value) ? value[property] : void 0;
|
|
469
194
|
}
|
|
470
|
-
function messageFromUnknown(error) {
|
|
471
|
-
return error instanceof Error ? error.message : String(error);
|
|
472
|
-
}
|
|
473
|
-
function addEnvironmentSecretName(names, secret) {
|
|
474
|
-
if (secret.source === "environment") names.add(secret.name);
|
|
475
|
-
}
|
|
476
|
-
function secretUsesOnePassword(secret) {
|
|
477
|
-
return secret.source === "1password";
|
|
478
|
-
}
|
|
479
|
-
function collectMcpConfigEnvironmentSecretNames(config) {
|
|
480
|
-
const names = /* @__PURE__ */ new Set();
|
|
481
|
-
for (const provider of Object.values(config.providers)) {
|
|
482
|
-
const transport = provider.transport;
|
|
483
|
-
const secrets = transport.kind === "stdio" ? Object.values(transport.env) : Object.values(transport.headers);
|
|
484
|
-
for (const secret of secrets) addEnvironmentSecretName(names, secret);
|
|
485
|
-
}
|
|
486
|
-
return names;
|
|
487
|
-
}
|
|
488
|
-
function collectMcpPortalConfigEnvironmentSecretNames(config) {
|
|
489
|
-
const names = /* @__PURE__ */ new Set();
|
|
490
|
-
addEnvironmentSecretName(names, config.server.accessHeader.secret);
|
|
491
|
-
for (const agent of Object.values(config.agents)) if (agent.hmacKey !== void 0) addEnvironmentSecretName(names, agent.hmacKey);
|
|
492
|
-
return names;
|
|
493
|
-
}
|
|
494
|
-
function mcpConfigUsesOnePassword(config) {
|
|
495
|
-
return Object.values(config.providers).some((provider) => {
|
|
496
|
-
const transport = provider.transport;
|
|
497
|
-
return (transport.kind === "stdio" ? Object.values(transport.env) : Object.values(transport.headers)).some(secretUsesOnePassword);
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
function mcpPortalConfigUsesOnePassword(config) {
|
|
501
|
-
return secretUsesOnePassword(config.server.accessHeader.secret) || Object.values(config.agents).some((agent) => agent.hmacKey !== void 0 && secretUsesOnePassword(agent.hmacKey));
|
|
502
|
-
}
|
|
503
|
-
function resolveRequiredPortalEnv(props) {
|
|
504
|
-
const resolvedEnv = {};
|
|
505
|
-
for (const name of [...props.names].toSorted()) {
|
|
506
|
-
const value = props.env[name];
|
|
507
|
-
if (value === void 0 || value.length === 0) throw new Error(`Missing environment secret ${name} for MCP Portal subprocess.`);
|
|
508
|
-
resolvedEnv[name] = value;
|
|
509
|
-
}
|
|
510
|
-
return resolvedEnv;
|
|
511
|
-
}
|
|
512
|
-
function createPortalSubprocessConfigEnv(props) {
|
|
513
|
-
const env = props.env ?? process.env;
|
|
514
|
-
const portalEnv = { ...resolveRequiredPortalEnv({
|
|
515
|
-
env,
|
|
516
|
-
names: new Set([...collectMcpConfigEnvironmentSecretNames(props.mcpConfig), ...collectMcpPortalConfigEnvironmentSecretNames(props.mcpPortalConfig)])
|
|
517
|
-
}) };
|
|
518
|
-
if (mcpConfigUsesOnePassword(props.mcpConfig) || mcpPortalConfigUsesOnePassword(props.mcpPortalConfig)) for (const name of onePasswordCliEnvNames) {
|
|
519
|
-
const value = env[name];
|
|
520
|
-
if (value !== void 0 && value.length > 0) portalEnv[name] = value;
|
|
521
|
-
}
|
|
522
|
-
return portalEnv;
|
|
523
|
-
}
|
|
524
195
|
function resolveConfigDir(api) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const topLevelMcpConfigDir = getObjectProperty(getObjectProperty(api.config, "mcp"), "configDir");
|
|
196
|
+
if (api.pluginConfig !== void 0) return parsePortalConfig(api.pluginConfig).configDir;
|
|
197
|
+
const topLevelMcpConfigDir = getObjectProperty(getObjectProperty(api.config, "mcpPortal"), "configDir");
|
|
528
198
|
if (typeof topLevelMcpConfigDir === "string" && topLevelMcpConfigDir.length > 0) return topLevelMcpConfigDir;
|
|
529
199
|
const zones = getObjectProperty(api.config, "zones");
|
|
530
200
|
if (isUnknownArray(zones)) {
|
|
531
|
-
const zoneMcpConfigDir = getObjectProperty(getObjectProperty(zones.at(0), "
|
|
201
|
+
const zoneMcpConfigDir = getObjectProperty(getObjectProperty(zones.at(0), "mcpPortal"), "configDir");
|
|
532
202
|
if (typeof zoneMcpConfigDir === "string" && zoneMcpConfigDir.length > 0) return zoneMcpConfigDir;
|
|
533
203
|
}
|
|
534
|
-
throw new Error("MCP Portal plugin requires configDir in plugin config or zone
|
|
535
|
-
}
|
|
536
|
-
function tcpPoolConfigFromApi(api) {
|
|
537
|
-
const tcpPool = getObjectProperty(api.config, "tcpPool");
|
|
538
|
-
const basePort = getObjectProperty(tcpPool, "basePort");
|
|
539
|
-
const size = getObjectProperty(tcpPool, "size");
|
|
540
|
-
return typeof basePort === "number" && typeof size === "number" ? {
|
|
541
|
-
basePort,
|
|
542
|
-
size
|
|
543
|
-
} : null;
|
|
204
|
+
throw new Error("MCP Portal plugin requires configDir in plugin config or zone mcpPortal config.");
|
|
544
205
|
}
|
|
545
206
|
function validatePortalPortAgainstTcpPool(props) {
|
|
546
207
|
if (props.tcpPool === null) return;
|
|
@@ -556,8 +217,8 @@ function createLoggerAdapter(api) {
|
|
|
556
217
|
};
|
|
557
218
|
}
|
|
558
219
|
function validatePortalPluginApi(api) {
|
|
559
|
-
if (!hasFunction(api.
|
|
560
|
-
if (!hasFunction(api.on)
|
|
220
|
+
if (!hasFunction(api.registerTool)) throw new Error("MCP Portal plugin requires OpenClaw registerTool API.");
|
|
221
|
+
if (!hasFunction(api.on)) throw new Error("MCP Portal plugin requires OpenClaw before_tool_call hook API.");
|
|
561
222
|
if (hasFunction(api.lifecycle?.registerRuntimeLifecycle) || hasFunction(api.registerRuntimeLifecycle)) return;
|
|
562
223
|
throw new Error("MCP Portal plugin requires an OpenClaw lifecycle cleanup API.");
|
|
563
224
|
}
|
|
@@ -566,8 +227,8 @@ function registerPortalRuntimeCleanup(api, cleanup) {
|
|
|
566
227
|
cleanup: async () => {
|
|
567
228
|
await cleanup();
|
|
568
229
|
},
|
|
569
|
-
description: "
|
|
570
|
-
id: "mcp-portal-
|
|
230
|
+
description: "Closes MCP Portal upstream clients owned by the agent-vm plugin.",
|
|
231
|
+
id: "mcp-portal-core"
|
|
571
232
|
};
|
|
572
233
|
if (hasFunction(api.lifecycle?.registerRuntimeLifecycle)) {
|
|
573
234
|
api.lifecycle.registerRuntimeLifecycle(runtimeLifecycle);
|
|
@@ -579,53 +240,163 @@ function registerPortalRuntimeCleanup(api, cleanup) {
|
|
|
579
240
|
}
|
|
580
241
|
throw new Error("MCP Portal plugin requires an OpenClaw lifecycle cleanup API.");
|
|
581
242
|
}
|
|
582
|
-
function
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
243
|
+
function selectorsFromNamespaceTools(namespaceTools) {
|
|
244
|
+
return Object.entries(namespaceTools).flatMap(([namespace, toolNames]) => toolNames.map((toolName) => ({
|
|
245
|
+
namespace,
|
|
246
|
+
toolName
|
|
247
|
+
})));
|
|
248
|
+
}
|
|
249
|
+
function buildProfilePolicyMaps(portalConfig) {
|
|
250
|
+
const enabledNamespacesByAgent = {};
|
|
251
|
+
const enabledToolsByAgent = {};
|
|
252
|
+
const hiddenToolsByAgent = {};
|
|
253
|
+
const profileTtls = [];
|
|
254
|
+
for (const [agentId, agent] of Object.entries(portalConfig.agents)) {
|
|
255
|
+
const profile = resolveMcpPortalProfile(portalConfig, agent.profile);
|
|
256
|
+
enabledNamespacesByAgent[agentId] = profile.enabledNamespaces;
|
|
257
|
+
enabledToolsByAgent[agentId] = selectorsFromNamespaceTools(profile.enabledToolsByNamespace);
|
|
258
|
+
hiddenToolsByAgent[agentId] = selectorsFromNamespaceTools(profile.hiddenToolsByNamespace);
|
|
259
|
+
profileTtls.push(profile.cache.catalogTtlMs);
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
cacheTtlMs: profileTtls.length === 0 ? 6e4 : Math.min(...profileTtls),
|
|
263
|
+
enabledNamespacesByAgent,
|
|
264
|
+
enabledToolsByAgent,
|
|
265
|
+
hiddenToolsByAgent
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
async function resolveManagedPortalSecret(secret) {
|
|
269
|
+
if (secret.source !== "environment") throw new Error("MCP Portal managed OpenClaw effective config must use environment secret refs.");
|
|
270
|
+
const value = process.env[secret.name];
|
|
271
|
+
if (value === void 0 || value.length === 0) throw new Error(`Missing environment secret ${secret.name} for MCP Portal native plugin.`);
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
async function createManagedPortalCore(configDir) {
|
|
275
|
+
const effectiveConfigPaths = await resolveEffectiveConfigPaths(configDir);
|
|
276
|
+
const [mcpConfig, portalConfig] = await Promise.all([loadMcpConfig(effectiveConfigPaths.mcpConfigPath), loadMcpPortalConfig(effectiveConfigPaths.portalConfigPath)]);
|
|
277
|
+
const upstreamServers = await resolveUpstreamServers({
|
|
278
|
+
config: mcpConfig,
|
|
279
|
+
resolveSecret: resolveManagedPortalSecret
|
|
280
|
+
});
|
|
281
|
+
const upstreamRuntime = createUpstreamMcpClientRuntime({ servers: upstreamServers });
|
|
282
|
+
const profilePolicyMaps = buildProfilePolicyMaps(portalConfig);
|
|
283
|
+
return createPortalCore({
|
|
284
|
+
accessPolicy: {
|
|
285
|
+
defaultPolicy: "deny-all",
|
|
286
|
+
enabledNamespacesByAgent: profilePolicyMaps.enabledNamespacesByAgent,
|
|
287
|
+
enabledToolsByAgent: profilePolicyMaps.enabledToolsByAgent,
|
|
288
|
+
hiddenToolsByAgent: profilePolicyMaps.hiddenToolsByAgent
|
|
289
|
+
},
|
|
290
|
+
approvalTrustBoundary: "openclaw-before-tool-call-hook",
|
|
291
|
+
catalogTtlMs: profilePolicyMaps.cacheTtlMs,
|
|
292
|
+
runtime: {
|
|
293
|
+
callUpstreamTool: upstreamRuntime.callTool,
|
|
294
|
+
closeAgentScope: upstreamRuntime.closeAgentScope,
|
|
295
|
+
closeSession: upstreamRuntime.closeSession,
|
|
296
|
+
listTools: upstreamRuntime.listTools
|
|
297
|
+
},
|
|
298
|
+
upstreamNamespaces: upstreamServers.map((server) => server.namespace)
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
function portalUpdateFromCoreEvent(event) {
|
|
302
|
+
if (event.kind === "progress") return {
|
|
303
|
+
message: event.message ?? "MCP Portal progress",
|
|
304
|
+
...event.progress !== void 0 ? { progress: event.progress } : {},
|
|
305
|
+
requestId: event.requestId,
|
|
306
|
+
...event.total !== void 0 ? { total: event.total } : {},
|
|
307
|
+
type: "mcp_portal_progress"
|
|
308
|
+
};
|
|
309
|
+
if (event.kind === "partial_content") return {
|
|
310
|
+
content: event.content,
|
|
311
|
+
requestId: event.requestId,
|
|
312
|
+
type: "mcp_portal_partial_content"
|
|
313
|
+
};
|
|
314
|
+
if (event.kind === "upstream_notification") return {
|
|
315
|
+
method: event.method,
|
|
316
|
+
params: event.params,
|
|
317
|
+
requestId: event.requestId,
|
|
318
|
+
type: "mcp_portal_upstream_notification"
|
|
319
|
+
};
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
async function forwardCoreEvent(event, logger, onUpdate) {
|
|
323
|
+
const update = portalUpdateFromCoreEvent(event);
|
|
324
|
+
if (update !== null) try {
|
|
325
|
+
await onUpdate?.(update);
|
|
326
|
+
} catch (error) {
|
|
327
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
328
|
+
logger.warn(`[mcp-portal] OpenClaw onUpdate delivery failed: ${message}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function createNativeTool(props) {
|
|
332
|
+
return {
|
|
333
|
+
description: props.descriptor.description,
|
|
334
|
+
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
335
|
+
if (props.context.agentId === void 0 || props.context.agentId.length === 0) throw new Error("mcp-portal: OpenClaw did not provide a trusted agentId.");
|
|
336
|
+
const core = await props.getCore();
|
|
337
|
+
const scope = core.createAgentScope({
|
|
338
|
+
agentId: props.context.agentId,
|
|
339
|
+
agentScopeId: props.context.agentId,
|
|
340
|
+
...props.context.sessionId ? { sessionId: props.context.sessionId } : {},
|
|
341
|
+
...props.context.sessionKey ? { sessionKey: props.context.sessionKey } : {},
|
|
342
|
+
source: "openclaw-trusted"
|
|
611
343
|
});
|
|
612
|
-
await
|
|
613
|
-
|
|
344
|
+
const result = await core.collectPortalCoreResult(core.callStream({
|
|
345
|
+
input: params,
|
|
346
|
+
scope,
|
|
347
|
+
...signal !== void 0 ? { signal } : {},
|
|
348
|
+
toolName: props.descriptor.name
|
|
349
|
+
}), { onEvent: (event) => forwardCoreEvent(event, props.logger, onUpdate) });
|
|
350
|
+
return {
|
|
351
|
+
content: JSON.stringify(result),
|
|
352
|
+
details: result
|
|
353
|
+
};
|
|
614
354
|
},
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
355
|
+
label: props.descriptor.name,
|
|
356
|
+
name: props.descriptor.name,
|
|
357
|
+
parameters: props.descriptor.inputSchema
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function descriptorsForOpenClawContext(props) {
|
|
361
|
+
if (props.portalConfig === null || props.context.agentId === void 0) return listPortalCoreToolDescriptors();
|
|
362
|
+
const agent = props.portalConfig.agents[props.context.agentId];
|
|
363
|
+
if (agent === void 0) return listPortalCoreToolDescriptors();
|
|
364
|
+
return listPortalCoreToolDescriptors(resolveMcpPortalProfile(props.portalConfig, agent.profile).enabledNamespaces);
|
|
365
|
+
}
|
|
366
|
+
function registerNativePortalTools(props) {
|
|
367
|
+
const descriptorNames = listPortalCoreToolDescriptors().map((descriptor) => descriptor.name);
|
|
368
|
+
const logger = createLoggerAdapter(props.api);
|
|
369
|
+
props.api.registerTool?.((context) => {
|
|
370
|
+
return descriptorsForOpenClawContext({
|
|
371
|
+
context,
|
|
372
|
+
portalConfig: props.runtimeState.getLoadedPortalConfig()
|
|
373
|
+
}).map((descriptor) => createNativeTool({
|
|
374
|
+
context,
|
|
375
|
+
descriptor,
|
|
376
|
+
getCore: props.getCore,
|
|
377
|
+
logger
|
|
378
|
+
}));
|
|
379
|
+
}, {
|
|
380
|
+
names: descriptorNames,
|
|
381
|
+
optional: true
|
|
618
382
|
});
|
|
619
|
-
return { getSupervisor: () => supervisor };
|
|
620
383
|
}
|
|
621
384
|
function registerMcpPortalPlugin(api) {
|
|
622
385
|
if (api.registrationMode !== void 0 && api.registrationMode !== "full") return;
|
|
623
386
|
validatePortalPluginApi(api);
|
|
624
387
|
const configDir = resolveConfigDir(api);
|
|
625
388
|
const runtimeState = createPortalPluginRuntimeState({ configDir });
|
|
626
|
-
|
|
389
|
+
let corePromise;
|
|
390
|
+
const getCore = () => {
|
|
391
|
+
corePromise ??= createManagedPortalCore(configDir).catch((error) => {
|
|
392
|
+
corePromise = void 0;
|
|
393
|
+
throw error;
|
|
394
|
+
});
|
|
395
|
+
return corePromise;
|
|
396
|
+
};
|
|
397
|
+
registerNativePortalTools({
|
|
627
398
|
api,
|
|
628
|
-
|
|
399
|
+
getCore,
|
|
629
400
|
runtimeState
|
|
630
401
|
});
|
|
631
402
|
api.on?.("before_tool_call", createBeforeToolCallHandler({
|
|
@@ -637,13 +408,12 @@ function registerMcpPortalPlugin(api) {
|
|
|
637
408
|
const result = await createBeforePromptBuildHandler({ runtimeState })({}, context);
|
|
638
409
|
if (result?.appendSystemContext !== void 0) context.appendPrompt?.(result.appendSystemContext);
|
|
639
410
|
});
|
|
640
|
-
registerPortalRuntimeCleanup(api, () =>
|
|
641
|
-
|
|
642
|
-
api.logger?.error?.(`[mcp-portal] failed to initialize portal config: ${messageFromUnknown(error)}`);
|
|
411
|
+
registerPortalRuntimeCleanup(api, async () => {
|
|
412
|
+
await (await corePromise?.catch(() => void 0))?.close();
|
|
643
413
|
});
|
|
644
414
|
}
|
|
645
415
|
const pluginEntry = {
|
|
646
|
-
description: "
|
|
416
|
+
description: "Registers native OpenClaw MCP Portal tools and wires per-agent approval hooks.",
|
|
647
417
|
id: pluginId,
|
|
648
418
|
name: "MCP Portal",
|
|
649
419
|
register: registerMcpPortalPlugin
|
|
@@ -654,7 +424,7 @@ function createPortalPromptContext(props) {
|
|
|
654
424
|
const namespaceList = props.namespaces.length > 0 ? props.namespaces.map((entry) => `${entry.namespace}(${entry.toolCount} tools)`).join(", ") : "none configured";
|
|
655
425
|
const diagnostics = props.diagnostics !== void 0 && props.diagnostics.length > 0 ? [`Discovery diagnostics: ${props.diagnostics.map((entry) => `${entry.namespace}: ${entry.message}`).join("; ")}`] : [];
|
|
656
426
|
return [
|
|
657
|
-
"MCP Portal is available as
|
|
427
|
+
"MCP Portal is available as native OpenClaw tools.",
|
|
658
428
|
"Use mcp_portal_list with requests[], mcp_portal_search with requests[],",
|
|
659
429
|
"mcp_portal_describe with requests[], and mcp_portal_call with calls[].",
|
|
660
430
|
"Responses are { ok, results, errors, diagnostics }; results is keyed by each request/call id and each value is discriminated by ok: true or ok: false.",
|
|
@@ -674,6 +444,6 @@ function redactPortalSecrets(text, secretValues = []) {
|
|
|
674
444
|
//#region src/index.ts
|
|
675
445
|
const OPENCLAW_MCP_PORTAL_PLUGIN_PACKAGE_NAME = "@agent-vm/openclaw-mcp-portal-plugin";
|
|
676
446
|
//#endregion
|
|
677
|
-
export { OPENCLAW_MCP_PORTAL_PLUGIN_PACKAGE_NAME, createBeforePromptBuildHandler, createBeforeToolCallHandler,
|
|
447
|
+
export { OPENCLAW_MCP_PORTAL_PLUGIN_PACKAGE_NAME, createBeforePromptBuildHandler, createBeforeToolCallHandler, createPortalPluginRuntimeState, createPortalPromptContext, pluginEntry as default, parsePortalConfig, portalPluginConfigSchema, profileAllowsPortalCall, profileRequiresPortalApproval, redactPortalSecrets, registerMcpPortalPlugin, validatePortalPluginApi, validatePortalPortAgainstTcpPool };
|
|
678
448
|
|
|
679
449
|
//# sourceMappingURL=index.js.map
|