@agent-vm/openclaw-mcp-portal-plugin 0.0.68 → 0.0.70
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 +26 -50
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +214 -381
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +10 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
# @agent-vm/openclaw-mcp-portal-plugin
|
|
2
2
|
|
|
3
|
-
OpenClaw plugin that
|
|
4
|
-
into the OpenClaw agent loop.
|
|
3
|
+
OpenClaw plugin that registers native MCP Portal tools and wires portal calls
|
|
4
|
+
directly into the OpenClaw agent loop.
|
|
5
5
|
|
|
6
6
|
## What This Package Owns
|
|
7
7
|
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
approval tokens to approved calls.
|
|
8
|
+
- Registers native OpenClaw tools for `mcp_portal_list`,
|
|
9
|
+
`mcp_portal_search`, `mcp_portal_describe`, and `mcp_portal_call`.
|
|
10
|
+
- Loads `/core` from the controller-materialized effective config directory.
|
|
11
|
+
- Registers `before_tool_call` to deny disallowed portal calls and request
|
|
12
|
+
OpenClaw approval when policy requires it.
|
|
14
13
|
- Registers `before_prompt_build` to inject scoped progressive-disclosure hints.
|
|
15
14
|
|
|
16
15
|
## Runtime Config
|
|
17
16
|
|
|
18
|
-
The OpenClaw plugin config should only carry
|
|
17
|
+
The OpenClaw plugin config should only carry the effective config directory:
|
|
19
18
|
|
|
20
19
|
```json
|
|
21
20
|
{
|
|
22
|
-
"configDir": "/home/openclaw/.openclaw/
|
|
23
|
-
"binPath": "/opt/agent-vm/portal/bin/agent-vm-mcp-portal-server"
|
|
21
|
+
"configDir": "/home/openclaw/.openclaw/cache/mcp-portal-effective"
|
|
24
22
|
}
|
|
25
23
|
```
|
|
26
24
|
|
|
@@ -29,7 +27,6 @@ Namespace/tool policy does not live in OpenClaw plugin config. It lives in
|
|
|
29
27
|
|
|
30
28
|
## Start Reading
|
|
31
29
|
|
|
32
|
-
- `src/plugin-registration.ts` for
|
|
33
|
-
- `src/
|
|
34
|
-
- `src/before-tool-call-handler.ts` for policy and approval-token behavior.
|
|
30
|
+
- `src/plugin-registration.ts` for native tool and hook registration.
|
|
31
|
+
- `src/before-tool-call-handler.ts` for policy and approval behavior.
|
|
35
32
|
- `src/before-prompt-build-handler.ts` for prompt context injection.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { McpPortalConfig, ResolvedMcpPortalProfile } from "@agent-vm/config-contracts";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { ChildProcess, SpawnOptions } from "node:child_process";
|
|
4
3
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
5
4
|
|
|
6
5
|
//#region src/openclaw-plugin-api.d.ts
|
|
@@ -8,6 +7,24 @@ interface OpenClawPromptHookContext {
|
|
|
8
7
|
readonly agentId?: string;
|
|
9
8
|
readonly appendPrompt?: (content: string) => void;
|
|
10
9
|
}
|
|
10
|
+
interface OpenClawPluginToolContext {
|
|
11
|
+
readonly agentId?: string;
|
|
12
|
+
readonly sessionId?: string;
|
|
13
|
+
readonly sessionKey?: string;
|
|
14
|
+
}
|
|
15
|
+
type OpenClawToolUpdateCallback = (update: unknown) => Promise<void> | void;
|
|
16
|
+
interface OpenClawToolRegistrationResult {
|
|
17
|
+
readonly content: string;
|
|
18
|
+
readonly details?: unknown;
|
|
19
|
+
}
|
|
20
|
+
interface OpenClawToolRegistration {
|
|
21
|
+
readonly description: string;
|
|
22
|
+
readonly execute: (toolCallId: string, params: unknown, signal?: AbortSignal, onUpdate?: OpenClawToolUpdateCallback) => Promise<OpenClawToolRegistrationResult>;
|
|
23
|
+
readonly label?: string;
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly parameters: unknown;
|
|
26
|
+
}
|
|
27
|
+
type OpenClawToolFactory = (context: OpenClawPluginToolContext) => OpenClawToolRegistration | readonly OpenClawToolRegistration[] | null | undefined;
|
|
11
28
|
interface OpenClawPluginHookContext {
|
|
12
29
|
readonly agentId?: string;
|
|
13
30
|
readonly sessionId?: string;
|
|
@@ -98,6 +115,11 @@ interface OpenClawPortalPluginApi {
|
|
|
98
115
|
};
|
|
99
116
|
readonly pluginConfig?: unknown;
|
|
100
117
|
readonly registrationMode?: string;
|
|
118
|
+
readonly registerTool?: (tool: OpenClawToolRegistration | OpenClawToolFactory, options?: {
|
|
119
|
+
readonly name?: string;
|
|
120
|
+
readonly names?: readonly string[];
|
|
121
|
+
readonly optional?: boolean;
|
|
122
|
+
}) => void;
|
|
101
123
|
readonly registerRuntimeLifecycle?: OpenClawRuntimeLifecycleRegistrar;
|
|
102
124
|
readonly registerService?: (service: OpenClawPluginService) => void;
|
|
103
125
|
readonly on?: <THookName extends keyof OpenClawPluginHookEventMap>(hookName: THookName, handler: (event: OpenClawPluginHookEventMap[THookName], context: OpenClawPluginHookContext) => OpenClawPluginHookResultMap[THookName] | Promise<OpenClawPluginHookResultMap[THookName] | void> | void, options?: OpenClawPluginHookOptions) => void;
|
|
@@ -123,26 +145,14 @@ declare const pluginEntry: {
|
|
|
123
145
|
register: typeof registerMcpPortalPlugin;
|
|
124
146
|
};
|
|
125
147
|
//#endregion
|
|
126
|
-
//#region src/hmac-key-registry.d.ts
|
|
127
|
-
interface CreateHmacKeyRegistryProps {
|
|
128
|
-
readonly agentIds: readonly string[];
|
|
129
|
-
}
|
|
130
|
-
interface HmacKeyRegistry {
|
|
131
|
-
readonly agentIds: readonly string[];
|
|
132
|
-
readonly getKey: (agentId: string) => Buffer;
|
|
133
|
-
readonly serializeForEnv: () => Readonly<Record<string, string>>;
|
|
134
|
-
}
|
|
135
|
-
declare function createHmacKeyRegistry(props: CreateHmacKeyRegistryProps): HmacKeyRegistry;
|
|
136
|
-
//#endregion
|
|
137
148
|
//#region src/portal-plugin-runtime-state.d.ts
|
|
138
149
|
interface PortalPluginRuntimeState {
|
|
139
150
|
readonly configDir: string;
|
|
151
|
+
readonly getLoadedPortalConfig: () => McpPortalConfig | null;
|
|
140
152
|
readonly getPortalUnavailableReason: () => string | null;
|
|
141
|
-
readonly getKeyRegistry: () => HmacKeyRegistry;
|
|
142
153
|
readonly loadPortalConfig: () => Promise<McpPortalConfig>;
|
|
143
154
|
readonly markPortalAvailable: () => void;
|
|
144
155
|
readonly markPortalUnavailable: (reason: string) => void;
|
|
145
|
-
readonly setKeyRegistry: (registry: HmacKeyRegistry) => void;
|
|
146
156
|
}
|
|
147
157
|
declare function createPortalPluginRuntimeState(props: {
|
|
148
158
|
readonly configDir: string;
|
|
@@ -165,44 +175,12 @@ interface CreateBeforeToolCallHandlerProps {
|
|
|
165
175
|
declare function createBeforeToolCallHandler(props: CreateBeforeToolCallHandlerProps): (event: OpenClawBeforeToolCallEvent, context: OpenClawPluginHookContext) => Promise<OpenClawBeforeToolCallResult | undefined>;
|
|
166
176
|
//#endregion
|
|
167
177
|
//#region src/portal-config.d.ts
|
|
168
|
-
declare const defaultPortalBinPath = "/opt/agent-vm/portal/bin/agent-vm-mcp-portal-server";
|
|
169
178
|
declare const portalPluginConfigSchema: z.ZodObject<{
|
|
170
|
-
|
|
171
|
-
configDir: z.ZodOptional<z.ZodString>;
|
|
179
|
+
configDir: z.ZodString;
|
|
172
180
|
}, z.core.$strict>;
|
|
173
181
|
type PortalPluginConfig = z.infer<typeof portalPluginConfigSchema>;
|
|
174
182
|
declare function parsePortalConfig(value: unknown): PortalPluginConfig;
|
|
175
183
|
//#endregion
|
|
176
|
-
//#region src/portal-subprocess-supervisor.d.ts
|
|
177
|
-
interface PortalSubprocessLogger {
|
|
178
|
-
readonly error: (message: string) => void;
|
|
179
|
-
readonly info: (message: string) => void;
|
|
180
|
-
readonly warn: (message: string) => void;
|
|
181
|
-
}
|
|
182
|
-
type PortalSubprocessSpawnFunction = (command: string, args: readonly string[], options: SpawnOptions) => ChildProcess;
|
|
183
|
-
interface CreatePortalSubprocessSupervisorProps {
|
|
184
|
-
readonly backoffSteps?: readonly number[];
|
|
185
|
-
readonly binPath: string;
|
|
186
|
-
readonly configDir: string;
|
|
187
|
-
readonly fetchFn?: typeof fetch;
|
|
188
|
-
readonly healthPollIntervalMs?: number;
|
|
189
|
-
readonly healthTimeoutMs?: number;
|
|
190
|
-
readonly host: string;
|
|
191
|
-
readonly hmacEnv: Readonly<Record<string, string>>;
|
|
192
|
-
readonly logger: PortalSubprocessLogger;
|
|
193
|
-
readonly maxRestarts?: number;
|
|
194
|
-
readonly onFatal?: (reason: string) => void;
|
|
195
|
-
readonly port: number;
|
|
196
|
-
readonly spawnFn?: PortalSubprocessSpawnFunction;
|
|
197
|
-
readonly stopGraceMs?: number;
|
|
198
|
-
}
|
|
199
|
-
interface PortalSubprocessSupervisor {
|
|
200
|
-
readonly isAlive: () => boolean;
|
|
201
|
-
readonly start: () => Promise<void>;
|
|
202
|
-
readonly stop: () => Promise<void>;
|
|
203
|
-
}
|
|
204
|
-
declare function createPortalSubprocessSupervisor(props: CreatePortalSubprocessSupervisorProps): PortalSubprocessSupervisor;
|
|
205
|
-
//#endregion
|
|
206
184
|
//#region src/portal-tool-policy.d.ts
|
|
207
185
|
interface PortalCallRequest {
|
|
208
186
|
readonly arguments: Record<string, unknown>;
|
|
@@ -210,8 +188,6 @@ interface PortalCallRequest {
|
|
|
210
188
|
readonly namespace: string;
|
|
211
189
|
readonly toolName: string;
|
|
212
190
|
}
|
|
213
|
-
declare function portalServerNameForAgent(agentId: string): string;
|
|
214
|
-
declare function materializedPortalToolNames(serverName: string): readonly string[];
|
|
215
191
|
declare function profileAllowsPortalCall(profile: ResolvedMcpPortalProfile, call: {
|
|
216
192
|
readonly namespace: string;
|
|
217
193
|
readonly toolName: string;
|
|
@@ -241,5 +217,5 @@ declare function redactPortalSecrets(text: string, secretValues?: readonly strin
|
|
|
241
217
|
//#region src/index.d.ts
|
|
242
218
|
declare const OPENCLAW_MCP_PORTAL_PLUGIN_PACKAGE_NAME = "@agent-vm/openclaw-mcp-portal-plugin";
|
|
243
219
|
//#endregion
|
|
244
|
-
export { CreateBeforePromptBuildHandlerProps, CreateBeforeToolCallHandlerProps,
|
|
220
|
+
export { CreateBeforePromptBuildHandlerProps, CreateBeforeToolCallHandlerProps, OPENCLAW_MCP_PORTAL_PLUGIN_PACKAGE_NAME, OpenClawAgentTurnPrepareEvent, OpenClawApprovalResolution, OpenClawBeforePromptBuildEvent, OpenClawBeforeToolCallEvent, OpenClawBeforeToolCallResult, OpenClawHttpRouteRegistration, OpenClawPluginHookContext, OpenClawPluginHookEventMap, OpenClawPluginHookOptions, OpenClawPluginHookResultMap, OpenClawPluginHostCleanupReason, OpenClawPluginService, OpenClawPluginToolContext, OpenClawPortalPluginApi, OpenClawPromptHookContext, OpenClawPromptHookResult, OpenClawRuntimeLifecycleRegistrar, OpenClawRuntimeLifecycleRegistration, OpenClawToolFactory, OpenClawToolRegistration, OpenClawToolRegistrationResult, OpenClawToolUpdateCallback, PortalCallRequest, PortalPluginConfig, PortalPluginRuntimeState, PortalPromptDiagnostic, PortalPromptNamespaceSummary, createBeforePromptBuildHandler, createBeforeToolCallHandler, createPortalPluginRuntimeState, createPortalPromptContext, pluginEntry as default, parsePortalConfig, portalPluginConfigSchema, profileAllowsPortalCall, profileRequiresPortalApproval, redactPortalSecrets, registerMcpPortalPlugin, validatePortalPluginApi, validatePortalPortAgainstTcpPool };
|
|
245
221
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/openclaw-plugin-api.ts","../src/plugin-registration.ts","../src/
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/openclaw-plugin-api.ts","../src/plugin-registration.ts","../src/portal-plugin-runtime-state.ts","../src/before-prompt-build-handler.ts","../src/before-tool-call-handler.ts","../src/portal-config.ts","../src/portal-tool-policy.ts","../src/portal-prompt-context.ts","../src/redaction.ts","../src/index.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { loadMcpPortalConfig, mcpPortalCallRequiresApproval, resolveMcpPortalProfile } from "@agent-vm/config-contracts";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { z } from "zod";
|
|
1
|
+
import { loadMcpConfig, loadMcpPortalConfig, mcpPortalCallRequiresApproval, resolveMcpPortalProfile } from "@agent-vm/config-contracts";
|
|
2
|
+
import { createPortalCore, createUpstreamMcpClientRuntime, listPortalCoreToolDescriptors, redactCredentialText, resolveUpstreamServers } from "@agent-vm/mcp-portal/core";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
5
4
|
import { join } from "node:path";
|
|
6
|
-
import {
|
|
5
|
+
import { z } from "zod";
|
|
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,287 +100,80 @@ 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) {
|
|
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
|
-
...hmacEnv
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
function logSubprocessOutput(props) {
|
|
254
|
-
const text = String(props.chunk);
|
|
255
|
-
for (const line of text.split(/\r?\n/u)) {
|
|
256
|
-
if (line.length === 0) continue;
|
|
257
|
-
const message = `[mcp-portal ${props.streamName}] ${line}`;
|
|
258
|
-
if (props.streamName === "stderr") props.logger.warn(message);
|
|
259
|
-
else props.logger.info(message);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
function delay(ms) {
|
|
263
|
-
return new Promise((resolve) => {
|
|
264
|
-
setTimeout(resolve, ms);
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
async function waitForExit(child, timeoutMs) {
|
|
268
|
-
return new Promise((resolve) => {
|
|
269
|
-
let settled = false;
|
|
270
|
-
const timer = setTimeout(() => {
|
|
271
|
-
if (settled) return;
|
|
272
|
-
settled = true;
|
|
273
|
-
child.off("exit", handleExit);
|
|
274
|
-
resolve(false);
|
|
275
|
-
}, timeoutMs);
|
|
276
|
-
const handleExit = () => {
|
|
277
|
-
if (settled) return;
|
|
278
|
-
settled = true;
|
|
279
|
-
clearTimeout(timer);
|
|
280
|
-
resolve(true);
|
|
281
|
-
};
|
|
282
|
-
child.once("exit", handleExit);
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
async function waitForHealthAttempt(props) {
|
|
286
|
-
if (Date.now() - props.startedAt > props.timeoutMs) {
|
|
287
|
-
const message = props.lastError instanceof Error ? props.lastError.message : String(props.lastError);
|
|
288
|
-
throw new Error(`Timed out waiting for MCP Portal health: ${message}`);
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
const response = await props.fetchFn(`http://${props.host}:${String(props.port)}/health`);
|
|
292
|
-
if (response.ok) return;
|
|
293
|
-
await delay(props.intervalMs);
|
|
294
|
-
return waitForHealthAttempt({
|
|
295
|
-
...props,
|
|
296
|
-
lastError: /* @__PURE__ */ new Error(`health returned ${String(response.status)}`)
|
|
297
|
-
});
|
|
298
|
-
} catch (error) {
|
|
299
|
-
await delay(props.intervalMs);
|
|
300
|
-
return waitForHealthAttempt({
|
|
301
|
-
...props,
|
|
302
|
-
lastError: error
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
async function waitForHealth(props) {
|
|
307
|
-
const startedAt = Date.now();
|
|
308
|
-
return waitForHealthAttempt({
|
|
309
|
-
...props,
|
|
310
|
-
lastError: void 0,
|
|
311
|
-
startedAt
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
function errorMessage(error) {
|
|
315
|
-
return error instanceof Error ? error.message : String(error);
|
|
316
|
-
}
|
|
317
|
-
function createPortalSubprocessSupervisor(props) {
|
|
318
|
-
const spawnFn = props.spawnFn ?? ((command, args, options) => spawn(command, [...args], options));
|
|
319
|
-
const fetchFn = props.fetchFn ?? fetch;
|
|
320
|
-
const healthPollIntervalMs = props.healthPollIntervalMs ?? 200;
|
|
321
|
-
const healthTimeoutMs = props.healthTimeoutMs ?? 1e4;
|
|
322
|
-
const stopGraceMs = props.stopGraceMs ?? 5e3;
|
|
323
|
-
const maxRestarts = props.maxRestarts ?? 5;
|
|
324
|
-
const backoffSteps = props.backoffSteps ?? defaultBackoffSteps;
|
|
325
|
-
let child = null;
|
|
326
|
-
let stopping = false;
|
|
327
|
-
let restartCount = 0;
|
|
328
|
-
const spawnChild = () => {
|
|
329
|
-
const nextChild = spawnFn(props.binPath, ["--config-dir", props.configDir], {
|
|
330
|
-
env: createPortalSubprocessEnv(props.hmacEnv),
|
|
331
|
-
stdio: [
|
|
332
|
-
"ignore",
|
|
333
|
-
"pipe",
|
|
334
|
-
"pipe"
|
|
335
|
-
]
|
|
336
|
-
});
|
|
337
|
-
let autoRestartEnabled = false;
|
|
338
|
-
let failureHandled = false;
|
|
339
|
-
let rejectEarlyFailure;
|
|
340
|
-
const earlyFailure = new Promise((_resolve, reject) => {
|
|
341
|
-
rejectEarlyFailure = reject;
|
|
342
|
-
});
|
|
343
|
-
const rejectBeforeHealth = (error) => {
|
|
344
|
-
if (rejectEarlyFailure === void 0) throw new Error("MCP Portal early-failure rejector was not initialized.");
|
|
345
|
-
rejectEarlyFailure(error);
|
|
346
|
-
};
|
|
347
|
-
child = nextChild;
|
|
348
|
-
nextChild.stdout?.on("data", (chunk) => {
|
|
349
|
-
logSubprocessOutput({
|
|
350
|
-
chunk,
|
|
351
|
-
logger: props.logger,
|
|
352
|
-
streamName: "stdout"
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
nextChild.stdout?.on("error", (error) => {
|
|
356
|
-
props.logger.warn(`[mcp-portal stdout] stream error: ${error.message}`);
|
|
357
|
-
});
|
|
358
|
-
nextChild.stderr?.on("data", (chunk) => {
|
|
359
|
-
logSubprocessOutput({
|
|
360
|
-
chunk,
|
|
361
|
-
logger: props.logger,
|
|
362
|
-
streamName: "stderr"
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
nextChild.stderr?.on("error", (error) => {
|
|
366
|
-
props.logger.warn(`[mcp-portal stderr] stream error: ${error.message}`);
|
|
367
|
-
});
|
|
368
|
-
nextChild.on("error", (error) => {
|
|
369
|
-
props.logger.error(`[mcp-portal] subprocess spawn failed: ${error.message}`);
|
|
370
|
-
if (failureHandled) return;
|
|
371
|
-
failureHandled = true;
|
|
372
|
-
if (child === nextChild) child = null;
|
|
373
|
-
if (stopping) return;
|
|
374
|
-
if (autoRestartEnabled) scheduleRestart();
|
|
375
|
-
else rejectBeforeHealth(error);
|
|
376
|
-
});
|
|
377
|
-
nextChild.on("exit", (code, signal) => {
|
|
378
|
-
if (failureHandled) return;
|
|
379
|
-
failureHandled = true;
|
|
380
|
-
if (child === nextChild) child = null;
|
|
381
|
-
if (stopping) return;
|
|
382
|
-
if (autoRestartEnabled) scheduleRestart();
|
|
383
|
-
else rejectBeforeHealth(/* @__PURE__ */ new Error(`MCP Portal subprocess exited before health check completed (code=${String(code)} signal=${String(signal)}).`));
|
|
384
|
-
});
|
|
385
|
-
return {
|
|
386
|
-
child: nextChild,
|
|
387
|
-
earlyFailure,
|
|
388
|
-
enableAutoRestart: () => {
|
|
389
|
-
autoRestartEnabled = true;
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
};
|
|
393
|
-
const spawnChildAndWaitForHealth = async () => {
|
|
394
|
-
const spawnedChild = spawnChild();
|
|
395
|
-
try {
|
|
396
|
-
await Promise.race([waitForHealth({
|
|
397
|
-
fetchFn,
|
|
398
|
-
host: props.host,
|
|
399
|
-
intervalMs: healthPollIntervalMs,
|
|
400
|
-
port: props.port,
|
|
401
|
-
timeoutMs: healthTimeoutMs
|
|
402
|
-
}), spawnedChild.earlyFailure]);
|
|
403
|
-
} catch (error) {
|
|
404
|
-
if (child === spawnedChild.child) {
|
|
405
|
-
child = null;
|
|
406
|
-
if (!spawnedChild.child.killed) spawnedChild.child.kill("SIGTERM");
|
|
407
|
-
}
|
|
408
|
-
throw error;
|
|
409
|
-
}
|
|
410
|
-
spawnedChild.enableAutoRestart();
|
|
411
|
-
restartCount = 0;
|
|
412
|
-
props.logger.info("[mcp-portal] subprocess is healthy.");
|
|
413
|
-
};
|
|
414
|
-
const scheduleRestart = async () => {
|
|
415
|
-
restartCount += 1;
|
|
416
|
-
if (restartCount > maxRestarts) {
|
|
417
|
-
props.logger.error("[mcp-portal] subprocess restart limit exhausted.");
|
|
418
|
-
props.onFatal?.("backoff-exhausted");
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
const backoffMs = backoffSteps[Math.min(restartCount - 1, backoffSteps.length - 1)] ?? backoffSteps[backoffSteps.length - 1] ?? 5e3;
|
|
422
|
-
props.logger.warn(`[mcp-portal] subprocess exited; restarting in ${String(backoffMs)}ms.`);
|
|
423
|
-
await delay(backoffMs);
|
|
424
|
-
if (stopping) return;
|
|
425
|
-
try {
|
|
426
|
-
await spawnChildAndWaitForHealth();
|
|
427
|
-
} catch (error) {
|
|
428
|
-
props.logger.error(`[mcp-portal] subprocess restart failed: ${errorMessage(error)}`);
|
|
429
|
-
if (!stopping) await scheduleRestart();
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
return {
|
|
433
|
-
isAlive: () => child !== null && !child.killed,
|
|
434
|
-
start: async () => {
|
|
435
|
-
stopping = false;
|
|
436
|
-
await spawnChildAndWaitForHealth();
|
|
437
|
-
},
|
|
438
|
-
stop: async () => {
|
|
439
|
-
stopping = true;
|
|
440
|
-
const activeChild = child;
|
|
441
|
-
child = null;
|
|
442
|
-
if (activeChild === null || activeChild.killed) return;
|
|
443
|
-
activeChild.kill("SIGTERM");
|
|
444
|
-
if (!await waitForExit(activeChild, stopGraceMs) && !activeChild.killed) activeChild.kill("SIGKILL");
|
|
445
177
|
}
|
|
446
178
|
};
|
|
447
179
|
}
|
|
@@ -460,29 +192,16 @@ function isUnknownArray(value) {
|
|
|
460
192
|
function getObjectProperty(value, property) {
|
|
461
193
|
return isObjectRecord(value) ? value[property] : void 0;
|
|
462
194
|
}
|
|
463
|
-
function messageFromUnknown(error) {
|
|
464
|
-
return error instanceof Error ? error.message : String(error);
|
|
465
|
-
}
|
|
466
195
|
function resolveConfigDir(api) {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
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");
|
|
470
198
|
if (typeof topLevelMcpConfigDir === "string" && topLevelMcpConfigDir.length > 0) return topLevelMcpConfigDir;
|
|
471
199
|
const zones = getObjectProperty(api.config, "zones");
|
|
472
200
|
if (isUnknownArray(zones)) {
|
|
473
|
-
const zoneMcpConfigDir = getObjectProperty(getObjectProperty(zones.at(0), "
|
|
201
|
+
const zoneMcpConfigDir = getObjectProperty(getObjectProperty(zones.at(0), "mcpPortal"), "configDir");
|
|
474
202
|
if (typeof zoneMcpConfigDir === "string" && zoneMcpConfigDir.length > 0) return zoneMcpConfigDir;
|
|
475
203
|
}
|
|
476
|
-
throw new Error("MCP Portal plugin requires configDir in plugin config or zone
|
|
477
|
-
}
|
|
478
|
-
function tcpPoolConfigFromApi(api) {
|
|
479
|
-
const tcpPool = getObjectProperty(api.config, "tcpPool");
|
|
480
|
-
const basePort = getObjectProperty(tcpPool, "basePort");
|
|
481
|
-
const size = getObjectProperty(tcpPool, "size");
|
|
482
|
-
return typeof basePort === "number" && typeof size === "number" ? {
|
|
483
|
-
basePort,
|
|
484
|
-
size
|
|
485
|
-
} : null;
|
|
204
|
+
throw new Error("MCP Portal plugin requires configDir in plugin config or zone mcpPortal config.");
|
|
486
205
|
}
|
|
487
206
|
function validatePortalPortAgainstTcpPool(props) {
|
|
488
207
|
if (props.tcpPool === null) return;
|
|
@@ -498,8 +217,8 @@ function createLoggerAdapter(api) {
|
|
|
498
217
|
};
|
|
499
218
|
}
|
|
500
219
|
function validatePortalPluginApi(api) {
|
|
501
|
-
if (!hasFunction(api.
|
|
502
|
-
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.");
|
|
503
222
|
if (hasFunction(api.lifecycle?.registerRuntimeLifecycle) || hasFunction(api.registerRuntimeLifecycle)) return;
|
|
504
223
|
throw new Error("MCP Portal plugin requires an OpenClaw lifecycle cleanup API.");
|
|
505
224
|
}
|
|
@@ -508,8 +227,8 @@ function registerPortalRuntimeCleanup(api, cleanup) {
|
|
|
508
227
|
cleanup: async () => {
|
|
509
228
|
await cleanup();
|
|
510
229
|
},
|
|
511
|
-
description: "
|
|
512
|
-
id: "mcp-portal-
|
|
230
|
+
description: "Closes MCP Portal upstream clients owned by the agent-vm plugin.",
|
|
231
|
+
id: "mcp-portal-core"
|
|
513
232
|
};
|
|
514
233
|
if (hasFunction(api.lifecycle?.registerRuntimeLifecycle)) {
|
|
515
234
|
api.lifecycle.registerRuntimeLifecycle(runtimeLifecycle);
|
|
@@ -521,48 +240,163 @@ function registerPortalRuntimeCleanup(api, cleanup) {
|
|
|
521
240
|
}
|
|
522
241
|
throw new Error("MCP Portal plugin requires an OpenClaw lifecycle cleanup API.");
|
|
523
242
|
}
|
|
524
|
-
function
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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"
|
|
548
343
|
});
|
|
549
|
-
await
|
|
550
|
-
|
|
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
|
+
};
|
|
551
354
|
},
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
|
555
382
|
});
|
|
556
|
-
return { getSupervisor: () => supervisor };
|
|
557
383
|
}
|
|
558
384
|
function registerMcpPortalPlugin(api) {
|
|
559
385
|
if (api.registrationMode !== void 0 && api.registrationMode !== "full") return;
|
|
560
386
|
validatePortalPluginApi(api);
|
|
561
387
|
const configDir = resolveConfigDir(api);
|
|
562
388
|
const runtimeState = createPortalPluginRuntimeState({ configDir });
|
|
563
|
-
|
|
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({
|
|
564
398
|
api,
|
|
565
|
-
|
|
399
|
+
getCore,
|
|
566
400
|
runtimeState
|
|
567
401
|
});
|
|
568
402
|
api.on?.("before_tool_call", createBeforeToolCallHandler({
|
|
@@ -574,13 +408,12 @@ function registerMcpPortalPlugin(api) {
|
|
|
574
408
|
const result = await createBeforePromptBuildHandler({ runtimeState })({}, context);
|
|
575
409
|
if (result?.appendSystemContext !== void 0) context.appendPrompt?.(result.appendSystemContext);
|
|
576
410
|
});
|
|
577
|
-
registerPortalRuntimeCleanup(api, () =>
|
|
578
|
-
|
|
579
|
-
api.logger?.error?.(`[mcp-portal] failed to initialize portal config: ${messageFromUnknown(error)}`);
|
|
411
|
+
registerPortalRuntimeCleanup(api, async () => {
|
|
412
|
+
await (await corePromise?.catch(() => void 0))?.close();
|
|
580
413
|
});
|
|
581
414
|
}
|
|
582
415
|
const pluginEntry = {
|
|
583
|
-
description: "
|
|
416
|
+
description: "Registers native OpenClaw MCP Portal tools and wires per-agent approval hooks.",
|
|
584
417
|
id: pluginId,
|
|
585
418
|
name: "MCP Portal",
|
|
586
419
|
register: registerMcpPortalPlugin
|
|
@@ -591,7 +424,7 @@ function createPortalPromptContext(props) {
|
|
|
591
424
|
const namespaceList = props.namespaces.length > 0 ? props.namespaces.map((entry) => `${entry.namespace}(${entry.toolCount} tools)`).join(", ") : "none configured";
|
|
592
425
|
const diagnostics = props.diagnostics !== void 0 && props.diagnostics.length > 0 ? [`Discovery diagnostics: ${props.diagnostics.map((entry) => `${entry.namespace}: ${entry.message}`).join("; ")}`] : [];
|
|
593
426
|
return [
|
|
594
|
-
"MCP Portal is available as
|
|
427
|
+
"MCP Portal is available as native OpenClaw tools.",
|
|
595
428
|
"Use mcp_portal_list with requests[], mcp_portal_search with requests[],",
|
|
596
429
|
"mcp_portal_describe with requests[], and mcp_portal_call with calls[].",
|
|
597
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.",
|
|
@@ -611,6 +444,6 @@ function redactPortalSecrets(text, secretValues = []) {
|
|
|
611
444
|
//#region src/index.ts
|
|
612
445
|
const OPENCLAW_MCP_PORTAL_PLUGIN_PACKAGE_NAME = "@agent-vm/openclaw-mcp-portal-plugin";
|
|
613
446
|
//#endregion
|
|
614
|
-
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 };
|
|
615
448
|
|
|
616
449
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["isObjectRecord","errorMessage"],"sources":["../src/before-prompt-build-handler.ts","../src/portal-tool-policy.ts","../src/before-tool-call-handler.ts","../src/hmac-key-registry.ts","../src/portal-config.ts","../src/portal-plugin-runtime-state.ts","../src/portal-subprocess-supervisor.ts","../src/plugin-registration.ts","../src/portal-prompt-context.ts","../src/redaction.ts","../src/index.ts"],"sourcesContent":["import { resolveMcpPortalProfile } from '@agent-vm/config-contracts';\n\nimport type {\n\tOpenClawBeforePromptBuildEvent,\n\tOpenClawPluginHookContext,\n\tOpenClawPromptHookResult,\n} from './openclaw-plugin-api.js';\nimport type { PortalPluginRuntimeState } from './portal-plugin-runtime-state.js';\n\nexport interface CreateBeforePromptBuildHandlerProps {\n\treadonly runtimeState: PortalPluginRuntimeState;\n}\n\nexport function createBeforePromptBuildHandler(\n\tprops: CreateBeforePromptBuildHandlerProps,\n): (\n\tevent: OpenClawBeforePromptBuildEvent,\n\tcontext: OpenClawPluginHookContext,\n) => Promise<OpenClawPromptHookResult | undefined> {\n\treturn async (_event, context) => {\n\t\tconst agentId = context.agentId;\n\t\tif (agentId === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst portalConfig = await props.runtimeState.loadPortalConfig();\n\t\tconst agent = portalConfig.agents[agentId];\n\t\tif (agent === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst profile = resolveMcpPortalProfile(portalConfig, agent.profile);\n\t\tif (!profile.promptContext.enabled) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst namespaces = profile.enabledNamespaces\n\t\t\t.toSorted()\n\t\t\t.slice(0, profile.promptContext.maxNamespaces);\n\t\tconst namespaceText =\n\t\t\tnamespaces.length === 0\n\t\t\t\t? ' (none in your profile)'\n\t\t\t\t: namespaces.map((name) => ` ${name}`).join('\\n');\n\t\treturn {\n\t\t\tappendSystemContext: [\n\t\t\t\t'MCP Portal namespaces available to this agent:',\n\t\t\t\tnamespaceText,\n\t\t\t\t'Use mcp_portal_search to find tools by intent, then mcp_portal_describe before mcp_portal_call.',\n\t\t\t].join('\\n'),\n\t\t};\n\t};\n}\n","import {\n\tmcpPortalCallRequiresApproval,\n\ttype ResolvedMcpPortalProfile,\n} from '@agent-vm/config-contracts';\n\nexport interface PortalCallRequest {\n\treadonly arguments: Record<string, unknown>;\n\treadonly id: string;\n\treadonly namespace: string;\n\treadonly toolName: string;\n}\n\nfunction encodePortalServerNameSegment(value: string): string {\n\tconst encodedCharacters: string[] = [];\n\tfor (let index = 0; index < value.length; index += 1) {\n\t\tconst character = value.charAt(index);\n\t\tif (/^[A-Za-z0-9]$/u.test(character)) {\n\t\t\tencodedCharacters.push(character);\n\t\t} else {\n\t\t\tencodedCharacters.push(`_${character.charCodeAt(0).toString(16).padStart(2, '0')}_`);\n\t\t}\n\t}\n\treturn encodedCharacters.join('');\n}\n\nexport function portalServerNameForAgent(agentId: string): string {\n\treturn `mcp_portal_${encodePortalServerNameSegment(agentId)}`;\n}\n\nexport function materializedPortalToolNames(serverName: string): readonly string[] {\n\treturn [\n\t\t`${serverName}__mcp_portal_list`,\n\t\t`${serverName}__mcp_portal_search`,\n\t\t`${serverName}__mcp_portal_describe`,\n\t\t`${serverName}__mcp_portal_call`,\n\t];\n}\n\nexport function profileAllowsPortalCall(\n\tprofile: ResolvedMcpPortalProfile,\n\tcall: { readonly namespace: string; readonly toolName: string },\n): boolean {\n\tif (!profile.enabledNamespaces.includes(call.namespace)) {\n\t\treturn false;\n\t}\n\tconst enabledTools = profile.enabledToolsByNamespace[call.namespace] ?? [];\n\tif (enabledTools.length > 0 && !enabledTools.includes(call.toolName)) {\n\t\treturn false;\n\t}\n\tconst hiddenTools = profile.hiddenToolsByNamespace[call.namespace] ?? [];\n\treturn !hiddenTools.includes(call.toolName);\n}\n\nexport function profileRequiresPortalApproval(\n\tprofile: ResolvedMcpPortalProfile,\n\tcall: { readonly namespace: string; readonly toolName: string },\n): boolean {\n\treturn mcpPortalCallRequiresApproval(profile, call);\n}\n","import { resolveMcpPortalProfile } from '@agent-vm/config-contracts';\nimport { hashCallArguments, signApprovalToken } from '@agent-vm/mcp-portal';\n\nimport type {\n\tOpenClawBeforeToolCallEvent,\n\tOpenClawBeforeToolCallResult,\n\tOpenClawPluginHookContext,\n} from './openclaw-plugin-api.js';\nimport type { PortalPluginRuntimeState } from './portal-plugin-runtime-state.js';\nimport {\n\tportalServerNameForAgent,\n\tprofileAllowsPortalCall,\n\tprofileRequiresPortalApproval,\n\ttype PortalCallRequest,\n} from './portal-tool-policy.js';\n\nconst approvalTokenTtlMs = 60_000;\n\nexport interface CreateBeforeToolCallHandlerProps {\n\treadonly logger?: {\n\t\treadonly warn?: (message: string) => void;\n\t};\n\treadonly runtimeState: PortalPluginRuntimeState;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction parseCallRequest(value: unknown): PortalCallRequest | null {\n\tif (!isObjectRecord(value)) {\n\t\treturn null;\n\t}\n\tconst id = value.id;\n\tconst namespace = value.namespace;\n\tconst toolName = value.toolName;\n\tconst argumentsValue = value.arguments;\n\tif (\n\t\ttypeof id !== 'string' ||\n\t\ttypeof namespace !== 'string' ||\n\t\ttypeof toolName !== 'string' ||\n\t\t!isObjectRecord(argumentsValue)\n\t) {\n\t\treturn null;\n\t}\n\treturn { arguments: argumentsValue, id, namespace, toolName };\n}\n\nfunction portalAgentIdFromToolName(toolName: string, agentIds: readonly string[]): string | null {\n\treturn (\n\t\tagentIds.find((agentId) =>\n\t\t\ttoolName.startsWith(`${portalServerNameForAgent(agentId)}__mcp_portal_`),\n\t\t) ?? null\n\t);\n}\n\nfunction parseCallRequests(params: Record<string, unknown>): readonly PortalCallRequest[] | null {\n\tconst calls = params.calls;\n\tif (!Array.isArray(calls)) {\n\t\treturn null;\n\t}\n\tconst parsedCalls: PortalCallRequest[] = [];\n\tfor (const call of calls) {\n\t\tconst parsedCall = parseCallRequest(call);\n\t\tif (parsedCall === null) {\n\t\t\treturn null;\n\t\t}\n\t\tparsedCalls.push(parsedCall);\n\t}\n\treturn parsedCalls;\n}\n\nfunction errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nexport function createBeforeToolCallHandler(\n\tprops: CreateBeforeToolCallHandlerProps,\n): (\n\tevent: OpenClawBeforeToolCallEvent,\n\tcontext: OpenClawPluginHookContext,\n) => Promise<OpenClawBeforeToolCallResult | undefined> {\n\treturn async (event, context) => {\n\t\tconst portalConfig = await props.runtimeState.loadPortalConfig();\n\t\tconst agentId = portalAgentIdFromToolName(event.toolName, Object.keys(portalConfig.agents));\n\t\tif (agentId === null) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst portalUnavailableReason = props.runtimeState.getPortalUnavailableReason();\n\t\tif (portalUnavailableReason !== null) {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\tblockReason: `mcp-portal: portal subprocess unavailable (${portalUnavailableReason}).`,\n\t\t\t};\n\t\t}\n\t\tif (context.agentId === undefined) {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\tblockReason: `mcp-portal: missing OpenClaw agent context for ${event.toolName}.`,\n\t\t\t};\n\t\t}\n\t\tif (context.agentId !== agentId) {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\tblockReason: `mcp-portal: tool ${event.toolName} is not assigned to agent ${context.agentId}.`,\n\t\t\t};\n\t\t}\n\t\tif (!event.toolName.endsWith('__mcp_portal_call')) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst agent = portalConfig.agents[agentId];\n\t\tif (agent === undefined) {\n\t\t\treturn { block: true, blockReason: `mcp-portal: agent \"${agentId}\" is not configured.` };\n\t\t}\n\t\tconst profile = resolveMcpPortalProfile(portalConfig, agent.profile);\n\t\tconst calls = parseCallRequests(event.params);\n\t\tif (calls === null || calls.length === 0) {\n\t\t\treturn { block: true, blockReason: 'mcp-portal: malformed portal call batch.' };\n\t\t}\n\n\t\tfor (const call of calls) {\n\t\t\tif (!profileAllowsPortalCall(profile, call)) {\n\t\t\t\treturn {\n\t\t\t\t\tblock: true,\n\t\t\t\t\tblockReason: `policy: ${agentId}/${call.namespace}/${call.toolName} not enabled`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst approvalCalls = calls.filter((call) => profileRequiresPortalApproval(profile, call));\n\t\tif (approvalCalls.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst token = signApprovalToken({\n\t\t\tagentId,\n\t\t\tcalls: approvalCalls.map((call) => ({\n\t\t\t\targumentsHash: hashCallArguments(call.arguments),\n\t\t\t\tnamespace: call.namespace,\n\t\t\t\ttoolName: call.toolName,\n\t\t\t})),\n\t\t\texpiresAtMs: Date.now() + approvalTokenTtlMs,\n\t\t\tkey: props.runtimeState.getKeyRegistry().getKey(agentId),\n\t\t});\n\t\ttry {\n\t\t\tevent.params.portalApprovalToken = token;\n\t\t} catch (error) {\n\t\t\tprops.logger?.warn?.(\n\t\t\t\t`[mcp-portal] could not attach server-side approval token: ${errorMessage(error)}`,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\tblockReason: 'mcp-portal: could not attach server-side approval token.',\n\t\t\t};\n\t\t}\n\t\tif (event.params.portalApprovalToken !== token) {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\tblockReason: 'mcp-portal: could not attach server-side approval token.',\n\t\t\t};\n\t\t}\n\n\t\tconst toolNames = approvalCalls\n\t\t\t.map((call) => `${call.namespace}.${call.toolName}`)\n\t\t\t.toSorted()\n\t\t\t.join(', ');\n\t\treturn {\n\t\t\trequireApproval: {\n\t\t\t\tdescription: `Allow MCP Portal batch for agent ${agentId}: ${toolNames}.`,\n\t\t\t\tpluginId: 'mcp-portal',\n\t\t\t\tseverity: 'warning',\n\t\t\t\ttimeoutBehavior: 'deny',\n\t\t\t\ttimeoutMs: 60_000,\n\t\t\t\ttitle: `MCP Portal batch: ${toolNames}`,\n\t\t\t},\n\t\t};\n\t};\n}\n","import { randomBytes } from 'node:crypto';\n\nimport { portalHmacKeyEnvName } from '@agent-vm/mcp-portal';\n\nconst hmacKeyBytes = 32;\n\nexport interface CreateHmacKeyRegistryProps {\n\treadonly agentIds: readonly string[];\n}\n\nexport interface HmacKeyRegistry {\n\treadonly agentIds: readonly string[];\n\treadonly getKey: (agentId: string) => Buffer;\n\treadonly serializeForEnv: () => Readonly<Record<string, string>>;\n}\n\nexport function createHmacKeyRegistry(props: CreateHmacKeyRegistryProps): HmacKeyRegistry {\n\tconst keysByAgent = new Map<string, Buffer>();\n\tfor (const agentId of props.agentIds) {\n\t\tkeysByAgent.set(agentId, randomBytes(hmacKeyBytes));\n\t}\n\n\treturn {\n\t\tagentIds: [...props.agentIds],\n\t\tgetKey: (agentId) => {\n\t\t\tconst key = keysByAgent.get(agentId);\n\t\t\tif (key === undefined) {\n\t\t\t\tthrow new Error(`HMAC key registry: unknown agent \"${agentId}\".`);\n\t\t\t}\n\t\t\treturn key;\n\t\t},\n\t\tserializeForEnv: () =>\n\t\t\tObject.fromEntries(\n\t\t\t\t[...keysByAgent.entries()].map(([agentId, key]) => [\n\t\t\t\t\tportalHmacKeyEnvName(agentId),\n\t\t\t\t\tkey.toString('hex'),\n\t\t\t\t]),\n\t\t\t),\n\t};\n}\n","import { z } from 'zod';\n\nexport const defaultPortalBinPath = '/opt/agent-vm/portal/bin/agent-vm-mcp-portal-server';\n\nexport const portalPluginConfigSchema = z\n\t.object({\n\t\tbinPath: z.string().min(1).default(defaultPortalBinPath),\n\t\tconfigDir: z.string().min(1).optional(),\n\t})\n\t.strict();\n\nexport type PortalPluginConfig = z.infer<typeof portalPluginConfigSchema>;\n\nexport function parsePortalConfig(value: unknown): PortalPluginConfig {\n\treturn portalPluginConfigSchema.parse(value ?? {});\n}\n","import { join } from 'node:path';\n\nimport { loadMcpPortalConfig, type McpPortalConfig } from '@agent-vm/config-contracts';\n\nimport type { HmacKeyRegistry } from './hmac-key-registry.js';\n\nexport interface PortalPluginRuntimeState {\n\treadonly configDir: string;\n\treadonly getPortalUnavailableReason: () => string | null;\n\treadonly getKeyRegistry: () => HmacKeyRegistry;\n\treadonly loadPortalConfig: () => Promise<McpPortalConfig>;\n\treadonly markPortalAvailable: () => void;\n\treadonly markPortalUnavailable: (reason: string) => void;\n\treadonly setKeyRegistry: (registry: HmacKeyRegistry) => void;\n}\n\nexport function createPortalPluginRuntimeState(props: {\n\treadonly configDir: string;\n\treadonly loadPortalConfig?: (path: string) => Promise<McpPortalConfig>;\n}): PortalPluginRuntimeState {\n\tlet keyRegistry: HmacKeyRegistry | null = null;\n\tlet portalConfigPromise: Promise<McpPortalConfig> | null = null;\n\tlet portalUnavailableReason: string | null = null;\n\tconst loadPortalConfigFile = props.loadPortalConfig ?? loadMcpPortalConfig;\n\tconst portalConfigPath = join(props.configDir, 'mcp-portal.config.jsonc');\n\n\tfunction loadPortalConfig(): Promise<McpPortalConfig> {\n\t\tif (portalConfigPromise !== null) {\n\t\t\treturn portalConfigPromise;\n\t\t}\n\t\tconst nextPromise = loadPortalConfigFile(portalConfigPath).catch((error: unknown) => {\n\t\t\tif (portalConfigPromise === nextPromise) {\n\t\t\t\tportalConfigPromise = null;\n\t\t\t}\n\t\t\tthrow error;\n\t\t});\n\t\tportalConfigPromise = nextPromise;\n\t\treturn nextPromise;\n\t}\n\n\treturn {\n\t\tconfigDir: props.configDir,\n\t\tgetPortalUnavailableReason: () => portalUnavailableReason,\n\t\tgetKeyRegistry: () => {\n\t\t\tif (keyRegistry === null) {\n\t\t\t\tthrow new Error('MCP Portal HMAC key registry is not initialized.');\n\t\t\t}\n\t\t\treturn keyRegistry;\n\t\t},\n\t\tloadPortalConfig,\n\t\tmarkPortalAvailable: () => {\n\t\t\tportalUnavailableReason = null;\n\t\t},\n\t\tmarkPortalUnavailable: (reason) => {\n\t\t\tportalUnavailableReason = reason;\n\t\t},\n\t\tsetKeyRegistry: (registry) => {\n\t\t\tkeyRegistry = registry;\n\t\t},\n\t};\n}\n","import { spawn } from 'node:child_process';\nimport type { ChildProcess, SpawnOptions } from 'node:child_process';\n\nexport interface PortalSubprocessLogger {\n\treadonly error: (message: string) => void;\n\treadonly info: (message: string) => void;\n\treadonly warn: (message: string) => void;\n}\n\nexport type PortalSubprocessSpawnFunction = (\n\tcommand: string,\n\targs: readonly string[],\n\toptions: SpawnOptions,\n) => ChildProcess;\n\nexport interface CreatePortalSubprocessSupervisorProps {\n\treadonly backoffSteps?: readonly number[];\n\treadonly binPath: string;\n\treadonly configDir: string;\n\treadonly fetchFn?: typeof fetch;\n\treadonly healthPollIntervalMs?: number;\n\treadonly healthTimeoutMs?: number;\n\treadonly host: string;\n\treadonly hmacEnv: Readonly<Record<string, string>>;\n\treadonly logger: PortalSubprocessLogger;\n\treadonly maxRestarts?: number;\n\treadonly onFatal?: (reason: string) => void;\n\treadonly port: number;\n\treadonly spawnFn?: PortalSubprocessSpawnFunction;\n\treadonly stopGraceMs?: number;\n}\n\nexport interface PortalSubprocessSupervisor {\n\treadonly isAlive: () => boolean;\n\treadonly start: () => Promise<void>;\n\treadonly stop: () => Promise<void>;\n}\n\nconst defaultBackoffSteps = [200, 400, 800, 1_600, 3_200, 5_000] as const;\nconst inheritedPortalEnvNames = ['HOME', 'PATH', 'TEMP', 'TMP', 'TMPDIR'] as const;\n\nfunction createPortalSubprocessEnv(\n\thmacEnv: Readonly<Record<string, string>>,\n): Readonly<Record<string, string>> {\n\tconst env: Record<string, string> = {};\n\tfor (const name of inheritedPortalEnvNames) {\n\t\tconst value = process.env[name];\n\t\tif (value !== undefined) {\n\t\t\tenv[name] = value;\n\t\t}\n\t}\n\treturn { ...env, ...hmacEnv };\n}\n\nfunction logSubprocessOutput(props: {\n\treadonly chunk: Buffer | string;\n\treadonly logger: PortalSubprocessLogger;\n\treadonly streamName: 'stderr' | 'stdout';\n}): void {\n\tconst text = String(props.chunk);\n\tfor (const line of text.split(/\\r?\\n/u)) {\n\t\tif (line.length === 0) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst message = `[mcp-portal ${props.streamName}] ${line}`;\n\t\tif (props.streamName === 'stderr') {\n\t\t\tprops.logger.warn(message);\n\t\t} else {\n\t\t\tprops.logger.info(message);\n\t\t}\n\t}\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolve) => {\n\t\tsetTimeout(resolve, ms);\n\t});\n}\n\nasync function waitForExit(child: ChildProcess, timeoutMs: number): Promise<boolean> {\n\treturn new Promise<boolean>((resolve) => {\n\t\tlet settled = false;\n\t\tconst timer = setTimeout(() => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tchild.off('exit', handleExit);\n\t\t\tresolve(false);\n\t\t}, timeoutMs);\n\t\tconst handleExit = (): void => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\tresolve(true);\n\t\t};\n\t\tchild.once('exit', handleExit);\n\t});\n}\n\ninterface WaitForHealthProps {\n\treadonly fetchFn: typeof fetch;\n\treadonly host: string;\n\treadonly intervalMs: number;\n\treadonly port: number;\n\treadonly timeoutMs: number;\n}\n\nasync function waitForHealthAttempt(props: {\n\treadonly fetchFn: typeof fetch;\n\treadonly host: string;\n\treadonly intervalMs: number;\n\treadonly lastError: unknown;\n\treadonly port: number;\n\treadonly startedAt: number;\n\treadonly timeoutMs: number;\n}): Promise<void> {\n\tif (Date.now() - props.startedAt > props.timeoutMs) {\n\t\tconst message =\n\t\t\tprops.lastError instanceof Error ? props.lastError.message : String(props.lastError);\n\t\tthrow new Error(`Timed out waiting for MCP Portal health: ${message}`);\n\t}\n\ttry {\n\t\tconst response = await props.fetchFn(`http://${props.host}:${String(props.port)}/health`);\n\t\tif (response.ok) {\n\t\t\treturn;\n\t\t}\n\t\tawait delay(props.intervalMs);\n\t\treturn waitForHealthAttempt({\n\t\t\t...props,\n\t\t\tlastError: new Error(`health returned ${String(response.status)}`),\n\t\t});\n\t} catch (error) {\n\t\tawait delay(props.intervalMs);\n\t\treturn waitForHealthAttempt({ ...props, lastError: error });\n\t}\n}\n\nasync function waitForHealth(props: WaitForHealthProps): Promise<void> {\n\tconst startedAt = Date.now();\n\treturn waitForHealthAttempt({ ...props, lastError: undefined, startedAt });\n}\n\nfunction errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\ninterface SpawnedPortalChild {\n\treadonly child: ChildProcess;\n\treadonly enableAutoRestart: () => void;\n\treadonly earlyFailure: Promise<never>;\n}\n\nexport function createPortalSubprocessSupervisor(\n\tprops: CreatePortalSubprocessSupervisorProps,\n): PortalSubprocessSupervisor {\n\tconst spawnFn: PortalSubprocessSpawnFunction =\n\t\tprops.spawnFn ?? ((command, args, options) => spawn(command, [...args], options));\n\tconst fetchFn = props.fetchFn ?? fetch;\n\tconst healthPollIntervalMs = props.healthPollIntervalMs ?? 200;\n\tconst healthTimeoutMs = props.healthTimeoutMs ?? 10_000;\n\tconst stopGraceMs = props.stopGraceMs ?? 5_000;\n\tconst maxRestarts = props.maxRestarts ?? 5;\n\tconst backoffSteps = props.backoffSteps ?? defaultBackoffSteps;\n\tlet child: ChildProcess | null = null;\n\tlet stopping = false;\n\tlet restartCount = 0;\n\n\tconst spawnChild = (): SpawnedPortalChild => {\n\t\tconst nextChild = spawnFn(props.binPath, ['--config-dir', props.configDir], {\n\t\t\tenv: createPortalSubprocessEnv(props.hmacEnv),\n\t\t\tstdio: ['ignore', 'pipe', 'pipe'],\n\t\t});\n\t\tlet autoRestartEnabled = false;\n\t\tlet failureHandled = false;\n\t\tlet rejectEarlyFailure: ((error: Error) => void) | undefined;\n\t\tconst earlyFailure = new Promise<never>((_resolve, reject) => {\n\t\t\trejectEarlyFailure = reject;\n\t\t});\n\t\tconst rejectBeforeHealth = (error: Error): void => {\n\t\t\tif (rejectEarlyFailure === undefined) {\n\t\t\t\tthrow new Error('MCP Portal early-failure rejector was not initialized.');\n\t\t\t}\n\t\t\trejectEarlyFailure(error);\n\t\t};\n\t\tchild = nextChild;\n\t\tnextChild.stdout?.on('data', (chunk: Buffer | string) => {\n\t\t\tlogSubprocessOutput({ chunk, logger: props.logger, streamName: 'stdout' });\n\t\t});\n\t\tnextChild.stdout?.on('error', (error: Error) => {\n\t\t\tprops.logger.warn(`[mcp-portal stdout] stream error: ${error.message}`);\n\t\t});\n\t\tnextChild.stderr?.on('data', (chunk: Buffer | string) => {\n\t\t\tlogSubprocessOutput({ chunk, logger: props.logger, streamName: 'stderr' });\n\t\t});\n\t\tnextChild.stderr?.on('error', (error: Error) => {\n\t\t\tprops.logger.warn(`[mcp-portal stderr] stream error: ${error.message}`);\n\t\t});\n\t\tnextChild.on('error', (error: Error) => {\n\t\t\tprops.logger.error(`[mcp-portal] subprocess spawn failed: ${error.message}`);\n\t\t\tif (failureHandled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfailureHandled = true;\n\t\t\tif (child === nextChild) {\n\t\t\t\tchild = null;\n\t\t\t}\n\t\t\tif (stopping) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (autoRestartEnabled) {\n\t\t\t\tvoid scheduleRestart();\n\t\t\t} else {\n\t\t\t\trejectBeforeHealth(error);\n\t\t\t}\n\t\t});\n\t\tnextChild.on('exit', (code: number | null, signal: NodeJS.Signals | null) => {\n\t\t\tif (failureHandled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfailureHandled = true;\n\t\t\tif (child === nextChild) {\n\t\t\t\tchild = null;\n\t\t\t}\n\t\t\tif (stopping) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (autoRestartEnabled) {\n\t\t\t\tvoid scheduleRestart();\n\t\t\t} else {\n\t\t\t\trejectBeforeHealth(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`MCP Portal subprocess exited before health check completed (code=${String(code)} signal=${String(signal)}).`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t\treturn {\n\t\t\tchild: nextChild,\n\t\t\tearlyFailure,\n\t\t\tenableAutoRestart: () => {\n\t\t\t\tautoRestartEnabled = true;\n\t\t\t},\n\t\t};\n\t};\n\n\tconst spawnChildAndWaitForHealth = async (): Promise<void> => {\n\t\tconst spawnedChild = spawnChild();\n\t\ttry {\n\t\t\tawait Promise.race([\n\t\t\t\twaitForHealth({\n\t\t\t\t\tfetchFn,\n\t\t\t\t\thost: props.host,\n\t\t\t\t\tintervalMs: healthPollIntervalMs,\n\t\t\t\t\tport: props.port,\n\t\t\t\t\ttimeoutMs: healthTimeoutMs,\n\t\t\t\t}),\n\t\t\t\tspawnedChild.earlyFailure,\n\t\t\t]);\n\t\t} catch (error) {\n\t\t\tif (child === spawnedChild.child) {\n\t\t\t\tchild = null;\n\t\t\t\tif (!spawnedChild.child.killed) {\n\t\t\t\t\tspawnedChild.child.kill('SIGTERM');\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tspawnedChild.enableAutoRestart();\n\t\trestartCount = 0;\n\t\tprops.logger.info('[mcp-portal] subprocess is healthy.');\n\t};\n\n\tconst scheduleRestart = async (): Promise<void> => {\n\t\trestartCount += 1;\n\t\tif (restartCount > maxRestarts) {\n\t\t\tprops.logger.error('[mcp-portal] subprocess restart limit exhausted.');\n\t\t\tprops.onFatal?.('backoff-exhausted');\n\t\t\treturn;\n\t\t}\n\t\tconst backoffIndex = Math.min(restartCount - 1, backoffSteps.length - 1);\n\t\tconst backoffMs = backoffSteps[backoffIndex] ?? backoffSteps[backoffSteps.length - 1] ?? 5_000;\n\t\tprops.logger.warn(`[mcp-portal] subprocess exited; restarting in ${String(backoffMs)}ms.`);\n\t\tawait delay(backoffMs);\n\t\tif (stopping) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait spawnChildAndWaitForHealth();\n\t\t} catch (error) {\n\t\t\tprops.logger.error(`[mcp-portal] subprocess restart failed: ${errorMessage(error)}`);\n\t\t\tif (!stopping) {\n\t\t\t\tawait scheduleRestart();\n\t\t\t}\n\t\t}\n\t};\n\n\treturn {\n\t\tisAlive: () => child !== null && !child.killed,\n\t\tstart: async () => {\n\t\t\tstopping = false;\n\t\t\tawait spawnChildAndWaitForHealth();\n\t\t},\n\t\tstop: async () => {\n\t\t\tstopping = true;\n\t\t\tconst activeChild = child;\n\t\t\tchild = null;\n\t\t\tif (activeChild === null || activeChild.killed) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tactiveChild.kill('SIGTERM');\n\t\t\tconst exited = await waitForExit(activeChild, stopGraceMs);\n\t\t\tif (!exited && !activeChild.killed) {\n\t\t\t\tactiveChild.kill('SIGKILL');\n\t\t\t}\n\t\t},\n\t};\n}\n","import { createBeforePromptBuildHandler } from './before-prompt-build-handler.js';\nimport { createBeforeToolCallHandler } from './before-tool-call-handler.js';\nimport { createHmacKeyRegistry } from './hmac-key-registry.js';\nimport type { OpenClawPortalPluginApi } from './openclaw-plugin-api.js';\nimport { parsePortalConfig } from './portal-config.js';\nimport {\n\tcreatePortalPluginRuntimeState,\n\ttype PortalPluginRuntimeState,\n} from './portal-plugin-runtime-state.js';\nimport {\n\tcreatePortalSubprocessSupervisor,\n\ttype PortalSubprocessSupervisor,\n} from './portal-subprocess-supervisor.js';\n\ninterface PortalPluginEntry {\n\treadonly description: string;\n\treadonly id: string;\n\treadonly name: string;\n\treadonly register: (api: OpenClawPortalPluginApi) => void;\n}\n\ninterface TcpPoolConfig {\n\treadonly basePort: number;\n\treadonly size: number;\n}\n\nconst pluginId = 'mcp-portal';\n\nfunction hasFunction(value: unknown): value is (...args: readonly unknown[]) => unknown {\n\treturn typeof value === 'function';\n}\n\nfunction isObjectRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isUnknownArray(value: unknown): value is readonly unknown[] {\n\treturn Array.isArray(value);\n}\n\nfunction getObjectProperty(value: unknown, property: string): unknown {\n\treturn isObjectRecord(value) ? value[property] : undefined;\n}\n\nfunction messageFromUnknown(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nfunction resolveConfigDir(api: OpenClawPortalPluginApi): string {\n\tconst pluginConfig = parsePortalConfig(api.pluginConfig ?? {});\n\tif (pluginConfig.configDir !== undefined) {\n\t\treturn pluginConfig.configDir;\n\t}\n\tconst topLevelMcpConfigDir = getObjectProperty(getObjectProperty(api.config, 'mcp'), 'configDir');\n\tif (typeof topLevelMcpConfigDir === 'string' && topLevelMcpConfigDir.length > 0) {\n\t\treturn topLevelMcpConfigDir;\n\t}\n\tconst zones = getObjectProperty(api.config, 'zones');\n\tif (isUnknownArray(zones)) {\n\t\tconst firstZone = zones.at(0);\n\t\tconst zoneMcpConfigDir = getObjectProperty(getObjectProperty(firstZone, 'mcp'), 'configDir');\n\t\tif (typeof zoneMcpConfigDir === 'string' && zoneMcpConfigDir.length > 0) {\n\t\t\treturn zoneMcpConfigDir;\n\t\t}\n\t}\n\tthrow new Error('MCP Portal plugin requires configDir in plugin config or zone mcp config.');\n}\n\nfunction tcpPoolConfigFromApi(api: OpenClawPortalPluginApi): TcpPoolConfig | null {\n\tconst tcpPool = getObjectProperty(api.config, 'tcpPool');\n\tconst basePort = getObjectProperty(tcpPool, 'basePort');\n\tconst size = getObjectProperty(tcpPool, 'size');\n\treturn typeof basePort === 'number' && typeof size === 'number' ? { basePort, size } : null;\n}\n\nexport function validatePortalPortAgainstTcpPool(props: {\n\treadonly port: number;\n\treadonly tcpPool: TcpPoolConfig | null;\n}): void {\n\tif (props.tcpPool === null) {\n\t\treturn;\n\t}\n\tconst firstTcpPoolPort = props.tcpPool.basePort;\n\tconst lastTcpPoolPortExclusive = props.tcpPool.basePort + props.tcpPool.size;\n\tif (props.port >= firstTcpPoolPort && props.port < lastTcpPoolPortExclusive) {\n\t\tthrow new Error(\n\t\t\t`MCP Portal port ${String(props.port)} overlaps the Tool VM TCP pool ` +\n\t\t\t\t`[${String(firstTcpPoolPort)}, ${String(lastTcpPoolPortExclusive)}).`,\n\t\t);\n\t}\n}\n\nfunction createLoggerAdapter(api: OpenClawPortalPluginApi): {\n\treadonly error: (message: string) => void;\n\treadonly info: (message: string) => void;\n\treadonly warn: (message: string) => void;\n} {\n\treturn {\n\t\terror: (message) => api.logger?.error?.(message),\n\t\tinfo: (message) => api.logger?.info?.(message),\n\t\twarn: (message) => api.logger?.warn?.(message),\n\t};\n}\n\nexport function validatePortalPluginApi(api: OpenClawPortalPluginApi): void {\n\tif (!hasFunction(api.registerService)) {\n\t\tthrow new Error('MCP Portal plugin requires OpenClaw registerService API.');\n\t}\n\tif (!hasFunction(api.on) && !hasFunction(api.registerPromptHook)) {\n\t\tthrow new Error('MCP Portal plugin requires OpenClaw prompt hook registration API.');\n\t}\n\tconst hasLifecycleCleanupApi =\n\t\thasFunction(api.lifecycle?.registerRuntimeLifecycle) ||\n\t\thasFunction(api.registerRuntimeLifecycle);\n\tif (hasLifecycleCleanupApi) {\n\t\treturn;\n\t}\n\tthrow new Error('MCP Portal plugin requires an OpenClaw lifecycle cleanup API.');\n}\n\nfunction registerPortalRuntimeCleanup(\n\tapi: OpenClawPortalPluginApi,\n\tcleanup: () => Promise<void> | void,\n): void {\n\tconst runtimeLifecycle = {\n\t\tcleanup: async () => {\n\t\t\tawait cleanup();\n\t\t},\n\t\tdescription: 'Stops the MCP Portal subprocess supervised by the agent-vm plugin.',\n\t\tid: 'mcp-portal-subprocess',\n\t} satisfies Parameters<NonNullable<OpenClawPortalPluginApi['registerRuntimeLifecycle']>>[0];\n\tif (hasFunction(api.lifecycle?.registerRuntimeLifecycle)) {\n\t\tapi.lifecycle.registerRuntimeLifecycle(runtimeLifecycle);\n\t\treturn;\n\t}\n\tif (hasFunction(api.registerRuntimeLifecycle)) {\n\t\tapi.registerRuntimeLifecycle(runtimeLifecycle);\n\t\treturn;\n\t}\n\tthrow new Error('MCP Portal plugin requires an OpenClaw lifecycle cleanup API.');\n}\n\nfunction registerPortalService(props: {\n\treadonly api: OpenClawPortalPluginApi;\n\treadonly configDir: string;\n\treadonly runtimeState: PortalPluginRuntimeState;\n}): { readonly getSupervisor: () => PortalSubprocessSupervisor | null } {\n\tconst portalConfig = parsePortalConfig(props.api.pluginConfig ?? {});\n\tlet supervisor: PortalSubprocessSupervisor | null = null;\n\n\tprops.api.registerService?.({\n\t\tid: 'mcp-portal-subprocess',\n\t\tstart: async () => {\n\t\t\tconst mcpPortalConfig = await props.runtimeState.loadPortalConfig();\n\t\t\tvalidatePortalPortAgainstTcpPool({\n\t\t\t\tport: mcpPortalConfig.server.port,\n\t\t\t\ttcpPool: tcpPoolConfigFromApi(props.api),\n\t\t\t});\n\t\t\tconst keyRegistry = createHmacKeyRegistry({\n\t\t\t\tagentIds: Object.keys(mcpPortalConfig.agents).toSorted(),\n\t\t\t});\n\t\t\tprops.runtimeState.setKeyRegistry(keyRegistry);\n\t\t\tsupervisor = createPortalSubprocessSupervisor({\n\t\t\t\tbinPath: portalConfig.binPath,\n\t\t\t\tconfigDir: props.configDir,\n\t\t\t\thost: mcpPortalConfig.server.host,\n\t\t\t\thmacEnv: keyRegistry.serializeForEnv(),\n\t\t\t\tlogger: createLoggerAdapter(props.api),\n\t\t\t\tonFatal: (reason) => {\n\t\t\t\t\tprops.runtimeState.markPortalUnavailable(reason);\n\t\t\t\t\tprops.api.logger?.error?.(`[mcp-portal] subprocess supervisor fatal: ${reason}`);\n\t\t\t\t},\n\t\t\t\tport: mcpPortalConfig.server.port,\n\t\t\t});\n\t\t\tawait supervisor.start();\n\t\t\tprops.runtimeState.markPortalAvailable();\n\t\t},\n\t\tstop: async () => {\n\t\t\tawait supervisor?.stop();\n\t\t},\n\t});\n\n\treturn { getSupervisor: () => supervisor };\n}\n\nexport function registerMcpPortalPlugin(api: OpenClawPortalPluginApi): void {\n\tif (api.registrationMode !== undefined && api.registrationMode !== 'full') {\n\t\treturn;\n\t}\n\tvalidatePortalPluginApi(api);\n\tconst configDir = resolveConfigDir(api);\n\tconst runtimeState = createPortalPluginRuntimeState({ configDir });\n\tconst registeredService = registerPortalService({ api, configDir, runtimeState });\n\n\tapi.on?.(\n\t\t'before_tool_call',\n\t\tcreateBeforeToolCallHandler({ logger: createLoggerAdapter(api), runtimeState }),\n\t\t{\n\t\t\tpriority: 80,\n\t\t},\n\t);\n\n\tapi.on?.('before_prompt_build', createBeforePromptBuildHandler({ runtimeState }), {\n\t\tpriority: 80,\n\t});\n\n\tif (!api.on && api.registerPromptHook) {\n\t\tapi.registerPromptHook('before_prompt_build', async (context) => {\n\t\t\tconst handler = createBeforePromptBuildHandler({ runtimeState });\n\t\t\tconst result = await handler({}, context);\n\t\t\tif (result?.appendSystemContext !== undefined) {\n\t\t\t\tcontext.appendPrompt?.(result.appendSystemContext);\n\t\t\t}\n\t\t});\n\t}\n\n\tregisterPortalRuntimeCleanup(api, () => registeredService.getSupervisor()?.stop());\n\tvoid runtimeState.loadPortalConfig().catch((error: unknown) => {\n\t\tapi.logger?.error?.(\n\t\t\t`[mcp-portal] failed to initialize portal config: ${messageFromUnknown(error)}`,\n\t\t);\n\t});\n}\n\nconst pluginEntry = {\n\tdescription: 'Supervises the MCP Portal subprocess and wires per-agent approval hooks.',\n\tid: pluginId,\n\tname: 'MCP Portal',\n\tregister: registerMcpPortalPlugin,\n} satisfies PortalPluginEntry;\n\nexport default pluginEntry;\n","export interface PortalPromptNamespaceSummary {\n\treadonly namespace: string;\n\treadonly toolCount: number;\n}\n\nexport interface PortalPromptDiagnostic {\n\treadonly message: string;\n\treadonly namespace: string;\n}\n\nexport function createPortalPromptContext(props: {\n\treadonly diagnostics?: readonly PortalPromptDiagnostic[];\n\treadonly namespaces: readonly PortalPromptNamespaceSummary[];\n}): string {\n\tconst namespaceList =\n\t\tprops.namespaces.length > 0\n\t\t\t? props.namespaces.map((entry) => `${entry.namespace}(${entry.toolCount} tools)`).join(', ')\n\t\t\t: 'none configured';\n\tconst diagnostics =\n\t\tprops.diagnostics !== undefined && props.diagnostics.length > 0\n\t\t\t? [\n\t\t\t\t\t`Discovery diagnostics: ${props.diagnostics\n\t\t\t\t\t\t.map((entry) => `${entry.namespace}: ${entry.message}`)\n\t\t\t\t\t\t.join('; ')}`,\n\t\t\t\t]\n\t\t\t: [];\n\n\treturn [\n\t\t'MCP Portal is available as an MCP server.',\n\t\t'Use mcp_portal_list with requests[], mcp_portal_search with requests[],',\n\t\t'mcp_portal_describe with requests[], and mcp_portal_call with calls[].',\n\t\t'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.',\n\t\t'Call upstream tools by namespace + toolName inside calls[].',\n\t\t'Call mcp_portal_describe before mcp_portal_call unless you already saw the full schema for that tool in this portal session.',\n\t\t'Gateway owns MCP auth.',\n\t\t`Namespaces: ${namespaceList}`,\n\t\t...diagnostics,\n\t].join('\\n');\n}\n","import { redactCredentialText } from '@agent-vm/mcp-portal';\n\nexport function redactPortalSecrets(text: string, secretValues: readonly string[] = []): string {\n\treturn secretValues\n\t\t.filter((secretValue) => secretValue.length > 0)\n\t\t.reduce(\n\t\t\t(current, secretValue) => current.split(secretValue).join('[REDACTED]'),\n\t\t\tredactCredentialText(text),\n\t\t);\n}\n","export * from './openclaw-plugin-api.js';\nexport * from './plugin-registration.js';\nexport * from './before-prompt-build-handler.js';\nexport * from './before-tool-call-handler.js';\nexport * from './hmac-key-registry.js';\nexport * from './portal-config.js';\nexport * from './portal-plugin-runtime-state.js';\nexport * from './portal-subprocess-supervisor.js';\nexport * from './portal-tool-policy.js';\nexport * from './portal-prompt-context.js';\nexport * from './redaction.js';\nexport { default } from './plugin-registration.js';\n\nexport const OPENCLAW_MCP_PORTAL_PLUGIN_PACKAGE_NAME = '@agent-vm/openclaw-mcp-portal-plugin';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["isObjectRecord","isObjectRecord"],"sources":["../src/before-prompt-build-handler.ts","../src/portal-tool-policy.ts","../src/before-tool-call-handler.ts","../src/effective-config-manifest.ts","../src/portal-config.ts","../src/portal-plugin-runtime-state.ts","../src/plugin-registration.ts","../src/portal-prompt-context.ts","../src/redaction.ts","../src/index.ts"],"sourcesContent":["import { resolveMcpPortalProfile } from '@agent-vm/config-contracts';\n\nimport type {\n\tOpenClawBeforePromptBuildEvent,\n\tOpenClawPluginHookContext,\n\tOpenClawPromptHookResult,\n} from './openclaw-plugin-api.js';\nimport type { PortalPluginRuntimeState } from './portal-plugin-runtime-state.js';\n\nexport interface CreateBeforePromptBuildHandlerProps {\n\treadonly runtimeState: PortalPluginRuntimeState;\n}\n\nexport function createBeforePromptBuildHandler(\n\tprops: CreateBeforePromptBuildHandlerProps,\n): (\n\tevent: OpenClawBeforePromptBuildEvent,\n\tcontext: OpenClawPluginHookContext,\n) => Promise<OpenClawPromptHookResult | undefined> {\n\treturn async (_event, context) => {\n\t\tconst agentId = context.agentId;\n\t\tif (agentId === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst portalConfig = await props.runtimeState.loadPortalConfig();\n\t\tconst agent = portalConfig.agents[agentId];\n\t\tif (agent === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst profile = resolveMcpPortalProfile(portalConfig, agent.profile);\n\t\tif (!profile.promptContext.enabled) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst namespaces = profile.enabledNamespaces\n\t\t\t.toSorted()\n\t\t\t.slice(0, profile.promptContext.maxNamespaces);\n\t\tconst namespaceText =\n\t\t\tnamespaces.length === 0\n\t\t\t\t? ' (none in your profile)'\n\t\t\t\t: namespaces.map((name) => ` ${name}`).join('\\n');\n\t\treturn {\n\t\t\tappendSystemContext: [\n\t\t\t\t'MCP Portal namespaces available to this agent:',\n\t\t\t\tnamespaceText,\n\t\t\t\t'Use mcp_portal_search to find tools by intent, then mcp_portal_describe before mcp_portal_call.',\n\t\t\t].join('\\n'),\n\t\t};\n\t};\n}\n","import {\n\tmcpPortalCallRequiresApproval,\n\ttype ResolvedMcpPortalProfile,\n} from '@agent-vm/config-contracts';\n\nexport interface PortalCallRequest {\n\treadonly arguments: Record<string, unknown>;\n\treadonly id: string;\n\treadonly namespace: string;\n\treadonly toolName: string;\n}\n\nexport function profileAllowsPortalCall(\n\tprofile: ResolvedMcpPortalProfile,\n\tcall: { readonly namespace: string; readonly toolName: string },\n): boolean {\n\tif (!profile.enabledNamespaces.includes(call.namespace)) {\n\t\treturn false;\n\t}\n\tconst enabledTools = profile.enabledToolsByNamespace[call.namespace] ?? [];\n\tif (enabledTools.length > 0 && !enabledTools.includes(call.toolName)) {\n\t\treturn false;\n\t}\n\tconst hiddenTools = profile.hiddenToolsByNamespace[call.namespace] ?? [];\n\treturn !hiddenTools.includes(call.toolName);\n}\n\nexport function profileRequiresPortalApproval(\n\tprofile: ResolvedMcpPortalProfile,\n\tcall: { readonly namespace: string; readonly toolName: string },\n): boolean {\n\treturn mcpPortalCallRequiresApproval(profile, call);\n}\n","import { resolveMcpPortalProfile } from '@agent-vm/config-contracts';\n\nimport type {\n\tOpenClawBeforeToolCallEvent,\n\tOpenClawBeforeToolCallResult,\n\tOpenClawPluginHookContext,\n} from './openclaw-plugin-api.js';\nimport type { PortalPluginRuntimeState } from './portal-plugin-runtime-state.js';\nimport {\n\tprofileAllowsPortalCall,\n\tprofileRequiresPortalApproval,\n\ttype PortalCallRequest,\n} from './portal-tool-policy.js';\n\nexport interface CreateBeforeToolCallHandlerProps {\n\treadonly logger?: {\n\t\treadonly warn?: (message: string) => void;\n\t};\n\treadonly runtimeState: PortalPluginRuntimeState;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction parseCallRequest(value: unknown): PortalCallRequest | null {\n\tif (!isObjectRecord(value)) {\n\t\treturn null;\n\t}\n\tconst id = value.id;\n\tconst namespace = value.namespace;\n\tconst toolName = value.toolName;\n\tconst argumentsValue = value.arguments;\n\tif (\n\t\ttypeof id !== 'string' ||\n\t\ttypeof namespace !== 'string' ||\n\t\ttypeof toolName !== 'string' ||\n\t\t!isObjectRecord(argumentsValue)\n\t) {\n\t\treturn null;\n\t}\n\treturn { arguments: argumentsValue, id, namespace, toolName };\n}\n\nfunction parseCallRequests(params: Record<string, unknown>): readonly PortalCallRequest[] | null {\n\tconst calls = params.calls;\n\tif (!Array.isArray(calls)) {\n\t\treturn null;\n\t}\n\tconst parsedCalls: PortalCallRequest[] = [];\n\tfor (const call of calls) {\n\t\tconst parsedCall = parseCallRequest(call);\n\t\tif (parsedCall === null) {\n\t\t\treturn null;\n\t\t}\n\t\tparsedCalls.push(parsedCall);\n\t}\n\treturn parsedCalls;\n}\n\nexport function createBeforeToolCallHandler(\n\tprops: CreateBeforeToolCallHandlerProps,\n): (\n\tevent: OpenClawBeforeToolCallEvent,\n\tcontext: OpenClawPluginHookContext,\n) => Promise<OpenClawBeforeToolCallResult | undefined> {\n\treturn async (event, context) => {\n\t\tif (event.toolName !== 'mcp_portal_call') {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (context.agentId === undefined) {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\tblockReason: `mcp-portal: missing OpenClaw agent context for ${event.toolName}.`,\n\t\t\t};\n\t\t}\n\t\tconst portalConfig = await props.runtimeState.loadPortalConfig();\n\t\tconst agentId = context.agentId;\n\t\tconst agent = portalConfig.agents[agentId];\n\t\tif (agent === undefined) {\n\t\t\treturn { block: true, blockReason: `mcp-portal: agent \"${agentId}\" is not configured.` };\n\t\t}\n\t\tconst profile = resolveMcpPortalProfile(portalConfig, agent.profile);\n\t\tconst calls = parseCallRequests(event.params);\n\t\tif (calls === null || calls.length === 0) {\n\t\t\treturn { block: true, blockReason: 'mcp-portal: malformed portal call batch.' };\n\t\t}\n\n\t\tfor (const call of calls) {\n\t\t\tif (!profileAllowsPortalCall(profile, call)) {\n\t\t\t\treturn {\n\t\t\t\t\tblock: true,\n\t\t\t\t\tblockReason: `policy: ${agentId}/${call.namespace}/${call.toolName} not enabled`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst approvalCalls = calls.filter((call) => profileRequiresPortalApproval(profile, call));\n\t\tif (approvalCalls.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst toolNames = approvalCalls\n\t\t\t.map((call) => `${call.namespace}.${call.toolName}`)\n\t\t\t.toSorted()\n\t\t\t.join(', ');\n\t\treturn {\n\t\t\trequireApproval: {\n\t\t\t\tdescription: `Allow MCP Portal batch for agent ${agentId}: ${toolNames}.`,\n\t\t\t\tpluginId: 'mcp-portal',\n\t\t\t\tseverity: 'warning',\n\t\t\t\ttimeoutBehavior: 'deny',\n\t\t\t\ttimeoutMs: 60_000,\n\t\t\t\ttitle: `MCP Portal batch: ${toolNames}`,\n\t\t\t},\n\t\t};\n\t};\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nconst effectiveConfigManifestFileName = 'mcp-portal-effective-manifest.json';\n\nexport interface EffectiveConfigPaths {\n\treadonly mcpConfigPath: string;\n\treadonly portalConfigPath: string;\n}\n\nfunction isObjectRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isSafeManifestFileName(value: string): boolean {\n\treturn value.length > 0 && !value.includes('/') && !value.includes('\\\\');\n}\n\nfunction parseEffectiveConfigManifest(value: unknown): {\n\treadonly mcpConfigFile: string;\n\treadonly portalConfigFile: string;\n} {\n\tif (!isObjectRecord(value)) {\n\t\tthrow new Error('MCP Portal effective config manifest must be an object.');\n\t}\n\tif (value.schemaVersion !== 1) {\n\t\tthrow new Error('MCP Portal effective config manifest must use schemaVersion 1.');\n\t}\n\tif (typeof value.mcpConfigFile !== 'string' || !isSafeManifestFileName(value.mcpConfigFile)) {\n\t\tthrow new Error('MCP Portal effective config manifest must contain a safe mcpConfigFile.');\n\t}\n\tif (\n\t\ttypeof value.portalConfigFile !== 'string' ||\n\t\t!isSafeManifestFileName(value.portalConfigFile)\n\t) {\n\t\tthrow new Error('MCP Portal effective config manifest must contain a safe portalConfigFile.');\n\t}\n\treturn {\n\t\tmcpConfigFile: value.mcpConfigFile,\n\t\tportalConfigFile: value.portalConfigFile,\n\t};\n}\n\nfunction isMissingFileError(error: unknown): boolean {\n\treturn (\n\t\tisObjectRecord(error) &&\n\t\ttypeof error.code === 'string' &&\n\t\t(error.code === 'ENOENT' || error.code === 'ENOTDIR')\n\t);\n}\n\nexport async function resolveEffectiveConfigPaths(\n\tconfigDir: string,\n): Promise<EffectiveConfigPaths> {\n\tconst manifestPath = join(configDir, effectiveConfigManifestFileName);\n\tlet manifestText: string;\n\ttry {\n\t\tmanifestText = await readFile(manifestPath, 'utf8');\n\t} catch (error) {\n\t\tif (isMissingFileError(error)) {\n\t\t\treturn {\n\t\t\t\tmcpConfigPath: join(configDir, 'mcp.config.jsonc'),\n\t\t\t\tportalConfigPath: join(configDir, 'mcp-portal.config.jsonc'),\n\t\t\t};\n\t\t}\n\t\tthrow error;\n\t}\n\tconst manifest = parseEffectiveConfigManifest(JSON.parse(manifestText));\n\treturn {\n\t\tmcpConfigPath: join(configDir, manifest.mcpConfigFile),\n\t\tportalConfigPath: join(configDir, manifest.portalConfigFile),\n\t};\n}\n","import { z } from 'zod';\n\nexport const portalPluginConfigSchema = z\n\t.object({\n\t\tconfigDir: z.string().min(1),\n\t})\n\t.strict();\n\nexport type PortalPluginConfig = z.infer<typeof portalPluginConfigSchema>;\n\nexport function parsePortalConfig(value: unknown): PortalPluginConfig {\n\treturn portalPluginConfigSchema.parse(value ?? {});\n}\n","import { loadMcpPortalConfig, type McpPortalConfig } from '@agent-vm/config-contracts';\n\nimport { resolveEffectiveConfigPaths } from './effective-config-manifest.js';\n\nexport interface PortalPluginRuntimeState {\n\treadonly configDir: string;\n\treadonly getLoadedPortalConfig: () => McpPortalConfig | null;\n\treadonly getPortalUnavailableReason: () => string | null;\n\treadonly loadPortalConfig: () => Promise<McpPortalConfig>;\n\treadonly markPortalAvailable: () => void;\n\treadonly markPortalUnavailable: (reason: string) => void;\n}\n\nexport function createPortalPluginRuntimeState(props: {\n\treadonly configDir: string;\n\treadonly loadPortalConfig?: (path: string) => Promise<McpPortalConfig>;\n}): PortalPluginRuntimeState {\n\tlet loadedPortalConfig: McpPortalConfig | null = null;\n\tlet portalConfigPromise: Promise<McpPortalConfig> | null = null;\n\tlet portalUnavailableReason: string | null = null;\n\tconst loadPortalConfigFile = props.loadPortalConfig ?? loadMcpPortalConfig;\n\n\tfunction loadPortalConfig(): Promise<McpPortalConfig> {\n\t\tif (portalConfigPromise !== null) {\n\t\t\treturn portalConfigPromise;\n\t\t}\n\t\tconst nextPromise = resolveEffectiveConfigPaths(props.configDir)\n\t\t\t.then((effectiveConfigPaths) => loadPortalConfigFile(effectiveConfigPaths.portalConfigPath))\n\t\t\t.then((portalConfig) => {\n\t\t\t\tloadedPortalConfig = portalConfig;\n\t\t\t\treturn portalConfig;\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tif (portalConfigPromise === nextPromise) {\n\t\t\t\t\tportalConfigPromise = null;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t});\n\t\tportalConfigPromise = nextPromise;\n\t\treturn portalConfigPromise;\n\t}\n\n\treturn {\n\t\tconfigDir: props.configDir,\n\t\tgetLoadedPortalConfig: () => loadedPortalConfig,\n\t\tgetPortalUnavailableReason: () => portalUnavailableReason,\n\t\tloadPortalConfig,\n\t\tmarkPortalAvailable: () => {\n\t\t\tportalUnavailableReason = null;\n\t\t},\n\t\tmarkPortalUnavailable: (reason) => {\n\t\t\tportalUnavailableReason = reason;\n\t\t},\n\t};\n}\n","import {\n\tloadMcpConfig,\n\tloadMcpPortalConfig,\n\ttype McpPortalConfig,\n\tresolveMcpPortalProfile,\n\ttype ResolvedMcpPortalProfile,\n\ttype SecretValue,\n} from '@agent-vm/config-contracts';\nimport {\n\tcreatePortalCore,\n\tcreateUpstreamMcpClientRuntime,\n\tlistPortalCoreToolDescriptors,\n\tresolveUpstreamServers,\n\ttype PortalCore,\n\ttype PortalCoreEvent,\n\ttype PortalCoreToolDescriptor,\n\ttype PortalToolSelector,\n} from '@agent-vm/mcp-portal/core';\n\nimport { createBeforePromptBuildHandler } from './before-prompt-build-handler.js';\nimport { createBeforeToolCallHandler } from './before-tool-call-handler.js';\nimport { resolveEffectiveConfigPaths } from './effective-config-manifest.js';\nimport type {\n\tOpenClawPortalPluginApi,\n\tOpenClawPluginToolContext,\n\tOpenClawToolRegistration,\n\tOpenClawToolUpdateCallback,\n} from './openclaw-plugin-api.js';\nimport { parsePortalConfig } from './portal-config.js';\nimport { createPortalPluginRuntimeState } from './portal-plugin-runtime-state.js';\n\ninterface PortalPluginEntry {\n\treadonly description: string;\n\treadonly id: string;\n\treadonly name: string;\n\treadonly register: (api: OpenClawPortalPluginApi) => void;\n}\n\ninterface TcpPoolConfig {\n\treadonly basePort: number;\n\treadonly size: number;\n}\n\ninterface ProfilePolicyMaps {\n\treadonly cacheTtlMs: number;\n\treadonly enabledNamespacesByAgent: Readonly<Record<string, readonly string[]>>;\n\treadonly enabledToolsByAgent: Readonly<Record<string, readonly PortalToolSelector[]>>;\n\treadonly hiddenToolsByAgent: Readonly<Record<string, readonly PortalToolSelector[]>>;\n}\n\nconst pluginId = 'mcp-portal';\n\nfunction hasFunction(value: unknown): value is (...args: readonly unknown[]) => unknown {\n\treturn typeof value === 'function';\n}\n\nfunction isObjectRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isUnknownArray(value: unknown): value is readonly unknown[] {\n\treturn Array.isArray(value);\n}\n\nfunction getObjectProperty(value: unknown, property: string): unknown {\n\treturn isObjectRecord(value) ? value[property] : undefined;\n}\n\nfunction resolveConfigDir(api: OpenClawPortalPluginApi): string {\n\tif (api.pluginConfig !== undefined) {\n\t\treturn parsePortalConfig(api.pluginConfig).configDir;\n\t}\n\t// Managed agent-vm passes pluginConfig. The config fallbacks keep the plugin usable\n\t// in direct OpenClaw test harnesses that load plugin config through the root config.\n\tconst topLevelMcpConfigDir = getObjectProperty(\n\t\tgetObjectProperty(api.config, 'mcpPortal'),\n\t\t'configDir',\n\t);\n\tif (typeof topLevelMcpConfigDir === 'string' && topLevelMcpConfigDir.length > 0) {\n\t\treturn topLevelMcpConfigDir;\n\t}\n\tconst zones = getObjectProperty(api.config, 'zones');\n\tif (isUnknownArray(zones)) {\n\t\tconst firstZone = zones.at(0);\n\t\tconst zoneMcpConfigDir = getObjectProperty(\n\t\t\tgetObjectProperty(firstZone, 'mcpPortal'),\n\t\t\t'configDir',\n\t\t);\n\t\tif (typeof zoneMcpConfigDir === 'string' && zoneMcpConfigDir.length > 0) {\n\t\t\treturn zoneMcpConfigDir;\n\t\t}\n\t}\n\tthrow new Error(\n\t\t'MCP Portal plugin requires configDir in plugin config or zone mcpPortal config.',\n\t);\n}\n\nexport function validatePortalPortAgainstTcpPool(props: {\n\treadonly port: number;\n\treadonly tcpPool: TcpPoolConfig | null;\n}): void {\n\tif (props.tcpPool === null) {\n\t\treturn;\n\t}\n\tconst firstTcpPoolPort = props.tcpPool.basePort;\n\tconst lastTcpPoolPortExclusive = props.tcpPool.basePort + props.tcpPool.size;\n\tif (props.port >= firstTcpPoolPort && props.port < lastTcpPoolPortExclusive) {\n\t\tthrow new Error(\n\t\t\t`MCP Portal port ${String(props.port)} overlaps the Tool VM TCP pool ` +\n\t\t\t\t`[${String(firstTcpPoolPort)}, ${String(lastTcpPoolPortExclusive)}).`,\n\t\t);\n\t}\n}\n\nfunction createLoggerAdapter(api: OpenClawPortalPluginApi): {\n\treadonly error: (message: string) => void;\n\treadonly info: (message: string) => void;\n\treadonly warn: (message: string) => void;\n} {\n\treturn {\n\t\terror: (message) => api.logger?.error?.(message),\n\t\tinfo: (message) => api.logger?.info?.(message),\n\t\twarn: (message) => api.logger?.warn?.(message),\n\t};\n}\n\nexport function validatePortalPluginApi(api: OpenClawPortalPluginApi): void {\n\tif (!hasFunction(api.registerTool)) {\n\t\tthrow new Error('MCP Portal plugin requires OpenClaw registerTool API.');\n\t}\n\tif (!hasFunction(api.on)) {\n\t\tthrow new Error('MCP Portal plugin requires OpenClaw before_tool_call hook API.');\n\t}\n\tconst hasLifecycleCleanupApi =\n\t\thasFunction(api.lifecycle?.registerRuntimeLifecycle) ||\n\t\thasFunction(api.registerRuntimeLifecycle);\n\tif (hasLifecycleCleanupApi) {\n\t\treturn;\n\t}\n\tthrow new Error('MCP Portal plugin requires an OpenClaw lifecycle cleanup API.');\n}\n\nfunction registerPortalRuntimeCleanup(\n\tapi: OpenClawPortalPluginApi,\n\tcleanup: () => Promise<void> | void,\n): void {\n\tconst runtimeLifecycle = {\n\t\tcleanup: async () => {\n\t\t\tawait cleanup();\n\t\t},\n\t\tdescription: 'Closes MCP Portal upstream clients owned by the agent-vm plugin.',\n\t\tid: 'mcp-portal-core',\n\t} satisfies Parameters<NonNullable<OpenClawPortalPluginApi['registerRuntimeLifecycle']>>[0];\n\tif (hasFunction(api.lifecycle?.registerRuntimeLifecycle)) {\n\t\tapi.lifecycle.registerRuntimeLifecycle(runtimeLifecycle);\n\t\treturn;\n\t}\n\tif (hasFunction(api.registerRuntimeLifecycle)) {\n\t\tapi.registerRuntimeLifecycle(runtimeLifecycle);\n\t\treturn;\n\t}\n\tthrow new Error('MCP Portal plugin requires an OpenClaw lifecycle cleanup API.');\n}\n\nfunction selectorsFromNamespaceTools(\n\tnamespaceTools: Readonly<Record<string, readonly string[]>>,\n): readonly PortalToolSelector[] {\n\treturn Object.entries(namespaceTools).flatMap(([namespace, toolNames]) =>\n\t\ttoolNames.map((toolName) => ({ namespace, toolName })),\n\t);\n}\n\nfunction buildProfilePolicyMaps(portalConfig: McpPortalConfig): ProfilePolicyMaps {\n\tconst enabledNamespacesByAgent: Record<string, readonly string[]> = {};\n\tconst enabledToolsByAgent: Record<string, readonly PortalToolSelector[]> = {};\n\tconst hiddenToolsByAgent: Record<string, readonly PortalToolSelector[]> = {};\n\tconst profileTtls: number[] = [];\n\n\tfor (const [agentId, agent] of Object.entries(portalConfig.agents)) {\n\t\tconst profile: ResolvedMcpPortalProfile = resolveMcpPortalProfile(portalConfig, agent.profile);\n\t\tenabledNamespacesByAgent[agentId] = profile.enabledNamespaces;\n\t\tenabledToolsByAgent[agentId] = selectorsFromNamespaceTools(profile.enabledToolsByNamespace);\n\t\thiddenToolsByAgent[agentId] = selectorsFromNamespaceTools(profile.hiddenToolsByNamespace);\n\t\tprofileTtls.push(profile.cache.catalogTtlMs);\n\t}\n\n\treturn {\n\t\tcacheTtlMs: profileTtls.length === 0 ? 60_000 : Math.min(...profileTtls),\n\t\tenabledNamespacesByAgent,\n\t\tenabledToolsByAgent,\n\t\thiddenToolsByAgent,\n\t};\n}\n\nasync function resolveManagedPortalSecret(secret: SecretValue): Promise<string> {\n\tif (secret.source !== 'environment') {\n\t\tthrow new Error(\n\t\t\t'MCP Portal managed OpenClaw effective config must use environment secret refs.',\n\t\t);\n\t}\n\tconst value = process.env[secret.name];\n\tif (value === undefined || value.length === 0) {\n\t\tthrow new Error(`Missing environment secret ${secret.name} for MCP Portal native plugin.`);\n\t}\n\treturn value;\n}\n\nasync function createManagedPortalCore(configDir: string): Promise<PortalCore> {\n\tconst effectiveConfigPaths = await resolveEffectiveConfigPaths(configDir);\n\tconst [mcpConfig, portalConfig] = await Promise.all([\n\t\tloadMcpConfig(effectiveConfigPaths.mcpConfigPath),\n\t\tloadMcpPortalConfig(effectiveConfigPaths.portalConfigPath),\n\t]);\n\tconst upstreamServers = await resolveUpstreamServers({\n\t\tconfig: mcpConfig,\n\t\tresolveSecret: resolveManagedPortalSecret,\n\t});\n\tconst upstreamRuntime = createUpstreamMcpClientRuntime({ servers: upstreamServers });\n\tconst profilePolicyMaps = buildProfilePolicyMaps(portalConfig);\n\n\treturn createPortalCore({\n\t\taccessPolicy: {\n\t\t\tdefaultPolicy: 'deny-all',\n\t\t\tenabledNamespacesByAgent: profilePolicyMaps.enabledNamespacesByAgent,\n\t\t\tenabledToolsByAgent: profilePolicyMaps.enabledToolsByAgent,\n\t\t\thiddenToolsByAgent: profilePolicyMaps.hiddenToolsByAgent,\n\t\t},\n\t\tapprovalTrustBoundary: 'openclaw-before-tool-call-hook',\n\t\tcatalogTtlMs: profilePolicyMaps.cacheTtlMs,\n\t\truntime: {\n\t\t\tcallUpstreamTool: upstreamRuntime.callTool,\n\t\t\tcloseAgentScope: upstreamRuntime.closeAgentScope,\n\t\t\tcloseSession: upstreamRuntime.closeSession,\n\t\t\tlistTools: upstreamRuntime.listTools,\n\t\t},\n\t\tupstreamNamespaces: upstreamServers.map((server) => server.namespace),\n\t});\n}\n\nfunction portalUpdateFromCoreEvent(event: PortalCoreEvent): Record<string, unknown> | null {\n\tif (event.kind === 'progress') {\n\t\treturn {\n\t\t\tmessage: event.message ?? 'MCP Portal progress',\n\t\t\t...(event.progress !== undefined ? { progress: event.progress } : {}),\n\t\t\trequestId: event.requestId,\n\t\t\t...(event.total !== undefined ? { total: event.total } : {}),\n\t\t\ttype: 'mcp_portal_progress',\n\t\t};\n\t}\n\tif (event.kind === 'partial_content') {\n\t\treturn {\n\t\t\tcontent: event.content,\n\t\t\trequestId: event.requestId,\n\t\t\ttype: 'mcp_portal_partial_content',\n\t\t};\n\t}\n\tif (event.kind === 'upstream_notification') {\n\t\treturn {\n\t\t\tmethod: event.method,\n\t\t\tparams: event.params,\n\t\t\trequestId: event.requestId,\n\t\t\ttype: 'mcp_portal_upstream_notification',\n\t\t};\n\t}\n\treturn null;\n}\n\nasync function forwardCoreEvent(\n\tevent: PortalCoreEvent,\n\tlogger: ReturnType<typeof createLoggerAdapter>,\n\tonUpdate: OpenClawToolUpdateCallback | undefined,\n): Promise<void> {\n\tconst update = portalUpdateFromCoreEvent(event);\n\tif (update !== null) {\n\t\ttry {\n\t\t\tawait onUpdate?.(update);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tlogger.warn(`[mcp-portal] OpenClaw onUpdate delivery failed: ${message}`);\n\t\t}\n\t}\n}\n\nfunction createNativeTool(props: {\n\treadonly context: OpenClawPluginToolContext;\n\treadonly descriptor: PortalCoreToolDescriptor;\n\treadonly getCore: () => Promise<PortalCore>;\n\treadonly logger: ReturnType<typeof createLoggerAdapter>;\n}): OpenClawToolRegistration {\n\treturn {\n\t\tdescription: props.descriptor.description,\n\t\texecute: async (_toolCallId, params, signal, onUpdate) => {\n\t\t\tif (props.context.agentId === undefined || props.context.agentId.length === 0) {\n\t\t\t\tthrow new Error('mcp-portal: OpenClaw did not provide a trusted agentId.');\n\t\t\t}\n\t\t\tconst core = await props.getCore();\n\t\t\tconst scope = core.createAgentScope({\n\t\t\t\tagentId: props.context.agentId,\n\t\t\t\tagentScopeId: props.context.agentId,\n\t\t\t\t...(props.context.sessionId ? { sessionId: props.context.sessionId } : {}),\n\t\t\t\t...(props.context.sessionKey ? { sessionKey: props.context.sessionKey } : {}),\n\t\t\t\tsource: 'openclaw-trusted',\n\t\t\t});\n\t\t\tconst result = await core.collectPortalCoreResult(\n\t\t\t\tcore.callStream({\n\t\t\t\t\tinput: params,\n\t\t\t\t\tscope,\n\t\t\t\t\t...(signal !== undefined ? { signal } : {}),\n\t\t\t\t\ttoolName: props.descriptor.name,\n\t\t\t\t}),\n\t\t\t\t{ onEvent: (event) => forwardCoreEvent(event, props.logger, onUpdate) },\n\t\t\t);\n\t\t\treturn { content: JSON.stringify(result), details: result };\n\t\t},\n\t\tlabel: props.descriptor.name,\n\t\tname: props.descriptor.name,\n\t\tparameters: props.descriptor.inputSchema,\n\t};\n}\n\nfunction descriptorsForOpenClawContext(props: {\n\treadonly context: OpenClawPluginToolContext;\n\treadonly portalConfig: McpPortalConfig | null;\n}): readonly PortalCoreToolDescriptor[] {\n\tif (props.portalConfig === null || props.context.agentId === undefined) {\n\t\treturn listPortalCoreToolDescriptors();\n\t}\n\tconst agent = props.portalConfig.agents[props.context.agentId];\n\tif (agent === undefined) {\n\t\treturn listPortalCoreToolDescriptors();\n\t}\n\tconst profile = resolveMcpPortalProfile(props.portalConfig, agent.profile);\n\treturn listPortalCoreToolDescriptors(profile.enabledNamespaces);\n}\n\nfunction registerNativePortalTools(props: {\n\treadonly api: OpenClawPortalPluginApi;\n\treadonly getCore: () => Promise<PortalCore>;\n\treadonly runtimeState: ReturnType<typeof createPortalPluginRuntimeState>;\n}): void {\n\tconst descriptorNames = listPortalCoreToolDescriptors().map((descriptor) => descriptor.name);\n\tconst logger = createLoggerAdapter(props.api);\n\tprops.api.registerTool?.(\n\t\t(context) => {\n\t\t\tconst descriptors = descriptorsForOpenClawContext({\n\t\t\t\tcontext,\n\t\t\t\tportalConfig: props.runtimeState.getLoadedPortalConfig(),\n\t\t\t});\n\t\t\treturn descriptors.map((descriptor) =>\n\t\t\t\tcreateNativeTool({ context, descriptor, getCore: props.getCore, logger }),\n\t\t\t);\n\t\t},\n\t\t{\n\t\t\tnames: descriptorNames,\n\t\t\toptional: true,\n\t\t},\n\t);\n}\n\nexport function registerMcpPortalPlugin(api: OpenClawPortalPluginApi): void {\n\tif (api.registrationMode !== undefined && api.registrationMode !== 'full') {\n\t\treturn;\n\t}\n\tvalidatePortalPluginApi(api);\n\tconst configDir = resolveConfigDir(api);\n\tconst runtimeState = createPortalPluginRuntimeState({ configDir });\n\tlet corePromise: Promise<PortalCore> | undefined;\n\tconst getCore = (): Promise<PortalCore> => {\n\t\tcorePromise ??= createManagedPortalCore(configDir).catch((error: unknown) => {\n\t\t\tcorePromise = undefined;\n\t\t\tthrow error;\n\t\t});\n\t\treturn corePromise;\n\t};\n\tregisterNativePortalTools({ api, getCore, runtimeState });\n\n\tapi.on?.(\n\t\t'before_tool_call',\n\t\tcreateBeforeToolCallHandler({ logger: createLoggerAdapter(api), runtimeState }),\n\t\t{\n\t\t\tpriority: 80,\n\t\t},\n\t);\n\n\tapi.on?.('before_prompt_build', createBeforePromptBuildHandler({ runtimeState }), {\n\t\tpriority: 80,\n\t});\n\n\tif (!api.on && api.registerPromptHook) {\n\t\tapi.registerPromptHook('before_prompt_build', async (context) => {\n\t\t\tconst handler = createBeforePromptBuildHandler({ runtimeState });\n\t\t\tconst result = await handler({}, context);\n\t\t\tif (result?.appendSystemContext !== undefined) {\n\t\t\t\tcontext.appendPrompt?.(result.appendSystemContext);\n\t\t\t}\n\t\t});\n\t}\n\n\tregisterPortalRuntimeCleanup(api, async () => {\n\t\tconst core = await corePromise?.catch(() => undefined);\n\t\tawait core?.close();\n\t});\n}\n\nconst pluginEntry = {\n\tdescription: 'Registers native OpenClaw MCP Portal tools and wires per-agent approval hooks.',\n\tid: pluginId,\n\tname: 'MCP Portal',\n\tregister: registerMcpPortalPlugin,\n} satisfies PortalPluginEntry;\n\nexport default pluginEntry;\n","export interface PortalPromptNamespaceSummary {\n\treadonly namespace: string;\n\treadonly toolCount: number;\n}\n\nexport interface PortalPromptDiagnostic {\n\treadonly message: string;\n\treadonly namespace: string;\n}\n\nexport function createPortalPromptContext(props: {\n\treadonly diagnostics?: readonly PortalPromptDiagnostic[];\n\treadonly namespaces: readonly PortalPromptNamespaceSummary[];\n}): string {\n\tconst namespaceList =\n\t\tprops.namespaces.length > 0\n\t\t\t? props.namespaces.map((entry) => `${entry.namespace}(${entry.toolCount} tools)`).join(', ')\n\t\t\t: 'none configured';\n\tconst diagnostics =\n\t\tprops.diagnostics !== undefined && props.diagnostics.length > 0\n\t\t\t? [\n\t\t\t\t\t`Discovery diagnostics: ${props.diagnostics\n\t\t\t\t\t\t.map((entry) => `${entry.namespace}: ${entry.message}`)\n\t\t\t\t\t\t.join('; ')}`,\n\t\t\t\t]\n\t\t\t: [];\n\n\treturn [\n\t\t'MCP Portal is available as native OpenClaw tools.',\n\t\t'Use mcp_portal_list with requests[], mcp_portal_search with requests[],',\n\t\t'mcp_portal_describe with requests[], and mcp_portal_call with calls[].',\n\t\t'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.',\n\t\t'Call upstream tools by namespace + toolName inside calls[].',\n\t\t'Call mcp_portal_describe before mcp_portal_call unless you already saw the full schema for that tool in this portal session.',\n\t\t'Gateway owns MCP auth.',\n\t\t`Namespaces: ${namespaceList}`,\n\t\t...diagnostics,\n\t].join('\\n');\n}\n","import { redactCredentialText } from '@agent-vm/mcp-portal/core';\n\nexport function redactPortalSecrets(text: string, secretValues: readonly string[] = []): string {\n\treturn secretValues\n\t\t.filter((secretValue) => secretValue.length > 0)\n\t\t.reduce(\n\t\t\t(current, secretValue) => current.split(secretValue).join('[REDACTED]'),\n\t\t\tredactCredentialText(text),\n\t\t);\n}\n","export * from './openclaw-plugin-api.js';\nexport * from './plugin-registration.js';\nexport * from './before-prompt-build-handler.js';\nexport * from './before-tool-call-handler.js';\nexport * from './portal-config.js';\nexport * from './portal-plugin-runtime-state.js';\nexport * from './portal-tool-policy.js';\nexport * from './portal-prompt-context.js';\nexport * from './redaction.js';\nexport { default } from './plugin-registration.js';\n\nexport const OPENCLAW_MCP_PORTAL_PLUGIN_PACKAGE_NAME = '@agent-vm/openclaw-mcp-portal-plugin';\n"],"mappings":"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"}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "mcp-portal",
|
|
3
3
|
"name": "MCP Portal",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "Native OpenClaw MCP Portal tool adapter over configured upstream MCP servers.",
|
|
5
5
|
"activation": {
|
|
6
6
|
"onStartup": true
|
|
7
7
|
},
|
|
8
|
+
"contracts": {
|
|
9
|
+
"tools": ["mcp_portal_list", "mcp_portal_search", "mcp_portal_describe", "mcp_portal_call"]
|
|
10
|
+
},
|
|
11
|
+
"toolMetadata": {
|
|
12
|
+
"mcp_portal_list": { "optional": true },
|
|
13
|
+
"mcp_portal_search": { "optional": true },
|
|
14
|
+
"mcp_portal_describe": { "optional": true },
|
|
15
|
+
"mcp_portal_call": { "optional": true }
|
|
16
|
+
},
|
|
8
17
|
"configSchema": {
|
|
9
18
|
"type": "object",
|
|
10
19
|
"additionalProperties": false,
|
|
11
20
|
"properties": {
|
|
12
|
-
"binPath": { "type": "string" },
|
|
13
21
|
"configDir": { "type": "string" }
|
|
14
22
|
}
|
|
15
23
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-vm/openclaw-mcp-portal-plugin",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "OpenClaw plugin that
|
|
3
|
+
"version": "0.0.70",
|
|
4
|
+
"description": "OpenClaw plugin that registers native MCP Portal tools over configured upstream MCP servers.",
|
|
5
5
|
"homepage": "https://github.com/ShravanSunder/agent-vm#readme",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/ShravanSunder/agent-vm/issues"
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"zod": "^4.4.3",
|
|
33
|
-
"@agent-vm/
|
|
34
|
-
"@agent-vm/
|
|
33
|
+
"@agent-vm/mcp-portal": "0.0.70",
|
|
34
|
+
"@agent-vm/config-contracts": "0.0.70"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"openclaw": "^2026.5.7",
|