@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 CHANGED
@@ -1,26 +1,24 @@
1
1
  # @agent-vm/openclaw-mcp-portal-plugin
2
2
 
3
- OpenClaw plugin that supervises the MCP Portal subprocess and wires portal calls
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
- - Starts and stops the `agent-vm-mcp-portal-server` subprocess through
9
- OpenClaw `registerService`.
10
- - Generates per-agent HMAC keys for each plugin boot and passes them to the
11
- portal subprocess as environment variables.
12
- - Registers `before_tool_call` to deny disallowed portal calls and attach
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 runtime process settings:
17
+ The OpenClaw plugin config should only carry the effective config directory:
19
18
 
20
19
  ```json
21
20
  {
22
- "configDir": "/home/openclaw/.openclaw/config",
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 OpenClaw service and hook registration.
33
- - `src/portal-subprocess-supervisor.ts` for process lifecycle.
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
- binPath: z.ZodDefault<z.ZodString>;
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, CreateHmacKeyRegistryProps, CreatePortalSubprocessSupervisorProps, HmacKeyRegistry, OPENCLAW_MCP_PORTAL_PLUGIN_PACKAGE_NAME, OpenClawAgentTurnPrepareEvent, OpenClawApprovalResolution, OpenClawBeforePromptBuildEvent, OpenClawBeforeToolCallEvent, OpenClawBeforeToolCallResult, OpenClawHttpRouteRegistration, OpenClawPluginHookContext, OpenClawPluginHookEventMap, OpenClawPluginHookOptions, OpenClawPluginHookResultMap, OpenClawPluginHostCleanupReason, OpenClawPluginService, OpenClawPortalPluginApi, OpenClawPromptHookContext, OpenClawPromptHookResult, OpenClawRuntimeLifecycleRegistrar, OpenClawRuntimeLifecycleRegistration, PortalCallRequest, PortalPluginConfig, PortalPluginRuntimeState, PortalPromptDiagnostic, PortalPromptNamespaceSummary, PortalSubprocessLogger, PortalSubprocessSpawnFunction, PortalSubprocessSupervisor, createBeforePromptBuildHandler, createBeforeToolCallHandler, createHmacKeyRegistry, createPortalPluginRuntimeState, createPortalPromptContext, createPortalSubprocessSupervisor, pluginEntry as default, defaultPortalBinPath, materializedPortalToolNames, parsePortalConfig, portalPluginConfigSchema, portalServerNameForAgent, profileAllowsPortalCall, profileRequiresPortalApproval, redactPortalSecrets, registerMcpPortalPlugin, validatePortalPluginApi, validatePortalPortAgainstTcpPool };
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/openclaw-plugin-api.ts","../src/plugin-registration.ts","../src/hmac-key-registry.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-subprocess-supervisor.ts","../src/portal-tool-policy.ts","../src/portal-prompt-context.ts","../src/redaction.ts","../src/index.ts"],"mappings":"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"}
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 { hashCallArguments, portalHmacKeyEnvName, redactCredentialText, signApprovalToken } from "@agent-vm/mcp-portal";
3
- import { randomBytes } from "node:crypto";
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 { spawn } from "node:child_process";
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
- const approvalTokenTtlMs = 6e4;
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$1(value)) return null;
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$1(argumentsValue)) return null;
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
- const portalConfig = await props.runtimeState.loadPortalConfig();
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
- if (context.agentId !== agentId) return {
108
- block: true,
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/hmac-key-registry.ts
165
- const hmacKeyBytes = 32;
166
- function createHmacKeyRegistry(props) {
167
- const keysByAgent = /* @__PURE__ */ new Map();
168
- for (const agentId of props.agentIds) keysByAgent.set(agentId, randomBytes(hmacKeyBytes));
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
- agentIds: [...props.agentIds],
171
- getKey: (agentId) => {
172
- const key = keysByAgent.get(agentId);
173
- if (key === void 0) throw new Error(`HMAC key registry: unknown agent "${agentId}".`);
174
- return key;
175
- },
176
- serializeForEnv: () => Object.fromEntries([...keysByAgent.entries()].map(([agentId, key]) => [portalHmacKeyEnvName(agentId), key.toString("hex")]))
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 defaultPortalBinPath = "/opt/agent-vm/portal/bin/agent-vm-mcp-portal-server";
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 keyRegistry = null;
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).catch((error) => {
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 nextPromise;
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
- const pluginConfig = parsePortalConfig(api.pluginConfig ?? {});
468
- if (pluginConfig.configDir !== void 0) return pluginConfig.configDir;
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), "mcp"), "configDir");
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 mcp config.");
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.registerService)) throw new Error("MCP Portal plugin requires OpenClaw registerService API.");
502
- if (!hasFunction(api.on) && !hasFunction(api.registerPromptHook)) throw new Error("MCP Portal plugin requires OpenClaw prompt hook registration API.");
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: "Stops the MCP Portal subprocess supervised by the agent-vm plugin.",
512
- id: "mcp-portal-subprocess"
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 registerPortalService(props) {
525
- const portalConfig = parsePortalConfig(props.api.pluginConfig ?? {});
526
- let supervisor = null;
527
- props.api.registerService?.({
528
- id: "mcp-portal-subprocess",
529
- start: async () => {
530
- const mcpPortalConfig = await props.runtimeState.loadPortalConfig();
531
- validatePortalPortAgainstTcpPool({
532
- port: mcpPortalConfig.server.port,
533
- tcpPool: tcpPoolConfigFromApi(props.api)
534
- });
535
- const keyRegistry = createHmacKeyRegistry({ agentIds: Object.keys(mcpPortalConfig.agents).toSorted() });
536
- props.runtimeState.setKeyRegistry(keyRegistry);
537
- supervisor = createPortalSubprocessSupervisor({
538
- binPath: portalConfig.binPath,
539
- configDir: props.configDir,
540
- host: mcpPortalConfig.server.host,
541
- hmacEnv: keyRegistry.serializeForEnv(),
542
- logger: createLoggerAdapter(props.api),
543
- onFatal: (reason) => {
544
- props.runtimeState.markPortalUnavailable(reason);
545
- props.api.logger?.error?.(`[mcp-portal] subprocess supervisor fatal: ${reason}`);
546
- },
547
- port: mcpPortalConfig.server.port
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 supervisor.start();
550
- props.runtimeState.markPortalAvailable();
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
- stop: async () => {
553
- await supervisor?.stop();
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
- const registeredService = registerPortalService({
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
- configDir,
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, () => registeredService.getSupervisor()?.stop());
578
- runtimeState.loadPortalConfig().catch((error) => {
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: "Supervises the MCP Portal subprocess and wires per-agent approval hooks.",
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 an MCP server.",
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, createHmacKeyRegistry, createPortalPluginRuntimeState, createPortalPromptContext, createPortalSubprocessSupervisor, pluginEntry as default, defaultPortalBinPath, materializedPortalToolNames, parsePortalConfig, portalPluginConfigSchema, portalServerNameForAgent, profileAllowsPortalCall, profileRequiresPortalApproval, redactPortalSecrets, registerMcpPortalPlugin, validatePortalPluginApi, validatePortalPortAgainstTcpPool };
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": "Managed per-agent MCP server facade over configured upstream MCP servers.",
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.68",
4
- "description": "OpenClaw plugin that supervises per-agent MCP Portal endpoints over configured upstream MCP servers.",
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/config-contracts": "0.0.68",
34
- "@agent-vm/mcp-portal": "0.0.68"
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",