@agent-vm/mcp-portal 0.0.59

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Shravan Sunder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @agent-vm/mcp-portal
2
+
3
+ Agent-scoped MCP Portal server and Tool VM helpers.
4
+
5
+ ## What This Package Owns
6
+
7
+ - The standalone `agent-vm-mcp-portal-server` HTTP MCP server.
8
+ - The four model-facing portal tools: `mcp_portal_list`, `mcp_portal_search`, `mcp_portal_describe`, and `mcp_portal_call`.
9
+ - JSON-Schema-derived Zod validation before upstream tool calls.
10
+ - HMAC approval-token verification for portal calls that OpenClaw approved.
11
+ - Tool VM helper exports for agents that need to reconstruct derived schemas.
12
+
13
+ ## Runtime Shape
14
+
15
+ The server is launched in the OpenClaw gateway VM and listens on a loopback port,
16
+ normally `127.0.0.1:18790`.
17
+
18
+ Each agent receives a distinct MCP URL:
19
+
20
+ ```text
21
+ http://127.0.0.1:18790/agents/<agentId>/mcp
22
+ ```
23
+
24
+ The portal loads two files from `--config-dir`:
25
+
26
+ - `mcp.config.jsonc`: upstream MCP provider catalog and credentials.
27
+ - `mcp-portal.config.jsonc`: portal access header, agents, profiles, and policy.
28
+
29
+ ## Start Reading
30
+
31
+ - `src/bin/portal-server.ts` for CLI boot and config loading.
32
+ - `src/mcp-server/portal-http-server.ts` for Hono routing and MCP transport.
33
+ - `src/mcp-server/portal-tools.ts` for portal tool behavior.
34
+ - `src/auth/hmac-token.ts` for approval-token signing and verification.
@@ -0,0 +1,10 @@
1
+ import { n as PortalToolRecord } from "../catalog-types--gUGFPpN.js";
2
+
3
+ //#region src/bin/agent-vm-mcp-portal.d.ts
4
+ interface PortalCatalogFile {
5
+ readonly tools: readonly PortalToolRecord[];
6
+ }
7
+ declare function runAgentVmMcpPortal(args: readonly string[]): Promise<number>;
8
+ //#endregion
9
+ export { PortalCatalogFile, runAgentVmMcpPortal };
10
+ //# sourceMappingURL=agent-vm-mcp-portal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-vm-mcp-portal.d.ts","names":[],"sources":["../../src/bin/agent-vm-mcp-portal.ts"],"mappings":";;;UAgBiB,iBAAA;EAAA,SACP,KAAA,WAAgB,gBAAA;AAAA;AAAA,iBAyBJ,mBAAA,CAAoB,IAAA,sBAA0B,OAAA"}
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import { a as portalToolRecordSchema, t as generateTypescriptCatalogArtifact } from "../typescript-artifact-BqU8okQy.js";
3
+ import { z } from "zod";
4
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ //#region src/bin/agent-vm-mcp-portal.ts
7
+ const catalogFileSchema = z.object({ tools: z.array(portalToolRecordSchema) }).strict();
8
+ async function readCatalogFile(catalogPath) {
9
+ const rawCatalog = await readFile(catalogPath, "utf-8");
10
+ const parsedJson = JSON.parse(rawCatalog);
11
+ return catalogFileSchema.parse(parsedJson);
12
+ }
13
+ function parseOutputDirectory(args) {
14
+ const outputFlagIndex = args.indexOf("--out");
15
+ if (outputFlagIndex === -1) return null;
16
+ return args[outputFlagIndex + 1] ?? null;
17
+ }
18
+ function printUsage() {
19
+ process.stderr.write("Usage: agent-vm-mcp-portal validate <catalog.json>\n");
20
+ process.stderr.write("Usage: agent-vm-mcp-portal generate-helper <catalog.json> --out <directory>\n");
21
+ }
22
+ async function runAgentVmMcpPortal(args) {
23
+ const [command, catalogPath, ...restArgs] = args;
24
+ if (!command || !catalogPath) {
25
+ printUsage();
26
+ return 1;
27
+ }
28
+ try {
29
+ const catalog = await readCatalogFile(catalogPath);
30
+ switch (command) {
31
+ case "validate": return 0;
32
+ case "generate-helper": {
33
+ const outputDirectory = parseOutputDirectory(restArgs);
34
+ if (!outputDirectory) {
35
+ printUsage();
36
+ return 1;
37
+ }
38
+ await mkdir(outputDirectory, { recursive: true });
39
+ await writeFile(join(outputDirectory, "catalog.json"), JSON.stringify(catalog, null, " "));
40
+ await writeFile(join(outputDirectory, "catalog.ts"), generateTypescriptCatalogArtifact(catalog));
41
+ return 0;
42
+ }
43
+ default:
44
+ printUsage();
45
+ return 1;
46
+ }
47
+ } catch (error) {
48
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
49
+ return 1;
50
+ }
51
+ }
52
+ if (process.argv[1]?.endsWith("agent-vm-mcp-portal.js")) process.exitCode = await runAgentVmMcpPortal(process.argv.slice(2));
53
+ //#endregion
54
+ export { runAgentVmMcpPortal };
55
+
56
+ //# sourceMappingURL=agent-vm-mcp-portal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-vm-mcp-portal.js","names":[],"sources":["../../src/bin/agent-vm-mcp-portal.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { z } from 'zod';\n\nimport { portalToolRecordSchema, type PortalToolRecord } from '../catalog-types.js';\nimport { generateTypescriptCatalogArtifact } from '../tool-vm/typescript-artifact.js';\n\nconst catalogFileSchema = z\n\t.object({\n\t\ttools: z.array(portalToolRecordSchema),\n\t})\n\t.strict();\n\nexport interface PortalCatalogFile {\n\treadonly tools: readonly PortalToolRecord[];\n}\n\nasync function readCatalogFile(catalogPath: string): Promise<PortalCatalogFile> {\n\tconst rawCatalog = await readFile(catalogPath, 'utf-8');\n\tconst parsedJson = JSON.parse(rawCatalog) as unknown;\n\treturn catalogFileSchema.parse(parsedJson);\n}\n\nfunction parseOutputDirectory(args: readonly string[]): string | null {\n\tconst outputFlagIndex = args.indexOf('--out');\n\tif (outputFlagIndex === -1) {\n\t\treturn null;\n\t}\n\n\treturn args[outputFlagIndex + 1] ?? null;\n}\n\nfunction printUsage(): void {\n\tprocess.stderr.write('Usage: agent-vm-mcp-portal validate <catalog.json>\\n');\n\tprocess.stderr.write(\n\t\t'Usage: agent-vm-mcp-portal generate-helper <catalog.json> --out <directory>\\n',\n\t);\n}\n\nexport async function runAgentVmMcpPortal(args: readonly string[]): Promise<number> {\n\tconst [command, catalogPath, ...restArgs] = args;\n\tif (!command || !catalogPath) {\n\t\tprintUsage();\n\t\treturn 1;\n\t}\n\n\ttry {\n\t\tconst catalog = await readCatalogFile(catalogPath);\n\t\tswitch (command) {\n\t\t\tcase 'validate':\n\t\t\t\treturn 0;\n\t\t\tcase 'generate-helper': {\n\t\t\t\tconst outputDirectory = parseOutputDirectory(restArgs);\n\t\t\t\tif (!outputDirectory) {\n\t\t\t\t\tprintUsage();\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\n\t\t\t\tawait mkdir(outputDirectory, { recursive: true });\n\t\t\t\tawait writeFile(join(outputDirectory, 'catalog.json'), JSON.stringify(catalog, null, '\\t'));\n\t\t\t\tawait writeFile(\n\t\t\t\t\tjoin(outputDirectory, 'catalog.ts'),\n\t\t\t\t\tgenerateTypescriptCatalogArtifact(catalog),\n\t\t\t\t);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tprintUsage();\n\t\t\t\treturn 1;\n\t\t}\n\t} catch (error) {\n\t\tprocess.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n\t\treturn 1;\n\t}\n}\n\nif (process.argv[1]?.endsWith('agent-vm-mcp-portal.js')) {\n\tprocess.exitCode = await runAgentVmMcpPortal(process.argv.slice(2));\n}\n"],"mappings":";;;;;;AAUA,MAAM,oBAAoB,EACxB,OAAO,EACP,OAAO,EAAE,MAAM,uBAAuB,EACtC,CAAC,CACD,QAAQ;AAMV,eAAe,gBAAgB,aAAiD;CAC/E,MAAM,aAAa,MAAM,SAAS,aAAa,QAAQ;CACvD,MAAM,aAAa,KAAK,MAAM,WAAW;CACzC,OAAO,kBAAkB,MAAM,WAAW;;AAG3C,SAAS,qBAAqB,MAAwC;CACrE,MAAM,kBAAkB,KAAK,QAAQ,QAAQ;CAC7C,IAAI,oBAAoB,IACvB,OAAO;CAGR,OAAO,KAAK,kBAAkB,MAAM;;AAGrC,SAAS,aAAmB;CAC3B,QAAQ,OAAO,MAAM,uDAAuD;CAC5E,QAAQ,OAAO,MACd,gFACA;;AAGF,eAAsB,oBAAoB,MAA0C;CACnF,MAAM,CAAC,SAAS,aAAa,GAAG,YAAY;CAC5C,IAAI,CAAC,WAAW,CAAC,aAAa;EAC7B,YAAY;EACZ,OAAO;;CAGR,IAAI;EACH,MAAM,UAAU,MAAM,gBAAgB,YAAY;EAClD,QAAQ,SAAR;GACC,KAAK,YACJ,OAAO;GACR,KAAK,mBAAmB;IACvB,MAAM,kBAAkB,qBAAqB,SAAS;IACtD,IAAI,CAAC,iBAAiB;KACrB,YAAY;KACZ,OAAO;;IAGR,MAAM,MAAM,iBAAiB,EAAE,WAAW,MAAM,CAAC;IACjD,MAAM,UAAU,KAAK,iBAAiB,eAAe,EAAE,KAAK,UAAU,SAAS,MAAM,IAAK,CAAC;IAC3F,MAAM,UACL,KAAK,iBAAiB,aAAa,EACnC,kCAAkC,QAAQ,CAC1C;IACD,OAAO;;GAER;IACC,YAAY;IACZ,OAAO;;UAED,OAAO;EACf,QAAQ,OAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC,IAAI;EACnF,OAAO;;;AAIT,IAAI,QAAQ,KAAK,IAAI,SAAS,yBAAyB,EACtD,QAAQ,WAAW,MAAM,oBAAoB,QAAQ,KAAK,MAAM,EAAE,CAAC"}
@@ -0,0 +1,54 @@
1
+ import { McpPortalAgentConfig, SecretValue } from "@agent-vm/config-contracts";
2
+ import { serve } from "@hono/node-server";
3
+
4
+ //#region src/bin/portal-server.d.ts
5
+ type PortalServeFunction = typeof serve;
6
+ type PortalServerLogEvent = {
7
+ readonly agentId: string;
8
+ readonly conservativeCallCount: number;
9
+ readonly event: 'conservative_approval_fallback';
10
+ readonly level: 'warn';
11
+ readonly primaryReason: string;
12
+ readonly strictCallCount: number;
13
+ readonly toolRefs: readonly string[];
14
+ } | {
15
+ readonly event: 'server_error';
16
+ readonly level: 'error';
17
+ readonly message: string;
18
+ readonly stack?: string;
19
+ };
20
+ interface PortalServerLogger {
21
+ readonly log: (event: PortalServerLogEvent) => void;
22
+ }
23
+ interface PortalServerCliArgs {
24
+ readonly agentOverrides: readonly string[];
25
+ readonly configDir: string;
26
+ readonly port?: number;
27
+ }
28
+ interface StartPortalServerProps {
29
+ readonly args: PortalServerCliArgs;
30
+ readonly env: Readonly<Record<string, string | undefined>>;
31
+ readonly logger?: PortalServerLogger;
32
+ readonly resolveSecret?: (secret: SecretValue) => Promise<string>;
33
+ readonly serveFn?: PortalServeFunction;
34
+ }
35
+ declare function parsePortalServerCliArgs(argv: readonly string[]): PortalServerCliArgs;
36
+ declare function applyAgentOverrides(agents: Readonly<Record<string, McpPortalAgentConfig>>, overrides: readonly string[]): Readonly<Record<string, McpPortalAgentConfig>>;
37
+ interface DeferredPort {
38
+ readonly promise: Promise<number>;
39
+ readonly reject: (error: Error) => void;
40
+ readonly resolve: (port: number) => void;
41
+ }
42
+ declare function handlePortalServerError(props: {
43
+ readonly error: Error;
44
+ readonly hasListened: boolean;
45
+ readonly listeningPort: DeferredPort;
46
+ readonly logger: PortalServerLogger;
47
+ }): void;
48
+ declare function startPortalServer(props: StartPortalServerProps): Promise<{
49
+ readonly close: () => Promise<void>;
50
+ readonly port: number;
51
+ }>;
52
+ //#endregion
53
+ export { DeferredPort, PortalServerCliArgs, PortalServerLogEvent, PortalServerLogger, StartPortalServerProps, applyAgentOverrides, handlePortalServerError, parsePortalServerCliArgs, startPortalServer };
54
+ //# sourceMappingURL=portal-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal-server.d.ts","names":[],"sources":["../../src/bin/portal-server.ts"],"mappings":";;;;KAmCK,mBAAA,UAA6B,KAAA;AAAA,KAEtB,oBAAA;EAAA,SAEA,OAAA;EAAA,SACA,qBAAA;EAAA,SACA,KAAA;EAAA,SACA,KAAA;EAAA,SACA,aAAA;EAAA,SACA,eAAA;EAAA,SACA,QAAA;AAAA;EAAA,SAGA,KAAA;EAAA,SACA,KAAA;EAAA,SACA,OAAA;EAAA,SACA,KAAA;AAAA;AAAA,UAGK,kBAAA;EAAA,SACP,GAAA,GAAM,KAAA,EAAO,oBAAA;AAAA;AAAA,UAGN,mBAAA;EAAA,SACP,cAAA;EAAA,SACA,SAAA;EAAA,SACA,IAAA;AAAA;AAAA,UAGO,sBAAA;EAAA,SACP,IAAA,EAAM,mBAAA;EAAA,SACN,GAAA,EAAK,QAAA,CAAS,MAAA;EAAA,SACd,MAAA,GAAS,kBAAA;EAAA,SACT,aAAA,IAAiB,MAAA,EAAQ,WAAA,KAAgB,OAAA;EAAA,SACzC,OAAA,GAAU,mBAAA;AAAA;AAAA,iBAoBJ,wBAAA,CAAyB,IAAA,sBAA0B,mBAAA;AAAA,iBAuBnD,mBAAA,CACf,MAAA,EAAQ,QAAA,CAAS,MAAA,SAAe,oBAAA,IAChC,SAAA,sBACE,QAAA,CAAS,MAAA,SAAe,oBAAA;AAAA,UAsBV,YAAA;EAAA,SACP,OAAA,EAAS,OAAA;EAAA,SACT,MAAA,GAAS,KAAA,EAAO,KAAA;EAAA,SAChB,OAAA,GAAU,IAAA;AAAA;AAAA,iBAmCJ,uBAAA,CAAwB,KAAA;EAAA,SAC9B,KAAA,EAAO,KAAA;EAAA,SACP,WAAA;EAAA,SACA,aAAA,EAAe,YAAA;EAAA,SACf,MAAA,EAAQ,kBAAA;AAAA;AAAA,iBAiHI,iBAAA,CACrB,KAAA,EAAO,sBAAA,GACL,OAAA;EAAA,SAAmB,KAAA,QAAa,OAAA;EAAA,SAAwB,IAAA;AAAA"}
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+ import { f as createPortalAgentRuntimeRecords, g as createPortalHttpApp, h as resolveAgentHmacKeys, j as parseHmacKeysFromEnv, l as createPortalSessionManager, m as createPortalHttpAgentResolver, p as createPortalApprovalVerifier, t as createUpstreamMcpClientRuntime } from "../upstream-mcp-client-runtime-DiBCBsDj.js";
3
+ import { loadMcpConfig, loadMcpPortalConfig, mcpConfigToResolvedProviders, resolveMcpPortalProfile } from "@agent-vm/config-contracts";
4
+ import { join } from "node:path";
5
+ import { parseArgs, promisify } from "node:util";
6
+ import { serve } from "@hono/node-server";
7
+ import { execFile } from "node:child_process";
8
+ //#region src/bin/secret-value-resolver.ts
9
+ const execFileAsync = promisify(execFile);
10
+ async function resolveSecretValue(secret, props) {
11
+ if (secret.source === "environment") {
12
+ const value = props.env[secret.name];
13
+ if (value === void 0 || value.length === 0) throw new Error(`Missing environment secret ${secret.name}.`);
14
+ return value;
15
+ }
16
+ return await (props.readOnePasswordSecret ?? readOnePasswordCliSecret)(secret.ref);
17
+ }
18
+ async function readOnePasswordCliSecret(ref) {
19
+ const { stdout } = await execFileAsync("op", ["read", ref], { encoding: "utf8" });
20
+ return stdout.trimEnd();
21
+ }
22
+ //#endregion
23
+ //#region src/bin/portal-server.ts
24
+ function parsePort(value) {
25
+ if (value === void 0) return;
26
+ const port = Number(value);
27
+ if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error(`Invalid --port value "${value}".`);
28
+ return port;
29
+ }
30
+ function parsePortalServerCliArgs(argv) {
31
+ const parsed = parseArgs({
32
+ args: [...argv],
33
+ options: {
34
+ agent: {
35
+ multiple: true,
36
+ type: "string"
37
+ },
38
+ "config-dir": { type: "string" },
39
+ port: {
40
+ short: "p",
41
+ type: "string"
42
+ }
43
+ },
44
+ strict: true
45
+ });
46
+ const configDir = parsed.values["config-dir"];
47
+ if (typeof configDir !== "string" || configDir.length === 0) throw new Error("--config-dir <path> is required.");
48
+ const rawAgentOverrides = parsed.values.agent;
49
+ const args = {
50
+ agentOverrides: Array.isArray(rawAgentOverrides) ? rawAgentOverrides : [],
51
+ configDir
52
+ };
53
+ const port = parsePort(parsed.values.port);
54
+ return port === void 0 ? args : {
55
+ ...args,
56
+ port
57
+ };
58
+ }
59
+ function applyAgentOverrides(agents, overrides) {
60
+ const nextAgents = { ...agents };
61
+ for (const override of overrides) {
62
+ const [agentId, profileName, extra] = override.split("=");
63
+ if (agentId === void 0 || profileName === void 0 || extra !== void 0 || agentId.length === 0 || profileName.length === 0) throw new Error(`Invalid --agent override "${override}". Expected <agentId>=<profile>.`);
64
+ const existingAgent = nextAgents[agentId];
65
+ if (existingAgent === void 0) throw new Error(`Cannot override unknown MCP Portal agent "${agentId}".`);
66
+ nextAgents[agentId] = {
67
+ ...existingAgent,
68
+ profile: profileName
69
+ };
70
+ }
71
+ return nextAgents;
72
+ }
73
+ function createDeferredPort() {
74
+ let rejectPort;
75
+ let resolvePort;
76
+ return {
77
+ promise: new Promise((resolve, reject) => {
78
+ rejectPort = reject;
79
+ resolvePort = resolve;
80
+ }),
81
+ reject: (error) => {
82
+ if (rejectPort === void 0) throw new Error("MCP Portal port rejector was not initialized.");
83
+ rejectPort(error);
84
+ },
85
+ resolve: (port) => {
86
+ if (resolvePort === void 0) throw new Error("MCP Portal port resolver was not initialized.");
87
+ resolvePort(port);
88
+ }
89
+ };
90
+ }
91
+ function defaultPortalServerLogger() {
92
+ return { log: (event) => {
93
+ process.stderr.write(`${JSON.stringify(event)}\n`);
94
+ } };
95
+ }
96
+ function handlePortalServerError(props) {
97
+ props.logger.log({
98
+ event: "server_error",
99
+ level: "error",
100
+ message: props.error.message,
101
+ ...props.error.stack === void 0 ? {} : { stack: props.error.stack }
102
+ });
103
+ if (!props.hasListened) props.listeningPort.reject(props.error);
104
+ }
105
+ function closeNodeServer(server) {
106
+ return new Promise((resolve, reject) => {
107
+ server.close((error) => {
108
+ if (error) reject(error);
109
+ else resolve();
110
+ });
111
+ });
112
+ }
113
+ async function resolveProviderSecretRecord(secrets, resolveSecret) {
114
+ const resolvedEntries = await Promise.all(Object.entries(secrets).map(async ([name, secret]) => [name, await resolveSecret(secret)]));
115
+ return Object.fromEntries(resolvedEntries);
116
+ }
117
+ async function resolveUpstreamServer(provider, resolveSecret) {
118
+ if (provider.transport === "stdio") return {
119
+ args: provider.args,
120
+ command: provider.command,
121
+ ...provider.cwd === void 0 ? {} : { cwd: provider.cwd },
122
+ env: await resolveProviderSecretRecord(provider.env, resolveSecret),
123
+ namespace: provider.namespace,
124
+ transport: "stdio"
125
+ };
126
+ return {
127
+ headers: await resolveProviderSecretRecord(provider.headers, resolveSecret),
128
+ namespace: provider.namespace,
129
+ transport: provider.transport,
130
+ url: provider.url
131
+ };
132
+ }
133
+ async function resolveUpstreamServers(mcpConfig, resolveSecret) {
134
+ return await Promise.all(mcpConfigToResolvedProviders(mcpConfig).map(async (provider) => resolveUpstreamServer(provider, resolveSecret)));
135
+ }
136
+ function selectorsFromNamespaceTools(namespaceTools) {
137
+ return Object.entries(namespaceTools).flatMap(([namespace, toolNames]) => toolNames.map((toolName) => ({
138
+ namespace,
139
+ toolName
140
+ })));
141
+ }
142
+ function buildProfilePolicyMaps(portalConfig) {
143
+ const enabledNamespacesByAgent = {};
144
+ const enabledToolsByAgent = {};
145
+ const hiddenToolsByAgent = {};
146
+ const profileTtls = [];
147
+ for (const [agentId, agent] of Object.entries(portalConfig.agents)) {
148
+ const profile = resolveMcpPortalProfile(portalConfig, agent.profile);
149
+ enabledNamespacesByAgent[agentId] = profile.enabledNamespaces;
150
+ enabledToolsByAgent[agentId] = selectorsFromNamespaceTools(profile.enabledToolsByNamespace);
151
+ hiddenToolsByAgent[agentId] = selectorsFromNamespaceTools(profile.hiddenToolsByNamespace);
152
+ profileTtls.push(profile.cache.catalogTtlMs);
153
+ }
154
+ return {
155
+ cacheTtlMs: profileTtls.length === 0 ? 6e4 : Math.min(...profileTtls),
156
+ enabledNamespacesByAgent,
157
+ enabledToolsByAgent,
158
+ hiddenToolsByAgent
159
+ };
160
+ }
161
+ function withAgentOverrides(portalConfig, agentOverrides) {
162
+ return {
163
+ ...portalConfig,
164
+ agents: applyAgentOverrides(portalConfig.agents, agentOverrides)
165
+ };
166
+ }
167
+ async function startPortalServer(props) {
168
+ const logger = props.logger ?? defaultPortalServerLogger();
169
+ const serveFn = props.serveFn ?? serve;
170
+ const resolveSecret = props.resolveSecret ?? ((secret) => resolveSecretValue(secret, { env: props.env }));
171
+ const mcpConfig = await loadMcpConfig(join(props.args.configDir, "mcp.config.jsonc"));
172
+ const portalConfig = withAgentOverrides(await loadMcpPortalConfig(join(props.args.configDir, "mcp-portal.config.jsonc")), props.args.agentOverrides);
173
+ const serverAccessSecret = await resolveSecret(portalConfig.server.accessHeader.secret);
174
+ const agentRecords = createPortalAgentRuntimeRecords({
175
+ hmacKeys: await resolveAgentHmacKeys({
176
+ agents: portalConfig.agents,
177
+ envKeys: parseHmacKeysFromEnv(props.env),
178
+ resolveSecret
179
+ }),
180
+ portalConfig
181
+ });
182
+ const upstreamServers = await resolveUpstreamServers(mcpConfig, resolveSecret);
183
+ const upstreamRuntime = createUpstreamMcpClientRuntime({ servers: upstreamServers });
184
+ const profilePolicyMaps = buildProfilePolicyMaps(portalConfig);
185
+ const sessionManager = createPortalSessionManager({
186
+ accessPolicy: {
187
+ defaultPolicy: "deny-all",
188
+ enabledNamespacesByAgent: profilePolicyMaps.enabledNamespacesByAgent,
189
+ enabledToolsByAgent: profilePolicyMaps.enabledToolsByAgent,
190
+ hiddenToolsByAgent: profilePolicyMaps.hiddenToolsByAgent
191
+ },
192
+ catalogTtlMs: profilePolicyMaps.cacheTtlMs,
193
+ runtime: upstreamRuntime,
194
+ upstreamNamespaces: upstreamServers.map((server) => server.namespace)
195
+ });
196
+ const verifyApproval = createPortalApprovalVerifier({
197
+ onConservativeApprovalFallback: (event) => {
198
+ logger.log({
199
+ agentId: event.agentId,
200
+ conservativeCallCount: event.conservativeCallCount,
201
+ event: "conservative_approval_fallback",
202
+ level: "warn",
203
+ primaryReason: event.primaryReason,
204
+ strictCallCount: event.strictCallCount,
205
+ toolRefs: event.toolRefs
206
+ });
207
+ },
208
+ records: agentRecords
209
+ });
210
+ const app = createPortalHttpApp({
211
+ onSessionClosed: async (identity) => {
212
+ await sessionManager.invalidateSession(identity);
213
+ },
214
+ registeredAgentIds: Object.keys(portalConfig.agents),
215
+ resolveAgentIdentity: createPortalHttpAgentResolver(agentRecords),
216
+ serverAccess: {
217
+ expectedValue: serverAccessSecret,
218
+ headerName: portalConfig.server.accessHeader.name
219
+ },
220
+ toolRuntime: {
221
+ approval: (calls, identity, approvalToken) => verifyApproval(calls, identity.agentId, approvalToken),
222
+ callUpstreamTool: upstreamRuntime.callTool,
223
+ getSession: sessionManager.getSession
224
+ }
225
+ });
226
+ const listeningPort = createDeferredPort();
227
+ let hasListened = false;
228
+ const server = serveFn({
229
+ fetch: app.fetch,
230
+ hostname: portalConfig.server.host,
231
+ port: props.args.port ?? portalConfig.server.port
232
+ }, (info) => {
233
+ hasListened = true;
234
+ process.stdout.write(`listening port=${String(info.port)}\n`);
235
+ listeningPort.resolve(info.port);
236
+ });
237
+ server.on("error", (error) => {
238
+ handlePortalServerError({
239
+ error,
240
+ hasListened,
241
+ listeningPort,
242
+ logger
243
+ });
244
+ });
245
+ return {
246
+ close: async () => {
247
+ await app.closePortalSessions();
248
+ await Promise.all(Object.keys(portalConfig.agents).map((agentId) => sessionManager.invalidateAgentScope(agentId)));
249
+ await closeNodeServer(server);
250
+ },
251
+ port: await listeningPort.promise
252
+ };
253
+ }
254
+ async function main() {
255
+ const startedServer = await startPortalServer({
256
+ args: parsePortalServerCliArgs(process.argv.slice(2)),
257
+ env: process.env
258
+ });
259
+ const shutdown = async () => {
260
+ await startedServer.close();
261
+ process.exit(0);
262
+ };
263
+ process.on("SIGINT", () => {
264
+ shutdown();
265
+ });
266
+ process.on("SIGTERM", () => {
267
+ shutdown();
268
+ });
269
+ }
270
+ if (import.meta.url === `file://${process.argv[1]}`) main().catch((error) => {
271
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
272
+ process.exit(1);
273
+ });
274
+ //#endregion
275
+ export { applyAgentOverrides, handlePortalServerError, parsePortalServerCliArgs, startPortalServer };
276
+
277
+ //# sourceMappingURL=portal-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal-server.js","names":[],"sources":["../../src/bin/secret-value-resolver.ts","../../src/bin/portal-server.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nimport type { SecretValue } from '@agent-vm/config-contracts';\n\nconst execFileAsync = promisify(execFile);\n\nexport interface ResolveSecretValueProps {\n\treadonly env: Readonly<Record<string, string | undefined>>;\n\treadonly readOnePasswordSecret?: (ref: string) => Promise<string>;\n}\n\nexport async function resolveSecretValue(\n\tsecret: SecretValue,\n\tprops: ResolveSecretValueProps,\n): Promise<string> {\n\tif (secret.source === 'environment') {\n\t\tconst value = props.env[secret.name];\n\t\tif (value === undefined || value.length === 0) {\n\t\t\tthrow new Error(`Missing environment secret ${secret.name}.`);\n\t\t}\n\t\treturn value;\n\t}\n\n\tconst readOnePasswordSecret = props.readOnePasswordSecret ?? readOnePasswordCliSecret;\n\treturn await readOnePasswordSecret(secret.ref);\n}\n\nasync function readOnePasswordCliSecret(ref: string): Promise<string> {\n\tconst { stdout } = await execFileAsync('op', ['read', ref], { encoding: 'utf8' });\n\treturn stdout.trimEnd();\n}\n","#!/usr/bin/env node\nimport { join } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport {\n\tloadMcpConfig,\n\tloadMcpPortalConfig,\n\tmcpConfigToResolvedProviders,\n\tresolveMcpPortalProfile,\n\ttype McpConfig,\n\ttype McpPortalAgentConfig,\n\ttype McpPortalConfig,\n\ttype ResolvedMcpProvider,\n\ttype ResolvedMcpPortalProfile,\n\ttype SecretValue,\n} from '@agent-vm/config-contracts';\nimport { serve } from '@hono/node-server';\n\nimport { parseHmacKeysFromEnv } from '../auth/hmac-env.js';\nimport { createPortalHttpApp } from '../mcp-server/portal-http-server.js';\nimport {\n\tcreatePortalAgentRuntimeRecords,\n\tcreatePortalApprovalVerifier,\n\tcreatePortalHttpAgentResolver,\n\tresolveAgentHmacKeys,\n} from '../mcp-server/resolve-agent-identity.js';\nimport type { PortalToolSelector } from '../portal-access-policy.js';\nimport { createPortalSessionManager } from '../portal-session.js';\nimport {\n\tcreateUpstreamMcpClientRuntime,\n\ttype NormalizedUpstreamMcpServer,\n} from '../upstream-mcp-client-runtime.js';\nimport { resolveSecretValue } from './secret-value-resolver.js';\n\ntype PortalNodeServer = ReturnType<typeof serve>;\ntype PortalServeFunction = typeof serve;\n\nexport type PortalServerLogEvent =\n\t| {\n\t\t\treadonly agentId: string;\n\t\t\treadonly conservativeCallCount: number;\n\t\t\treadonly event: 'conservative_approval_fallback';\n\t\t\treadonly level: 'warn';\n\t\t\treadonly primaryReason: string;\n\t\t\treadonly strictCallCount: number;\n\t\t\treadonly toolRefs: readonly string[];\n\t }\n\t| {\n\t\t\treadonly event: 'server_error';\n\t\t\treadonly level: 'error';\n\t\t\treadonly message: string;\n\t\t\treadonly stack?: string;\n\t };\n\nexport interface PortalServerLogger {\n\treadonly log: (event: PortalServerLogEvent) => void;\n}\n\nexport interface PortalServerCliArgs {\n\treadonly agentOverrides: readonly string[];\n\treadonly configDir: string;\n\treadonly port?: number;\n}\n\nexport interface StartPortalServerProps {\n\treadonly args: PortalServerCliArgs;\n\treadonly env: Readonly<Record<string, string | undefined>>;\n\treadonly logger?: PortalServerLogger;\n\treadonly resolveSecret?: (secret: SecretValue) => Promise<string>;\n\treadonly serveFn?: PortalServeFunction;\n}\n\ninterface ProfilePolicyMaps {\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\nfunction parsePort(value: string | undefined): number | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\tconst port = Number(value);\n\tif (!Number.isInteger(port) || port < 0 || port > 65_535) {\n\t\tthrow new Error(`Invalid --port value \"${value}\".`);\n\t}\n\treturn port;\n}\n\nexport function parsePortalServerCliArgs(argv: readonly string[]): PortalServerCliArgs {\n\tconst parsed = parseArgs({\n\t\targs: [...argv],\n\t\toptions: {\n\t\t\tagent: { multiple: true, type: 'string' },\n\t\t\t'config-dir': { type: 'string' },\n\t\t\tport: { short: 'p', type: 'string' },\n\t\t},\n\t\tstrict: true,\n\t});\n\tconst configDir = parsed.values['config-dir'];\n\tif (typeof configDir !== 'string' || configDir.length === 0) {\n\t\tthrow new Error('--config-dir <path> is required.');\n\t}\n\tconst rawAgentOverrides = parsed.values.agent;\n\tconst args = {\n\t\tagentOverrides: Array.isArray(rawAgentOverrides) ? rawAgentOverrides : [],\n\t\tconfigDir,\n\t};\n\tconst port = parsePort(parsed.values.port);\n\treturn port === undefined ? args : { ...args, port };\n}\n\nexport function applyAgentOverrides(\n\tagents: Readonly<Record<string, McpPortalAgentConfig>>,\n\toverrides: readonly string[],\n): Readonly<Record<string, McpPortalAgentConfig>> {\n\tconst nextAgents: Record<string, McpPortalAgentConfig> = { ...agents };\n\tfor (const override of overrides) {\n\t\tconst [agentId, profileName, extra] = override.split('=');\n\t\tif (\n\t\t\tagentId === undefined ||\n\t\t\tprofileName === undefined ||\n\t\t\textra !== undefined ||\n\t\t\tagentId.length === 0 ||\n\t\t\tprofileName.length === 0\n\t\t) {\n\t\t\tthrow new Error(`Invalid --agent override \"${override}\". Expected <agentId>=<profile>.`);\n\t\t}\n\t\tconst existingAgent = nextAgents[agentId];\n\t\tif (existingAgent === undefined) {\n\t\t\tthrow new Error(`Cannot override unknown MCP Portal agent \"${agentId}\".`);\n\t\t}\n\t\tnextAgents[agentId] = { ...existingAgent, profile: profileName };\n\t}\n\treturn nextAgents;\n}\n\nexport interface DeferredPort {\n\treadonly promise: Promise<number>;\n\treadonly reject: (error: Error) => void;\n\treadonly resolve: (port: number) => void;\n}\n\nfunction createDeferredPort(): DeferredPort {\n\tlet rejectPort: ((error: Error) => void) | undefined;\n\tlet resolvePort: ((port: number) => void) | undefined;\n\tconst promise = new Promise<number>((resolve, reject) => {\n\t\trejectPort = reject;\n\t\tresolvePort = resolve;\n\t});\n\treturn {\n\t\tpromise,\n\t\treject: (error) => {\n\t\t\tif (rejectPort === undefined) {\n\t\t\t\tthrow new Error('MCP Portal port rejector was not initialized.');\n\t\t\t}\n\t\t\trejectPort(error);\n\t\t},\n\t\tresolve: (port) => {\n\t\t\tif (resolvePort === undefined) {\n\t\t\t\tthrow new Error('MCP Portal port resolver was not initialized.');\n\t\t\t}\n\t\t\tresolvePort(port);\n\t\t},\n\t};\n}\n\nfunction defaultPortalServerLogger(): PortalServerLogger {\n\treturn {\n\t\tlog: (event) => {\n\t\t\tprocess.stderr.write(`${JSON.stringify(event)}\\n`);\n\t\t},\n\t};\n}\n\nexport function handlePortalServerError(props: {\n\treadonly error: Error;\n\treadonly hasListened: boolean;\n\treadonly listeningPort: DeferredPort;\n\treadonly logger: PortalServerLogger;\n}): void {\n\tprops.logger.log({\n\t\tevent: 'server_error',\n\t\tlevel: 'error',\n\t\tmessage: props.error.message,\n\t\t...(props.error.stack === undefined ? {} : { stack: props.error.stack }),\n\t});\n\tif (!props.hasListened) {\n\t\tprops.listeningPort.reject(props.error);\n\t}\n}\n\nfunction closeNodeServer(server: PortalNodeServer): Promise<void> {\n\treturn new Promise<void>((resolve, reject) => {\n\t\tserver.close((error) => {\n\t\t\tif (error) {\n\t\t\t\treject(error);\n\t\t\t} else {\n\t\t\t\tresolve();\n\t\t\t}\n\t\t});\n\t});\n}\n\nasync function resolveProviderSecretRecord(\n\tsecrets: Readonly<Record<string, SecretValue>>,\n\tresolveSecret: (secret: SecretValue) => Promise<string>,\n): Promise<Readonly<Record<string, string>>> {\n\tconst resolvedEntries = await Promise.all(\n\t\tObject.entries(secrets).map(\n\t\t\tasync ([name, secret]) => [name, await resolveSecret(secret)] as const,\n\t\t),\n\t);\n\treturn Object.fromEntries(resolvedEntries);\n}\n\nasync function resolveUpstreamServer(\n\tprovider: ResolvedMcpProvider,\n\tresolveSecret: (secret: SecretValue) => Promise<string>,\n): Promise<NormalizedUpstreamMcpServer> {\n\tif (provider.transport === 'stdio') {\n\t\treturn {\n\t\t\targs: provider.args,\n\t\t\tcommand: provider.command,\n\t\t\t...(provider.cwd === undefined ? {} : { cwd: provider.cwd }),\n\t\t\tenv: await resolveProviderSecretRecord(provider.env, resolveSecret),\n\t\t\tnamespace: provider.namespace,\n\t\t\ttransport: 'stdio',\n\t\t};\n\t}\n\n\treturn {\n\t\theaders: await resolveProviderSecretRecord(provider.headers, resolveSecret),\n\t\tnamespace: provider.namespace,\n\t\ttransport: provider.transport,\n\t\turl: provider.url,\n\t};\n}\n\nasync function resolveUpstreamServers(\n\tmcpConfig: McpConfig,\n\tresolveSecret: (secret: SecretValue) => Promise<string>,\n): Promise<readonly NormalizedUpstreamMcpServer[]> {\n\treturn await Promise.all(\n\t\tmcpConfigToResolvedProviders(mcpConfig).map(async (provider) =>\n\t\t\tresolveUpstreamServer(provider, resolveSecret),\n\t\t),\n\t);\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(\n\tportalConfig: McpPortalConfig,\n): ProfilePolicyMaps & { readonly cacheTtlMs: number } {\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\nfunction withAgentOverrides(\n\tportalConfig: McpPortalConfig,\n\tagentOverrides: readonly string[],\n): McpPortalConfig {\n\treturn {\n\t\t...portalConfig,\n\t\tagents: applyAgentOverrides(portalConfig.agents, agentOverrides),\n\t};\n}\n\nexport async function startPortalServer(\n\tprops: StartPortalServerProps,\n): Promise<{ readonly close: () => Promise<void>; readonly port: number }> {\n\tconst logger = props.logger ?? defaultPortalServerLogger();\n\tconst serveFn = props.serveFn ?? serve;\n\tconst resolveSecret =\n\t\tprops.resolveSecret ??\n\t\t((secret: SecretValue) => resolveSecretValue(secret, { env: props.env }));\n\tconst mcpConfig = await loadMcpConfig(join(props.args.configDir, 'mcp.config.jsonc'));\n\tconst portalConfig = withAgentOverrides(\n\t\tawait loadMcpPortalConfig(join(props.args.configDir, 'mcp-portal.config.jsonc')),\n\t\tprops.args.agentOverrides,\n\t);\n\tconst serverAccessSecret = await resolveSecret(portalConfig.server.accessHeader.secret);\n\tconst hmacKeys = await resolveAgentHmacKeys({\n\t\tagents: portalConfig.agents,\n\t\tenvKeys: parseHmacKeysFromEnv(props.env),\n\t\tresolveSecret,\n\t});\n\tconst agentRecords = createPortalAgentRuntimeRecords({ hmacKeys, portalConfig });\n\tconst upstreamServers = await resolveUpstreamServers(mcpConfig, resolveSecret);\n\tconst upstreamRuntime = createUpstreamMcpClientRuntime({ servers: upstreamServers });\n\tconst profilePolicyMaps = buildProfilePolicyMaps(portalConfig);\n\tconst sessionManager = createPortalSessionManager({\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\tcatalogTtlMs: profilePolicyMaps.cacheTtlMs,\n\t\truntime: upstreamRuntime,\n\t\tupstreamNamespaces: upstreamServers.map((server) => server.namespace),\n\t});\n\tconst verifyApproval = createPortalApprovalVerifier({\n\t\tonConservativeApprovalFallback: (event) => {\n\t\t\tlogger.log({\n\t\t\t\tagentId: event.agentId,\n\t\t\t\tconservativeCallCount: event.conservativeCallCount,\n\t\t\t\tevent: 'conservative_approval_fallback',\n\t\t\t\tlevel: 'warn',\n\t\t\t\tprimaryReason: event.primaryReason,\n\t\t\t\tstrictCallCount: event.strictCallCount,\n\t\t\t\ttoolRefs: event.toolRefs,\n\t\t\t});\n\t\t},\n\t\trecords: agentRecords,\n\t});\n\tconst app = createPortalHttpApp({\n\t\tonSessionClosed: async (identity) => {\n\t\t\tawait sessionManager.invalidateSession(identity);\n\t\t},\n\t\tregisteredAgentIds: Object.keys(portalConfig.agents),\n\t\tresolveAgentIdentity: createPortalHttpAgentResolver(agentRecords),\n\t\tserverAccess: {\n\t\t\texpectedValue: serverAccessSecret,\n\t\t\theaderName: portalConfig.server.accessHeader.name,\n\t\t},\n\t\ttoolRuntime: {\n\t\t\tapproval: (calls, identity, approvalToken) =>\n\t\t\t\tverifyApproval(calls, identity.agentId, approvalToken),\n\t\t\tcallUpstreamTool: upstreamRuntime.callTool,\n\t\t\tgetSession: sessionManager.getSession,\n\t\t},\n\t});\n\tconst listeningPort = createDeferredPort();\n\tlet hasListened = false;\n\tconst server = serveFn(\n\t\t{\n\t\t\tfetch: app.fetch,\n\t\t\thostname: portalConfig.server.host,\n\t\t\tport: props.args.port ?? portalConfig.server.port,\n\t\t},\n\t\t(info) => {\n\t\t\thasListened = true;\n\t\t\tprocess.stdout.write(`listening port=${String(info.port)}\\n`);\n\t\t\tlisteningPort.resolve(info.port);\n\t\t},\n\t);\n\tserver.on('error', (error: Error) => {\n\t\thandlePortalServerError({ error, hasListened, listeningPort, logger });\n\t});\n\tconst port = await listeningPort.promise;\n\n\treturn {\n\t\tclose: async () => {\n\t\t\tawait app.closePortalSessions();\n\t\t\tawait Promise.all(\n\t\t\t\tObject.keys(portalConfig.agents).map((agentId) =>\n\t\t\t\t\tsessionManager.invalidateAgentScope(agentId),\n\t\t\t\t),\n\t\t\t);\n\t\t\tawait closeNodeServer(server);\n\t\t},\n\t\tport,\n\t};\n}\n\nasync function main(): Promise<void> {\n\tconst startedServer = await startPortalServer({\n\t\targs: parsePortalServerCliArgs(process.argv.slice(2)),\n\t\tenv: process.env,\n\t});\n\tconst shutdown = async (): Promise<void> => {\n\t\tawait startedServer.close();\n\t\tprocess.exit(0);\n\t};\n\tprocess.on('SIGINT', () => {\n\t\tvoid shutdown();\n\t});\n\tprocess.on('SIGTERM', () => {\n\t\tvoid shutdown();\n\t});\n}\n\nif (import.meta.url === `file://${process.argv[1]}`) {\n\tvoid main().catch((error: unknown) => {\n\t\tprocess.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n\t\tprocess.exit(1);\n\t});\n}\n"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/json-schema.d.ts
4
+ type JsonPrimitive = boolean | null | number | string;
5
+ type JsonArray = JsonValue[];
6
+ type JsonObject = {
7
+ [key: string]: JsonValue;
8
+ };
9
+ type JsonValue = JsonArray | JsonObject | JsonPrimitive;
10
+ declare const jsonValueSchema: z.ZodType<JsonValue>;
11
+ declare const jsonObjectSchema: z.ZodType<JsonObject>;
12
+ declare function isJsonObject(value: unknown): value is JsonObject;
13
+ declare function assertJsonObject(value: unknown, label: string): JsonObject;
14
+ //#endregion
15
+ //#region src/catalog-types.d.ts
16
+ declare const portalToolAnnotationsSchema: z.ZodOptional<z.ZodObject<{
17
+ destructiveHint: z.ZodOptional<z.ZodBoolean>;
18
+ idempotentHint: z.ZodOptional<z.ZodBoolean>;
19
+ openWorldHint: z.ZodOptional<z.ZodBoolean>;
20
+ readOnlyHint: z.ZodOptional<z.ZodBoolean>;
21
+ title: z.ZodOptional<z.ZodString>;
22
+ }, z.core.$catchall<z.ZodUnion<[z.ZodUnion<[z.ZodUnion<[z.ZodUnion<[z.ZodType<JsonObject, unknown, z.core.$ZodTypeInternals<JsonObject, unknown>>, z.ZodString]>, z.ZodNumber]>, z.ZodBoolean]>, z.ZodNull]>>>>;
23
+ declare const safeToolMetadataSchema: z.ZodOptional<z.ZodType<JsonObject, unknown, z.core.$ZodTypeInternals<JsonObject, unknown>>>;
24
+ declare const portalToolRecordSchema: z.ZodObject<{
25
+ annotations: z.ZodOptional<z.ZodObject<{
26
+ destructiveHint: z.ZodOptional<z.ZodBoolean>;
27
+ idempotentHint: z.ZodOptional<z.ZodBoolean>;
28
+ openWorldHint: z.ZodOptional<z.ZodBoolean>;
29
+ readOnlyHint: z.ZodOptional<z.ZodBoolean>;
30
+ title: z.ZodOptional<z.ZodString>;
31
+ }, z.core.$catchall<z.ZodUnion<[z.ZodUnion<[z.ZodUnion<[z.ZodUnion<[z.ZodType<JsonObject, unknown, z.core.$ZodTypeInternals<JsonObject, unknown>>, z.ZodString]>, z.ZodNumber]>, z.ZodBoolean]>, z.ZodNull]>>>>;
32
+ description: z.ZodOptional<z.ZodString>;
33
+ inputSchema: z.ZodType<JsonObject, unknown, z.core.$ZodTypeInternals<JsonObject, unknown>>;
34
+ metadata: z.ZodOptional<z.ZodType<JsonObject, unknown, z.core.$ZodTypeInternals<JsonObject, unknown>>>;
35
+ namespace: z.ZodString;
36
+ outputSchema: z.ZodOptional<z.ZodType<JsonObject, unknown, z.core.$ZodTypeInternals<JsonObject, unknown>>>;
37
+ title: z.ZodOptional<z.ZodString>;
38
+ toolName: z.ZodString;
39
+ }, z.core.$strict>;
40
+ type PortalToolRecord = z.infer<typeof portalToolRecordSchema>;
41
+ type PortalToolAnnotations = z.infer<typeof portalToolAnnotationsSchema>;
42
+ //#endregion
43
+ export { safeToolMetadataSchema as a, JsonPrimitive as c, isJsonObject as d, jsonObjectSchema as f, portalToolRecordSchema as i, JsonValue as l, PortalToolRecord as n, JsonArray as o, jsonValueSchema as p, portalToolAnnotationsSchema as r, JsonObject as s, PortalToolAnnotations as t, assertJsonObject as u };
44
+ //# sourceMappingURL=catalog-types--gUGFPpN.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-types--gUGFPpN.d.ts","names":[],"sources":["../src/json-schema.ts","../src/catalog-types.ts"],"mappings":";;;KAEY,aAAA;AAAA,KACA,SAAA,GAAY,SAAA;AAAA,KACZ,UAAA;EAAA,CAAgB,GAAA,WAAc,SAAA;AAAA;AAAA,KAC9B,SAAA,GAAY,SAAA,GAAY,UAAA,GAAa,aAAA;AAAA,cAEpC,eAAA,EAAiB,CAAA,CAAE,OAAA,CAAQ,SAAA;AAAA,cAW3B,gBAAA,EAAkB,CAAA,CAAE,OAAA,CAAQ,UAAA;AAAA,iBAEzB,YAAA,CAAa,KAAA,YAAiB,KAAA,IAAS,UAAA;AAAA,iBAIvC,gBAAA,CAAiB,KAAA,WAAgB,KAAA,WAAgB,UAAA;;;cCmBpD,2BAAA,EAA2B,CAAA,CAAA,WAAA,CAAA,CAAA,CAAA,SAAA;;;;;;;cAW3B,sBAAA,EAAsB,CAAA,CAAA,WAAA,CAAA,CAAA,CAAA,OAAA,CAAA,UAAA,WAAA,CAAA,CAAA,IAAA,CAAA,iBAAA,CAAA,UAAA;AAAA,cAgBtB,sBAAA,EAAsB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;KAavB,gBAAA,GAAmB,CAAA,CAAE,KAAA,QAAa,sBAAA;AAAA,KAClC,qBAAA,GAAwB,CAAA,CAAE,KAAA,QAAa,2BAAA"}
@@ -0,0 +1,42 @@
1
+ import { n as PortalToolRecord, s as JsonObject } from "./catalog-types--gUGFPpN.js";
2
+
3
+ //#region src/tool-vm/typescript-artifact.d.ts
4
+ interface CatalogArtifactInput {
5
+ readonly tools: readonly PortalToolRecord[];
6
+ }
7
+ declare function generateTypescriptCatalogArtifact(input: CatalogArtifactInput): string;
8
+ //#endregion
9
+ //#region src/zod-schema-loader.d.ts
10
+ interface InputValidationIssue {
11
+ readonly code: string;
12
+ readonly message: string;
13
+ readonly path: readonly (number | string)[];
14
+ }
15
+ interface InputValidationError {
16
+ readonly kind: 'input_validation';
17
+ readonly issues: readonly InputValidationIssue[];
18
+ }
19
+ interface SchemaValidationUnavailableError {
20
+ readonly feature: string;
21
+ readonly kind: 'schema_validation_unavailable';
22
+ readonly message: string;
23
+ readonly path: readonly (number | string)[];
24
+ }
25
+ type PortalValidationResult = {
26
+ readonly ok: true;
27
+ readonly value: unknown;
28
+ } | {
29
+ readonly error: InputValidationError;
30
+ readonly ok: false;
31
+ };
32
+ type BuiltZodValidator = {
33
+ readonly ok: true;
34
+ readonly validate: (value: unknown) => PortalValidationResult;
35
+ } | {
36
+ readonly error: SchemaValidationUnavailableError;
37
+ readonly ok: false;
38
+ };
39
+ declare function buildZodValidatorFromJsonSchema(jsonSchema: JsonObject): BuiltZodValidator;
40
+ //#endregion
41
+ export { SchemaValidationUnavailableError as a, generateTypescriptCatalogArtifact as c, PortalValidationResult as i, InputValidationError as n, buildZodValidatorFromJsonSchema as o, InputValidationIssue as r, CatalogArtifactInput as s, BuiltZodValidator as t };
42
+ //# sourceMappingURL=index-BcI9c8sg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BcI9c8sg.d.ts","names":[],"sources":["../src/tool-vm/typescript-artifact.ts","../src/zod-schema-loader.ts"],"mappings":";;;UAKiB,oBAAA;EAAA,SACP,KAAA,WAAgB,gBAAA;AAAA;AAAA,iBAwBV,iCAAA,CAAkC,KAAA,EAAO,oBAAA;;;UC1BxC,oBAAA;EAAA,SACP,IAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;AAAA;AAAA,UAGO,oBAAA;EAAA,SACP,IAAA;EAAA,SACA,MAAA,WAAiB,oBAAA;AAAA;AAAA,UAGV,gCAAA;EAAA,SACP,OAAA;EAAA,SACA,IAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;AAAA;AAAA,KAGE,sBAAA;EAAA,SACE,EAAA;EAAA,SAAmB,KAAA;AAAA;EAAA,SACnB,KAAA,EAAO,oBAAA;EAAA,SAA+B,EAAA;AAAA;AAAA,KAExC,iBAAA;EAAA,SAEA,EAAA;EAAA,SACA,QAAA,GAAW,KAAA,cAAmB,sBAAA;AAAA;EAAA,SAG9B,KAAA,EAAO,gCAAA;EAAA,SACP,EAAA;AAAA;AAAA,iBA4EI,+BAAA,CAAgC,UAAA,EAAY,UAAA,GAAa,iBAAA"}