@agent-vm/mcp-portal 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.
Files changed (84) hide show
  1. package/README.md +42 -11
  2. package/dist/agent-bearer-token-DCtpDPCZ.js +59 -0
  3. package/dist/agent-bearer-token-DCtpDPCZ.js.map +1 -0
  4. package/dist/bin/mcp-portal.d.ts +28 -0
  5. package/dist/bin/mcp-portal.d.ts.map +1 -0
  6. package/dist/bin/mcp-portal.js +318 -0
  7. package/dist/bin/mcp-portal.js.map +1 -0
  8. package/dist/{catalog-types--gUGFPpN.d.ts → catalog-types-BVuB4Ynx.d.ts} +1 -1
  9. package/dist/{catalog-types--gUGFPpN.d.ts.map → catalog-types-BVuB4Ynx.d.ts.map} +1 -1
  10. package/dist/cli/index.d.ts +101 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +2 -0
  13. package/dist/core/index.d.ts +40 -0
  14. package/dist/core/index.d.ts.map +1 -0
  15. package/dist/core/index.js +5 -0
  16. package/dist/hmac-env-B4shpRRB.js +20 -0
  17. package/dist/hmac-env-B4shpRRB.js.map +1 -0
  18. package/dist/hmac-token-DBqWY3-w.js +100 -0
  19. package/dist/hmac-token-DBqWY3-w.js.map +1 -0
  20. package/dist/index.d.ts +5 -485
  21. package/dist/index.js +4 -5
  22. package/dist/mcp-proxy/index.d.ts +24 -0
  23. package/dist/mcp-proxy/index.d.ts.map +1 -0
  24. package/dist/mcp-proxy/index.js +2 -0
  25. package/dist/portal-auth/agent-bearer-token.d.ts +22 -0
  26. package/dist/portal-auth/agent-bearer-token.d.ts.map +1 -0
  27. package/dist/portal-auth/agent-bearer-token.js +2 -0
  28. package/dist/portal-auth/hmac-env.d.ts +6 -0
  29. package/dist/portal-auth/hmac-env.d.ts.map +1 -0
  30. package/dist/portal-auth/hmac-env.js +2 -0
  31. package/dist/portal-auth/hmac-token.d.ts +40 -0
  32. package/dist/portal-auth/hmac-token.d.ts.map +1 -0
  33. package/dist/portal-auth/hmac-token.js +2 -0
  34. package/dist/portal-config/index.d.ts +11 -0
  35. package/dist/portal-config/index.d.ts.map +1 -0
  36. package/dist/{tool-vm → portal-config}/index.js +2 -3
  37. package/dist/portal-core-CZQI7Ob6.d.ts +264 -0
  38. package/dist/portal-core-CZQI7Ob6.d.ts.map +1 -0
  39. package/dist/portal-core-Cgu714CL.js +416 -0
  40. package/dist/portal-core-Cgu714CL.js.map +1 -0
  41. package/dist/portal-session-DG2CUjIo.d.ts +184 -0
  42. package/dist/portal-session-DG2CUjIo.d.ts.map +1 -0
  43. package/dist/portal-tools-DKci1szO.js +528 -0
  44. package/dist/portal-tools-DKci1szO.js.map +1 -0
  45. package/dist/resolve-agent-identity-DnC_Pmnh.js +550 -0
  46. package/dist/resolve-agent-identity-DnC_Pmnh.js.map +1 -0
  47. package/dist/resolve-agent-identity-FQL02YdW.d.ts +81 -0
  48. package/dist/resolve-agent-identity-FQL02YdW.d.ts.map +1 -0
  49. package/dist/serve-command-CnSMUybd.js +358 -0
  50. package/dist/serve-command-CnSMUybd.js.map +1 -0
  51. package/dist/testing/fake-upstream-mcp-server.d.ts +5 -2
  52. package/dist/testing/fake-upstream-mcp-server.d.ts.map +1 -1
  53. package/dist/testing/fake-upstream-mcp-server.js +14 -4
  54. package/dist/testing/fake-upstream-mcp-server.js.map +1 -1
  55. package/dist/typescript-artifact-BVLt3Ifd.js +60 -0
  56. package/dist/typescript-artifact-BVLt3Ifd.js.map +1 -0
  57. package/dist/upstream-mcp-client-runtime-JlsfTm7_.js +760 -0
  58. package/dist/upstream-mcp-client-runtime-JlsfTm7_.js.map +1 -0
  59. package/dist/upstream-response-middleware-1MZnAD9C.d.ts +115 -0
  60. package/dist/upstream-response-middleware-1MZnAD9C.d.ts.map +1 -0
  61. package/dist/upstream-response-middleware-BjUWZ2G8.js +172 -0
  62. package/dist/upstream-response-middleware-BjUWZ2G8.js.map +1 -0
  63. package/dist/{index-BcI9c8sg.d.ts → zod-schema-loader-DLGQpYFD.d.ts} +3 -9
  64. package/dist/zod-schema-loader-DLGQpYFD.d.ts.map +1 -0
  65. package/dist/{typescript-artifact-BqU8okQy.js → zod-schema-loader-yNekKNpm.js} +85 -55
  66. package/dist/zod-schema-loader-yNekKNpm.js.map +1 -0
  67. package/package.json +30 -13
  68. package/dist/bin/agent-vm-mcp-portal.d.ts +0 -10
  69. package/dist/bin/agent-vm-mcp-portal.d.ts.map +0 -1
  70. package/dist/bin/agent-vm-mcp-portal.js +0 -56
  71. package/dist/bin/agent-vm-mcp-portal.js.map +0 -1
  72. package/dist/bin/portal-server.d.ts +0 -55
  73. package/dist/bin/portal-server.d.ts.map +0 -1
  74. package/dist/bin/portal-server.js +0 -289
  75. package/dist/bin/portal-server.js.map +0 -1
  76. package/dist/index-BcI9c8sg.d.ts.map +0 -1
  77. package/dist/index.d.ts.map +0 -1
  78. package/dist/tool-vm/index.d.ts +0 -2
  79. package/dist/tool-vm-ihnzDyjJ.js +0 -3
  80. package/dist/typescript-artifact-BqU8okQy.js.map +0 -1
  81. package/dist/upstream-mcp-client-runtime-DiBCBsDj.js +0 -1729
  82. package/dist/upstream-mcp-client-runtime-DiBCBsDj.js.map +0 -1
  83. package/dist/zod-schema-loader-CDDtoRE1.js +0 -90
  84. package/dist/zod-schema-loader-CDDtoRE1.js.map +0 -1
@@ -1,1729 +0,0 @@
1
- import { a as portalToolRecordSchema, l as jsonObjectSchema, n as decodeToolRef, r as encodeToolRef, t as generateTypescriptCatalogArtifact } from "./typescript-artifact-BqU8okQy.js";
2
- import { t as buildZodValidatorFromJsonSchema } from "./zod-schema-loader-CDDtoRE1.js";
3
- import { z } from "zod";
4
- import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
5
- import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
6
- import { Hono } from "hono";
7
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
- import { CallToolRequestSchema, ListToolsRequestSchema, ToolSchema } from "@modelcontextprotocol/sdk/types.js";
9
- import { mcpPortalCallRequiresApproval, resolveMcpPortalProfile } from "@agent-vm/config-contracts";
10
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
11
- import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
12
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
13
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
14
- import { normalizeHeaders } from "@modelcontextprotocol/sdk/shared/transport.js";
15
- //#region src/auth/hmac-env.ts
16
- const portalHmacKeyEnvPrefix = "PORTAL_HMAC_KEY__";
17
- const portalHmacKeyHexLength = 64;
18
- function portalHmacKeyEnvName(agentId) {
19
- return `${portalHmacKeyEnvPrefix}${agentId}`;
20
- }
21
- function parseHmacKeysFromEnv(env) {
22
- const keysByAgent = /* @__PURE__ */ new Map();
23
- for (const [name, value] of Object.entries(env)) {
24
- if (!name.startsWith(portalHmacKeyEnvPrefix) || value === void 0) continue;
25
- const agentId = name.slice(17);
26
- if (!/^[0-9a-f]+$/u.test(value) || value.length !== portalHmacKeyHexLength) throw new Error(`Malformed HMAC key in env var "${name}".`);
27
- keysByAgent.set(agentId, Buffer.from(value, "hex"));
28
- }
29
- return keysByAgent;
30
- }
31
- //#endregion
32
- //#region src/auth/hmac-token.ts
33
- const approvalTokenCallDigestSchema = z.object({
34
- argumentsHash: z.string().min(1),
35
- namespace: z.string().min(1),
36
- toolName: z.string().min(1)
37
- }).strict();
38
- const approvalTokenPayloadSchema = z.object({
39
- agentId: z.string().min(1),
40
- calls: z.array(approvalTokenCallDigestSchema),
41
- exp: z.number().int()
42
- }).strict();
43
- function base64UrlEncode(value) {
44
- return (typeof value === "string" ? Buffer.from(value, "utf8") : value).toString("base64url");
45
- }
46
- function canonicalize(value) {
47
- if (value === null || typeof value !== "object") return JSON.stringify(value ?? null);
48
- if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
49
- return `{${Object.entries(value).filter((entry) => entry[1] !== void 0).toSorted(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)).map(([key, entryValue]) => `${JSON.stringify(key)}:${canonicalize(entryValue)}`).join(",")}}`;
50
- }
51
- function hashCallArguments(args) {
52
- return createHash("sha256").update(canonicalize(args)).digest("base64url");
53
- }
54
- function signApprovalToken(props) {
55
- const payloadEncoded = base64UrlEncode(canonicalize({
56
- agentId: props.agentId,
57
- calls: [...props.calls],
58
- exp: props.expiresAtMs
59
- }));
60
- return `${payloadEncoded}.${createHmac("sha256", props.key).update(payloadEncoded).digest("base64url")}`;
61
- }
62
- function parseApprovalTokenPayload(payloadEncoded) {
63
- try {
64
- return approvalTokenPayloadSchema.parse(JSON.parse(Buffer.from(payloadEncoded, "base64url").toString("utf8")));
65
- } catch {
66
- return null;
67
- }
68
- }
69
- function isApprovalTokenParts(parts) {
70
- return parts.length === 2;
71
- }
72
- function callsMatch(leftCalls, rightCalls) {
73
- if (leftCalls.length !== rightCalls.length) return false;
74
- return leftCalls.every((leftCall, index) => {
75
- const rightCall = rightCalls[index];
76
- return rightCall !== void 0 && leftCall.argumentsHash === rightCall.argumentsHash && leftCall.namespace === rightCall.namespace && leftCall.toolName === rightCall.toolName;
77
- });
78
- }
79
- function verifyApprovalToken(props) {
80
- const parts = props.token.split(".");
81
- if (!isApprovalTokenParts(parts)) return {
82
- ok: false,
83
- reason: "malformed"
84
- };
85
- const [payloadEncoded, signatureEncoded] = parts;
86
- const expectedSignature = createHmac("sha256", props.key).update(payloadEncoded).digest();
87
- const providedSignature = Buffer.from(signatureEncoded, "base64url");
88
- if (providedSignature.length !== expectedSignature.length || !timingSafeEqual(providedSignature, expectedSignature)) return {
89
- ok: false,
90
- reason: "signature-mismatch"
91
- };
92
- const payload = parseApprovalTokenPayload(payloadEncoded);
93
- if (payload === null) return {
94
- ok: false,
95
- reason: "malformed"
96
- };
97
- if (payload.exp <= props.nowMs) return {
98
- ok: false,
99
- reason: "expired"
100
- };
101
- if (payload.agentId !== props.agentId) return {
102
- ok: false,
103
- reason: "agent-mismatch"
104
- };
105
- if (!callsMatch(payload.calls, props.calls)) return {
106
- ok: false,
107
- reason: "call-mismatch"
108
- };
109
- return { ok: true };
110
- }
111
- //#endregion
112
- //#region src/mcp-server/portal-call-validation.ts
113
- function validatePortalToolArguments(tool, argumentsValue) {
114
- const validator = buildZodValidatorFromJsonSchema(tool.inputSchema);
115
- if (!validator.ok) return {
116
- error: {
117
- ...validator.error,
118
- namespace: tool.namespace,
119
- toolName: tool.toolName
120
- },
121
- ok: false
122
- };
123
- const result = validator.validate(argumentsValue);
124
- if (!result.ok) return {
125
- error: {
126
- ...result.error,
127
- namespace: tool.namespace,
128
- toolName: tool.toolName
129
- },
130
- ok: false
131
- };
132
- return result;
133
- }
134
- //#endregion
135
- //#region src/portal-access-policy.ts
136
- const portalAgentIdentityBrand = Symbol("PortalAgentIdentity");
137
- function createPortalAgentIdentity(input) {
138
- validateIdentitySegment("agentId", input.agentId);
139
- validateIdentitySegment("agentScopeId", input.agentScopeId);
140
- if (input.sessionId !== void 0) validateIdentitySegment("sessionId", input.sessionId);
141
- return {
142
- agentId: input.agentId,
143
- agentScopeId: input.agentScopeId,
144
- ...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
145
- [portalAgentIdentityBrand]: true
146
- };
147
- }
148
- function validateIdentitySegment(name, value) {
149
- if (value.length === 0) throw new Error(`MCP Portal ${name} must not be empty.`);
150
- for (let index = 0; index < value.length; index += 1) {
151
- const codePoint = value.charCodeAt(index);
152
- if (codePoint < 32 || codePoint === 127) throw new Error(`MCP Portal ${name} must not contain control characters.`);
153
- }
154
- }
155
- function portalAgentScopeKey(identity) {
156
- return identity.sessionId ? `${identity.agentScopeId}\n${identity.sessionId}` : identity.agentScopeId;
157
- }
158
- function sortToolSelectors(selectors) {
159
- return [...selectors].toSorted((left, right) => {
160
- const namespaceOrder = left.namespace.localeCompare(right.namespace);
161
- return namespaceOrder === 0 ? left.toolName.localeCompare(right.toolName) : namespaceOrder;
162
- });
163
- }
164
- function resolvePortalAccessPolicy(props) {
165
- const agentNamespaces = props.config.enabledNamespacesByAgent[props.identity.agentId];
166
- const globalNamespaces = props.config.enabledNamespaces ?? [];
167
- const selectedNamespaces = agentNamespaces ?? (globalNamespaces.length > 0 ? globalNamespaces : props.config.defaultPolicy === "allow-all" ? props.upstreamNamespaces : []);
168
- const upstreamNamespaceSet = new Set(props.upstreamNamespaces);
169
- return {
170
- allowedNamespaces: selectedNamespaces.filter((namespace) => upstreamNamespaceSet.has(namespace)).toSorted(),
171
- enabledTools: sortToolSelectors(props.config.enabledToolsByAgent?.[props.identity.agentId] ?? []),
172
- hiddenTools: sortToolSelectors(props.config.hiddenToolsByAgent[props.identity.agentId] ?? [])
173
- };
174
- }
175
- //#endregion
176
- //#region src/tool-summary.ts
177
- function stringArrayFromValue(value) {
178
- if (!Array.isArray(value)) return [];
179
- return value.filter((entry) => typeof entry === "string").toSorted();
180
- }
181
- function objectPropertiesFromSchema$1(schema) {
182
- const properties = schema.properties;
183
- if (typeof properties !== "object" || properties === null || Array.isArray(properties)) return [];
184
- return Object.keys(properties).toSorted();
185
- }
186
- function summarizeJsonSchema(schema) {
187
- const properties = objectPropertiesFromSchema$1(schema);
188
- const required = stringArrayFromValue(schema.required);
189
- const requiredSet = new Set(required);
190
- return {
191
- optional: properties.filter((propertyName) => !requiredSet.has(propertyName)),
192
- propertyCount: properties.length,
193
- required,
194
- type: typeof schema.type === "string" ? schema.type : "unknown"
195
- };
196
- }
197
- function createToolSummary(tool) {
198
- const parsed = portalToolRecordSchema.parse(tool);
199
- const safety = {
200
- ...parsed.annotations?.destructiveHint !== void 0 ? { destructiveHint: parsed.annotations.destructiveHint } : {},
201
- ...parsed.annotations?.readOnlyHint !== void 0 ? { readOnlyHint: parsed.annotations.readOnlyHint } : {}
202
- };
203
- return {
204
- ...parsed.description !== void 0 ? { description: parsed.description } : {},
205
- input: summarizeJsonSchema(parsed.inputSchema),
206
- namespace: parsed.namespace,
207
- ...parsed.outputSchema ? { output: summarizeJsonSchema(parsed.outputSchema) } : {},
208
- safety,
209
- ...parsed.title !== void 0 ? { title: parsed.title } : {},
210
- toolName: parsed.toolName,
211
- toolRef: encodeToolRef({
212
- namespace: parsed.namespace,
213
- toolName: parsed.toolName
214
- })
215
- };
216
- }
217
- //#endregion
218
- //#region src/mcp-server/portal-tools.ts
219
- const requestIdSchema = z.string().min(1);
220
- const reservedRequestIds = new Set([
221
- "__proto__",
222
- "constructor",
223
- "prototype"
224
- ]);
225
- const safeRequestIdSchema = requestIdSchema.refine((id) => !reservedRequestIds.has(id), { message: "Portal request id uses a reserved object property name." });
226
- const namespaceToolSelectorSchema = z.object({
227
- namespace: z.string().min(1),
228
- toolName: z.string().min(1)
229
- }).strict();
230
- const listRequestSchema = z.object({
231
- cursor: z.string().regex(/^\d+$/u).optional(),
232
- id: safeRequestIdSchema,
233
- limit: z.number().int().positive().max(100).default(20),
234
- namespaces: z.array(z.string()).optional(),
235
- refs: z.array(z.string()).optional(),
236
- tools: z.array(namespaceToolSelectorSchema).optional()
237
- }).strict();
238
- const searchRequestSchema = z.object({
239
- id: safeRequestIdSchema,
240
- limit: z.number().int().positive().max(50).default(10),
241
- namespaces: z.array(z.string()).optional(),
242
- query: z.string().optional(),
243
- schemaDetail: z.enum([
244
- "none",
245
- "summary",
246
- "full"
247
- ]).default("summary")
248
- }).strict();
249
- const describeRequestSchema = z.object({
250
- id: safeRequestIdSchema,
251
- includeJsonSchema: z.boolean().default(true),
252
- includeRelated: z.boolean().default(true),
253
- includeTypescriptHelper: z.boolean().default(false),
254
- includeZod: z.boolean().default(false),
255
- refs: z.array(z.string()).optional(),
256
- tools: z.array(namespaceToolSelectorSchema).optional()
257
- }).strict();
258
- const callRequestSchema = z.object({
259
- arguments: jsonObjectSchema,
260
- id: safeRequestIdSchema,
261
- namespace: z.string().min(1),
262
- toolName: z.string().min(1)
263
- }).strict();
264
- const listInputSchema = z.object({ requests: z.array(listRequestSchema).min(1) }).strict();
265
- const searchInputSchema = z.object({ requests: z.array(searchRequestSchema).min(1) }).strict();
266
- const describeInputSchema = z.object({ requests: z.array(describeRequestSchema).min(1) }).strict();
267
- const callInputSchema = z.object({ calls: z.array(callRequestSchema).min(1) }).strict();
268
- const callExecutionInputSchema = z.object({
269
- calls: z.array(callRequestSchema).min(1),
270
- portalApprovalToken: z.string().min(1).optional()
271
- }).strict();
272
- function isToolSchemaProperties(value) {
273
- return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry));
274
- }
275
- function isStringArray(value) {
276
- return Array.isArray(value) && value.every((entry) => typeof entry === "string");
277
- }
278
- function toPortalInputJsonSchema(schema) {
279
- const jsonSchema = jsonObjectSchema.parse(z.toJSONSchema(schema, { io: "input" }));
280
- if (jsonSchema.type !== "object") throw new Error("MCP Portal tool input schemas must be JSON Schema objects.");
281
- const properties = isToolSchemaProperties(jsonSchema.properties) ? jsonSchema.properties : void 0;
282
- const required = isStringArray(jsonSchema.required) ? jsonSchema.required : void 0;
283
- return {
284
- ...jsonSchema,
285
- ...properties !== void 0 ? { properties } : {},
286
- ...required !== void 0 ? { required } : {},
287
- type: "object"
288
- };
289
- }
290
- const portalToolInputSchemas = {
291
- mcp_portal_call: toPortalInputJsonSchema(callInputSchema),
292
- mcp_portal_describe: toPortalInputJsonSchema(describeInputSchema),
293
- mcp_portal_list: toPortalInputJsonSchema(listInputSchema),
294
- mcp_portal_search: toPortalInputJsonSchema(searchInputSchema)
295
- };
296
- function messageFromError$1(error) {
297
- return error instanceof Error ? error.message : String(error);
298
- }
299
- function invalidPortalInput(error) {
300
- return {
301
- diagnostics: [],
302
- errors: [{
303
- kind: "invalid_portal_input",
304
- message: messageFromError$1(error)
305
- }],
306
- ok: false,
307
- results: {}
308
- };
309
- }
310
- function itemError(props) {
311
- return {
312
- error: props.error,
313
- input: props.input,
314
- ok: false
315
- };
316
- }
317
- function itemOutput(props) {
318
- return {
319
- input: props.input,
320
- ok: true,
321
- output: props.output
322
- };
323
- }
324
- function discoveryDiagnostics(session) {
325
- return session.catalog.discoveryFailures.map((failure) => ({
326
- kind: "upstream_discovery_failed",
327
- message: failure.message,
328
- namespace: failure.namespace
329
- }));
330
- }
331
- function portalBatchResult(results, diagnostics = []) {
332
- return {
333
- diagnostics,
334
- errors: [],
335
- ok: Object.values(results).every((result) => result.ok),
336
- results
337
- };
338
- }
339
- function duplicateIdErrors(items) {
340
- const seenIds = /* @__PURE__ */ new Set();
341
- const duplicateIds = /* @__PURE__ */ new Set();
342
- for (const item of items) {
343
- if (seenIds.has(item.id)) duplicateIds.add(item.id);
344
- seenIds.add(item.id);
345
- }
346
- return [...duplicateIds].toSorted().map((id) => ({
347
- id,
348
- kind: "duplicate_id",
349
- message: `Duplicate portal request id "${id}". Each request id must be unique.`
350
- }));
351
- }
352
- function duplicateIdResult(items) {
353
- const errors = duplicateIdErrors(items);
354
- return errors.length > 0 ? {
355
- diagnostics: [],
356
- errors,
357
- ok: false,
358
- results: {}
359
- } : null;
360
- }
361
- function findTool(session, selector) {
362
- return session.catalog.tools.find((tool) => tool.namespace === selector.namespace && tool.toolName === selector.toolName) ?? null;
363
- }
364
- function selectorsFromInput(tools, refs) {
365
- const selectors = [...tools ?? []];
366
- for (const toolRef of refs ?? []) try {
367
- selectors.push(decodeToolRef(toolRef));
368
- } catch (error) {
369
- return {
370
- error: {
371
- kind: "invalid_portal_input",
372
- message: messageFromError$1(error)
373
- },
374
- ok: false
375
- };
376
- }
377
- return {
378
- ok: true,
379
- selectors
380
- };
381
- }
382
- function applyExactFilters(tools, filters) {
383
- const namespaceFilter = new Set(filters.namespaces ?? []);
384
- const selectorResult = selectorsFromInput(filters.tools, filters.refs);
385
- if (!selectorResult.ok) return selectorResult;
386
- const exactSelectors = selectorResult.selectors;
387
- if (exactSelectors.length === 0) return {
388
- ok: true,
389
- tools: tools.filter((tool) => namespaceFilter.size === 0 || namespaceFilter.has(tool.namespace))
390
- };
391
- return {
392
- ok: true,
393
- tools: tools.filter((tool) => (namespaceFilter.size === 0 || namespaceFilter.has(tool.namespace)) && exactSelectors.some((selector) => selector.namespace === tool.namespace && selector.toolName === tool.toolName))
394
- };
395
- }
396
- function selectorKey(selector) {
397
- return `${selector.namespace}\n${selector.toolName}`;
398
- }
399
- function missingSelectorError(requestedSelectors, selectedTools) {
400
- const foundKeys = new Set(selectedTools.map((tool) => selectorKey({
401
- namespace: tool.namespace,
402
- toolName: tool.toolName
403
- })));
404
- if (requestedSelectors.filter((selector) => !foundKeys.has(selectorKey(selector))).length === 0) return null;
405
- return {
406
- kind: "unknown_or_denied_tool",
407
- message: "One or more requested tools are unknown or denied for this portal agent scope."
408
- };
409
- }
410
- function paginate(items, limit, cursor) {
411
- const offset = cursor ? Number.parseInt(cursor, 10) : 0;
412
- const safeOffset = Number.isFinite(offset) && offset > 0 ? offset : 0;
413
- const page = items.slice(safeOffset, safeOffset + limit);
414
- const nextOffset = safeOffset + page.length;
415
- return {
416
- items: page,
417
- ...nextOffset < items.length ? { nextCursor: String(nextOffset) } : {}
418
- };
419
- }
420
- function describeToolOutput(props) {
421
- const toolSummary = createToolSummary(props.tool);
422
- const result = {
423
- annotations: props.tool.annotations ?? {},
424
- namespace: props.tool.namespace,
425
- related: props.includeRelated ? props.session.graph.relationships.filter((relationship) => relationship.from.toolRef === toolSummary.toolRef || relationship.to.toolRef === toolSummary.toolRef) : [],
426
- toolName: props.tool.toolName,
427
- toolRef: toolSummary.toolRef
428
- };
429
- if (props.includeJsonSchema) {
430
- result.inputSchema = props.tool.inputSchema;
431
- result.outputSchema = props.tool.outputSchema;
432
- }
433
- if (props.includeZod) result.zod = {
434
- experimental: true,
435
- source: "z.fromJSONSchema(inputSchema)"
436
- };
437
- if (props.includeTypescriptHelper) result.typescriptHelper = generateTypescriptCatalogArtifact({ tools: [props.tool] });
438
- return result;
439
- }
440
- function searchOutputWithFullSchema(session, summary) {
441
- const tool = findTool(session, summary);
442
- const result = {
443
- input: summary.input,
444
- namespace: summary.namespace,
445
- safety: summary.safety,
446
- toolName: summary.toolName,
447
- toolRef: summary.toolRef
448
- };
449
- if (summary.description !== void 0) result.description = summary.description;
450
- if (summary.output !== void 0) result.output = summary.output;
451
- if (summary.relationshipHints !== void 0) result.relationshipHints = summary.relationshipHints;
452
- if (summary.schemaFieldMatches !== void 0) result.schemaFieldMatches = summary.schemaFieldMatches;
453
- if (summary.title !== void 0) result.title = summary.title;
454
- if (tool) {
455
- result.inputSchema = tool.inputSchema;
456
- result.outputSchema = tool.outputSchema;
457
- }
458
- return result;
459
- }
460
- function listRequestResult(session, request) {
461
- const filteredTools = applyExactFilters(session.catalog.tools, {
462
- ...request.namespaces !== void 0 ? { namespaces: request.namespaces } : {},
463
- ...request.refs !== void 0 ? { refs: request.refs } : {},
464
- ...request.tools !== void 0 ? { tools: request.tools } : {}
465
- });
466
- if (!filteredTools.ok) return itemError({
467
- error: filteredTools.error,
468
- input: request
469
- });
470
- const page = paginate(filteredTools.tools.map((tool) => createToolSummary(tool)), request.limit, request.cursor);
471
- return itemOutput({
472
- input: request,
473
- output: {
474
- namespaces: [...new Set(filteredTools.tools.map((tool) => tool.namespace))].toSorted(),
475
- ...page.nextCursor !== void 0 ? { nextCursor: page.nextCursor } : {},
476
- tools: page.items
477
- }
478
- });
479
- }
480
- function searchRequestResult(session, request) {
481
- const result = session.searchIndex.search({
482
- limit: request.limit,
483
- ...request.namespaces ? { namespaces: request.namespaces } : {},
484
- ...request.query !== void 0 ? { query: request.query } : {}
485
- });
486
- return itemOutput({
487
- input: request,
488
- output: { tools: request.schemaDetail === "full" ? result.results.map((summary) => searchOutputWithFullSchema(session, summary)) : result.results }
489
- });
490
- }
491
- function describeRequestResult(session, request) {
492
- const selectorResult = selectorsFromInput(request.tools, request.refs);
493
- if (!selectorResult.ok) return itemError({
494
- error: selectorResult.error,
495
- input: request
496
- });
497
- const selectors = selectorResult.selectors;
498
- const selectedTools = selectors.map((selector) => findTool(session, selector)).filter((tool) => tool !== null);
499
- const missingError = missingSelectorError(selectors, selectedTools);
500
- if (missingError) return itemError({
501
- error: {
502
- ...missingError,
503
- tools: selectors.filter((selector) => findTool(session, selector) === null)
504
- },
505
- input: request
506
- });
507
- return itemOutput({
508
- input: request,
509
- output: { tools: selectedTools.map((tool) => describeToolOutput({
510
- includeJsonSchema: request.includeJsonSchema,
511
- includeRelated: request.includeRelated,
512
- includeTypescriptHelper: request.includeTypescriptHelper,
513
- includeZod: request.includeZod,
514
- session,
515
- tool
516
- })) }
517
- });
518
- }
519
- function preparePortalCall(session, request) {
520
- const tool = findTool(session, request);
521
- if (!tool) return itemError({
522
- error: {
523
- kind: "unknown_or_denied_tool",
524
- message: "The requested tool is unknown or denied for this portal agent scope.",
525
- namespace: request.namespace,
526
- toolName: request.toolName
527
- },
528
- input: request
529
- });
530
- const validation = validatePortalToolArguments(tool, request.arguments);
531
- if (!validation.ok) return itemError({
532
- error: validation.error,
533
- input: request
534
- });
535
- const validatedArgumentsResult = jsonObjectSchema.safeParse(validation.value);
536
- if (!validatedArgumentsResult.success) return itemError({
537
- error: {
538
- kind: "invalid_portal_input",
539
- message: validatedArgumentsResult.error.message
540
- },
541
- input: request
542
- });
543
- return {
544
- input: request,
545
- tool,
546
- validatedArguments: validatedArgumentsResult.data
547
- };
548
- }
549
- async function executePreparedPortalCall(call, identity, runtime) {
550
- const input = {
551
- ...call.input,
552
- arguments: call.validatedArguments
553
- };
554
- try {
555
- return itemOutput({
556
- input,
557
- output: {
558
- namespace: call.tool.namespace,
559
- result: await runtime.callUpstreamTool({
560
- arguments: call.validatedArguments,
561
- agentScopeId: portalAgentScopeKey(identity),
562
- namespace: call.tool.namespace,
563
- toolName: call.tool.toolName
564
- }),
565
- toolName: call.tool.toolName
566
- }
567
- });
568
- } catch (error) {
569
- return itemError({
570
- error: {
571
- kind: "upstream_call_failed",
572
- message: messageFromError$1(error),
573
- namespace: call.tool.namespace,
574
- toolName: call.tool.toolName
575
- },
576
- input
577
- });
578
- }
579
- }
580
- function isPreparedPortalCall(value) {
581
- return "validatedArguments" in value;
582
- }
583
- async function addExecutableCallResults(props) {
584
- await Promise.all(props.preparedCalls.map(async (preparedCall) => {
585
- props.results[preparedCall.input.id] = await executePreparedPortalCall(preparedCall, props.identity, props.runtime);
586
- }));
587
- }
588
- function createPortalToolHandlers(runtime) {
589
- return {
590
- async call(call) {
591
- const parsedInput = callExecutionInputSchema.safeParse(call.input);
592
- if (!parsedInput.success) return invalidPortalInput(parsedInput.error);
593
- const duplicateResult = duplicateIdResult(parsedInput.data.calls);
594
- if (duplicateResult) return duplicateResult;
595
- const session = await runtime.getSession(call.identity);
596
- const preparedResults = parsedInput.data.calls.map((request) => preparePortalCall(session, request));
597
- const approvalCalls = preparedResults.filter(isPreparedPortalCall).map((executableCall) => ({
598
- arguments: executableCall.validatedArguments,
599
- id: executableCall.input.id,
600
- namespace: executableCall.tool.namespace,
601
- tool: executableCall.tool,
602
- toolName: executableCall.tool.toolName
603
- }));
604
- const allowDecision = { kind: "allow" };
605
- const approval = approvalCalls.length === 0 ? allowDecision : runtime.approval?.(approvalCalls, call.identity, parsedInput.data.portalApprovalToken) ?? allowDecision;
606
- const results = {};
607
- const callsToExecute = [];
608
- for (const preparedResult of preparedResults) {
609
- if (!isPreparedPortalCall(preparedResult)) {
610
- const input = preparedResult.input;
611
- if (typeof input === "object" && input !== null && "id" in input) {
612
- const id = input.id;
613
- if (typeof id === "string") results[id] = preparedResult;
614
- }
615
- continue;
616
- }
617
- if (approval.kind === "approval_required") {
618
- results[preparedResult.input.id] = itemError({
619
- error: {
620
- kind: "approval_required",
621
- level: approval.level,
622
- message: "Operator approval is required before this batch can run.",
623
- namespace: preparedResult.tool.namespace,
624
- toolName: preparedResult.tool.toolName
625
- },
626
- input: {
627
- ...preparedResult.input,
628
- arguments: preparedResult.validatedArguments
629
- }
630
- });
631
- continue;
632
- }
633
- if (approval.kind === "approval_token_missing") {
634
- results[preparedResult.input.id] = itemError({
635
- error: {
636
- kind: "approval_token_missing",
637
- message: "An MCP Portal approval token is required before this batch can run.",
638
- namespace: preparedResult.tool.namespace,
639
- toolName: preparedResult.tool.toolName
640
- },
641
- input: {
642
- ...preparedResult.input,
643
- arguments: preparedResult.validatedArguments
644
- }
645
- });
646
- continue;
647
- }
648
- if (approval.kind === "approval_token_invalid") {
649
- results[preparedResult.input.id] = itemError({
650
- error: {
651
- kind: "approval_token_invalid",
652
- message: `MCP Portal approval token is invalid: ${approval.reason}.`,
653
- namespace: preparedResult.tool.namespace,
654
- reason: approval.reason,
655
- toolName: preparedResult.tool.toolName
656
- },
657
- input: {
658
- ...preparedResult.input,
659
- arguments: preparedResult.validatedArguments
660
- }
661
- });
662
- continue;
663
- }
664
- callsToExecute.push(preparedResult);
665
- }
666
- await addExecutableCallResults({
667
- identity: call.identity,
668
- preparedCalls: callsToExecute,
669
- results,
670
- runtime
671
- });
672
- return portalBatchResult(results, discoveryDiagnostics(session));
673
- },
674
- async describe(call) {
675
- const parsedInput = describeInputSchema.safeParse(call.input);
676
- if (!parsedInput.success) return invalidPortalInput(parsedInput.error);
677
- const duplicateResult = duplicateIdResult(parsedInput.data.requests);
678
- if (duplicateResult) return duplicateResult;
679
- const session = await runtime.getSession(call.identity);
680
- return portalBatchResult(Object.fromEntries(parsedInput.data.requests.map((request) => [request.id, describeRequestResult(session, request)])), discoveryDiagnostics(session));
681
- },
682
- async list(call) {
683
- const parsedInput = listInputSchema.safeParse(call.input);
684
- if (!parsedInput.success) return invalidPortalInput(parsedInput.error);
685
- const duplicateResult = duplicateIdResult(parsedInput.data.requests);
686
- if (duplicateResult) return duplicateResult;
687
- const session = await runtime.getSession(call.identity);
688
- return portalBatchResult(Object.fromEntries(parsedInput.data.requests.map((request) => [request.id, listRequestResult(session, request)])), discoveryDiagnostics(session));
689
- },
690
- async search(call) {
691
- const parsedInput = searchInputSchema.safeParse(call.input);
692
- if (!parsedInput.success) return invalidPortalInput(parsedInput.error);
693
- const duplicateResult = duplicateIdResult(parsedInput.data.requests);
694
- if (duplicateResult) return duplicateResult;
695
- const session = await runtime.getSession(call.identity);
696
- return portalBatchResult(Object.fromEntries(parsedInput.data.requests.map((request) => [request.id, searchRequestResult(session, request)])), discoveryDiagnostics(session));
697
- }
698
- };
699
- }
700
- //#endregion
701
- //#region src/mcp-server/portal-mcp-server.ts
702
- const portalMcpToolNames = [
703
- "mcp_portal_list",
704
- "mcp_portal_search",
705
- "mcp_portal_describe",
706
- "mcp_portal_call"
707
- ];
708
- function listPortalMcpTools() {
709
- return [
710
- {
711
- description: "List authorized MCP namespaces and compact tool summaries.",
712
- inputSchema: portalToolInputSchemas.mcp_portal_list,
713
- name: "mcp_portal_list"
714
- },
715
- {
716
- description: "Search the caller scoped MCP Portal index.",
717
- inputSchema: portalToolInputSchemas.mcp_portal_search,
718
- name: "mcp_portal_search"
719
- },
720
- {
721
- description: "Describe exact MCP tool schemas and optional TypeScript/Zod helpers.",
722
- inputSchema: portalToolInputSchemas.mcp_portal_describe,
723
- name: "mcp_portal_describe"
724
- },
725
- {
726
- description: "Validate and call an authorized upstream MCP tool by namespace and toolName.",
727
- inputSchema: portalToolInputSchemas.mcp_portal_call,
728
- name: "mcp_portal_call"
729
- }
730
- ];
731
- }
732
- function jsonToolResult(value) {
733
- return {
734
- content: [{
735
- text: JSON.stringify(value),
736
- type: "text"
737
- }],
738
- ...value.ok ? {} : { isError: true }
739
- };
740
- }
741
- function createPortalMcpServer(props) {
742
- const handlers = createPortalToolHandlers(props.runtime);
743
- const server = new Server({
744
- name: "mcp-portal",
745
- version: "1.0.0"
746
- }, { capabilities: { tools: { listChanged: false } } });
747
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: listPortalMcpTools() }));
748
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
749
- switch (request.params.name) {
750
- case "mcp_portal_list": return jsonToolResult(await handlers.list({
751
- identity: props.identity,
752
- input: request.params.arguments ?? {}
753
- }));
754
- case "mcp_portal_search": return jsonToolResult(await handlers.search({
755
- identity: props.identity,
756
- input: request.params.arguments ?? {}
757
- }));
758
- case "mcp_portal_describe": return jsonToolResult(await handlers.describe({
759
- identity: props.identity,
760
- input: request.params.arguments ?? {}
761
- }));
762
- case "mcp_portal_call": return jsonToolResult(await handlers.call({
763
- identity: props.identity,
764
- input: request.params.arguments ?? {}
765
- }));
766
- default: return {
767
- content: [{
768
- text: `Unknown MCP Portal tool: ${request.params.name}`,
769
- type: "text"
770
- }],
771
- isError: true
772
- };
773
- }
774
- });
775
- return server;
776
- }
777
- //#endregion
778
- //#region src/mcp-server/portal-http-server.ts
779
- const mcpSessionIdHeader = "mcp-session-id";
780
- function timingSafeEqualString(left, right) {
781
- const leftBuffer = Buffer.from(left);
782
- const rightBuffer = Buffer.from(right);
783
- return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
784
- }
785
- function activeSessionKey(scopeId, sessionId) {
786
- return `${scopeId}\n${sessionId}`;
787
- }
788
- function createPortalHttpApp(options) {
789
- const app = new Hono();
790
- const activeSessions = /* @__PURE__ */ new Map();
791
- async function closeActiveSession(sessionKey, closeOptions) {
792
- const activeSession = activeSessions.get(sessionKey);
793
- if (!activeSession) return;
794
- activeSessions.delete(sessionKey);
795
- try {
796
- if (closeOptions.closeTransport) await activeSession.transport.close();
797
- } finally {
798
- await options.onSessionClosed?.(activeSession.identity);
799
- }
800
- }
801
- async function createActiveSession(identityBase) {
802
- const sessionId = randomUUID();
803
- const sessionKey = activeSessionKey(identityBase.agentScopeId, sessionId);
804
- let server = null;
805
- const identity = createPortalAgentIdentity({
806
- agentId: identityBase.agentId,
807
- agentScopeId: identityBase.agentScopeId,
808
- sessionId
809
- });
810
- const transport = new WebStandardStreamableHTTPServerTransport({
811
- onsessionclosed: () => {
812
- closeActiveSession(sessionKey, { closeTransport: false });
813
- },
814
- onsessioninitialized: (initializedSessionId) => {
815
- if (!server) throw new Error("MCP Portal session initialized before server connection.");
816
- activeSessions.set(activeSessionKey(identityBase.agentScopeId, initializedSessionId), {
817
- identity,
818
- server,
819
- transport
820
- });
821
- },
822
- sessionIdGenerator: () => sessionId
823
- });
824
- server = createPortalMcpServer({
825
- identity,
826
- runtime: options.toolRuntime
827
- });
828
- await server.connect(transport);
829
- return {
830
- identity,
831
- server,
832
- transport
833
- };
834
- }
835
- async function closePortalSessions() {
836
- await Promise.all([...activeSessions.keys()].map((sessionKey) => closeActiveSession(sessionKey, { closeTransport: true })));
837
- }
838
- app.get("/health", (context) => context.json({
839
- agents: [...options.registeredAgentIds ?? []].toSorted(),
840
- ok: true
841
- }));
842
- app.all("/agents/:agentId/mcp", async (context) => {
843
- const serverAccess = options.serverAccess;
844
- if (serverAccess !== void 0) {
845
- const providedSecret = context.req.header(serverAccess.headerName);
846
- if (providedSecret === void 0 || !timingSafeEqualString(providedSecret, serverAccess.expectedValue)) return context.json({
847
- error: { kind: "unauthorized" },
848
- ok: false
849
- }, 401);
850
- }
851
- const agentId = context.req.param("agentId");
852
- const agentIdentity = options.resolveAgentIdentity?.(agentId) ?? null;
853
- if (agentIdentity === null) return context.json({
854
- error: { kind: "unknown_agent" },
855
- ok: false
856
- }, 404);
857
- const mcpSessionId = context.req.header(mcpSessionIdHeader);
858
- if (mcpSessionId) {
859
- const activeSession = activeSessions.get(activeSessionKey(agentIdentity.agentScopeId, mcpSessionId));
860
- if (!activeSession) return new Response("Unknown MCP portal session", { status: 404 });
861
- return await activeSession.transport.handleRequest(context.req.raw);
862
- }
863
- return await (await createActiveSession(agentIdentity)).transport.handleRequest(context.req.raw);
864
- });
865
- return Object.assign(app, { closePortalSessions });
866
- }
867
- //#endregion
868
- //#region src/mcp-server/resolve-agent-identity.ts
869
- async function resolveAgentHmacKeyEntry(props) {
870
- const envKey = props.envKeys.get(props.agentId);
871
- if (envKey !== void 0) return [props.agentId, envKey];
872
- if (props.agent.hmacKey === void 0) throw new Error(`Missing HMAC key for MCP Portal agent "${props.agentId}".`);
873
- const secretValue = await props.resolveSecret(props.agent.hmacKey);
874
- if (!/^[0-9a-f]+$/u.test(secretValue) || secretValue.length !== 64) throw new Error(`MCP Portal agent "${props.agentId}" HMAC key must be 64 hex characters.`);
875
- return [props.agentId, Buffer.from(secretValue, "hex")];
876
- }
877
- async function resolveAgentHmacKeys(props) {
878
- return new Map(await Promise.all(Object.entries(props.agents).map(([agentId, agent]) => resolveAgentHmacKeyEntry({
879
- agent,
880
- agentId,
881
- envKeys: props.envKeys,
882
- resolveSecret: props.resolveSecret
883
- }))));
884
- }
885
- function createPortalAgentRuntimeRecords(props) {
886
- const records = /* @__PURE__ */ new Map();
887
- for (const [agentId, agent] of Object.entries(props.portalConfig.agents)) {
888
- const hmacKey = props.hmacKeys.get(agentId);
889
- if (hmacKey === void 0) throw new Error(`Missing HMAC key for MCP Portal agent "${agentId}".`);
890
- records.set(agentId, {
891
- agentId,
892
- hmacKey,
893
- profile: resolveMcpPortalProfile(props.portalConfig, agent.profile),
894
- profileName: agent.profile
895
- });
896
- }
897
- return records;
898
- }
899
- function createPortalHttpAgentResolver(records) {
900
- return (agentId) => {
901
- if (records.get(agentId) === void 0) return null;
902
- return createPortalAgentIdentity({
903
- agentId,
904
- agentScopeId: agentId
905
- });
906
- };
907
- }
908
- function approvalRequiredByProfile(profile, call) {
909
- const annotations = call.tool.annotations;
910
- return mcpPortalCallRequiresApproval(profile, {
911
- ...annotations === void 0 ? {} : { annotations },
912
- namespace: call.namespace,
913
- toolName: call.toolName
914
- });
915
- }
916
- function conservativeApprovalRequiredByProfile(profile, call) {
917
- return mcpPortalCallRequiresApproval(profile, {
918
- namespace: call.namespace,
919
- toolName: call.toolName
920
- });
921
- }
922
- function approvalTokenCallDigests(calls) {
923
- return calls.map((call) => ({
924
- argumentsHash: hashCallArguments(call.arguments),
925
- namespace: call.namespace,
926
- toolName: call.toolName
927
- }));
928
- }
929
- function createPortalApprovalVerifier(props) {
930
- return (calls, agentId, token) => {
931
- const record = props.records.get(agentId);
932
- if (record === void 0) return {
933
- kind: "approval_token_invalid",
934
- reason: "unknown-agent"
935
- };
936
- const callsRequiringApproval = calls.filter((call) => approvalRequiredByProfile(record.profile, call));
937
- if (callsRequiringApproval.length === 0) return { kind: "allow" };
938
- if (token === void 0) return { kind: "approval_token_missing" };
939
- const verificationProps = {
940
- agentId,
941
- calls: approvalTokenCallDigests(callsRequiringApproval),
942
- key: record.hmacKey,
943
- nowMs: Date.now(),
944
- token
945
- };
946
- const verification = verifyApprovalToken(verificationProps);
947
- if (verification.ok) return { kind: "allow" };
948
- const conservativeCallsRequiringApproval = calls.filter((call) => conservativeApprovalRequiredByProfile(record.profile, call));
949
- if (conservativeCallsRequiringApproval.length !== callsRequiringApproval.length) {
950
- if (verifyApprovalToken({
951
- ...verificationProps,
952
- calls: approvalTokenCallDigests(conservativeCallsRequiringApproval)
953
- }).ok) {
954
- props.onConservativeApprovalFallback?.({
955
- agentId,
956
- conservativeCallCount: conservativeCallsRequiringApproval.length,
957
- primaryReason: verification.reason,
958
- strictCallCount: callsRequiringApproval.length,
959
- toolRefs: conservativeCallsRequiringApproval.map((call) => `${call.namespace}/${call.toolName}`)
960
- });
961
- return { kind: "allow" };
962
- }
963
- }
964
- return {
965
- kind: "approval_token_invalid",
966
- reason: verification.reason
967
- };
968
- };
969
- }
970
- //#endregion
971
- //#region src/search-index.ts
972
- function collectSchemaText(value, parts) {
973
- if (Array.isArray(value)) {
974
- for (const item of value) collectSchemaText(item, parts);
975
- return;
976
- }
977
- if (typeof value !== "object" || value === null) {
978
- if (typeof value === "string") parts.push(value);
979
- return;
980
- }
981
- for (const [key, childValue] of Object.entries(value)) {
982
- parts.push(key);
983
- collectSchemaText(childValue, parts);
984
- }
985
- }
986
- function normalizeSearchText(text) {
987
- return text.toLowerCase().replace(/[_-]/g, " ");
988
- }
989
- function buildSearchText(tool) {
990
- const parts = [
991
- tool.namespace,
992
- tool.toolName,
993
- tool.title ?? "",
994
- tool.description ?? ""
995
- ];
996
- collectSchemaText(tool.inputSchema, parts);
997
- if (tool.outputSchema) collectSchemaText(tool.outputSchema, parts);
998
- return normalizeSearchText(parts.join(" "));
999
- }
1000
- function propertiesFromSchema(schema) {
1001
- const properties = schema.properties;
1002
- if (typeof properties !== "object" || properties === null || Array.isArray(properties)) return [];
1003
- return Object.keys(properties).toSorted();
1004
- }
1005
- function createRelationshipHints(tool, relationships) {
1006
- const targetToolRef = createToolSummary(tool).toolRef;
1007
- return relationships.filter((relationship) => relationship.to.toolRef === targetToolRef).map((relationship) => relationship.field === void 0 ? {
1008
- reason: relationship.reason,
1009
- sourceToolRef: relationship.from.toolRef,
1010
- type: relationship.type
1011
- } : {
1012
- field: relationship.field,
1013
- reason: relationship.reason,
1014
- sourceToolRef: relationship.from.toolRef,
1015
- type: relationship.type
1016
- }).toSorted((left, right) => {
1017
- const typeOrder = left.type.localeCompare(right.type);
1018
- if (typeOrder !== 0) return typeOrder;
1019
- return left.sourceToolRef.localeCompare(right.sourceToolRef);
1020
- });
1021
- }
1022
- function scopedSkillText(toolRef, graph) {
1023
- if (!graph) return "";
1024
- return graph.skills.filter((skill) => skill.toolRefs.includes(toolRef)).map((skill) => [
1025
- skill.title,
1026
- skill.description ?? "",
1027
- ...skill.tags
1028
- ].join(" ")).join(" ");
1029
- }
1030
- function scoreEntry(entry, terms) {
1031
- let score = 0;
1032
- for (const term of terms) if (entry.searchText.includes(term)) score += 1;
1033
- return score;
1034
- }
1035
- function compareSummaries(left, right) {
1036
- const namespaceOrder = left.namespace.localeCompare(right.namespace);
1037
- if (namespaceOrder !== 0) return namespaceOrder;
1038
- return left.toolName.localeCompare(right.toolName);
1039
- }
1040
- function withRelationshipHints(summary, relationshipHints) {
1041
- if (relationshipHints.length === 0) return summary;
1042
- return {
1043
- ...summary.description !== void 0 ? { description: summary.description } : {},
1044
- input: summary.input,
1045
- namespace: summary.namespace,
1046
- ...summary.output !== void 0 ? { output: summary.output } : {},
1047
- relationshipHints,
1048
- safety: summary.safety,
1049
- ...summary.title !== void 0 ? { title: summary.title } : {},
1050
- toolName: summary.toolName,
1051
- toolRef: summary.toolRef
1052
- };
1053
- }
1054
- function withSchemaFieldMatches(summary, schemaFieldMatches) {
1055
- if (schemaFieldMatches.length === 0) return summary;
1056
- return {
1057
- ...summary.description !== void 0 ? { description: summary.description } : {},
1058
- input: summary.input,
1059
- namespace: summary.namespace,
1060
- ...summary.output !== void 0 ? { output: summary.output } : {},
1061
- ...summary.relationshipHints !== void 0 ? { relationshipHints: summary.relationshipHints } : {},
1062
- safety: summary.safety,
1063
- schemaFieldMatches,
1064
- ...summary.title !== void 0 ? { title: summary.title } : {},
1065
- toolName: summary.toolName,
1066
- toolRef: summary.toolRef
1067
- };
1068
- }
1069
- function createSearchIndex(tools, graph) {
1070
- const entries = tools.map((tool) => portalToolRecordSchema.parse(tool)).map((tool) => {
1071
- const summary = createToolSummary(tool);
1072
- const relationshipHints = createRelationshipHints(tool, graph?.relationships ?? []);
1073
- const inputFields = propertiesFromSchema(tool.inputSchema);
1074
- const relationText = relationshipHints.map((hint) => [
1075
- hint.field ?? "",
1076
- hint.reason,
1077
- hint.sourceToolRef,
1078
- hint.type
1079
- ].join(" ")).join(" ");
1080
- const skillText = scopedSkillText(summary.toolRef, graph);
1081
- return {
1082
- inputFields,
1083
- relationshipHints,
1084
- searchText: normalizeSearchText([
1085
- buildSearchText(tool),
1086
- relationText,
1087
- skillText
1088
- ].join(" ")),
1089
- summary: withRelationshipHints(summary, relationshipHints)
1090
- };
1091
- }).toSorted((left, right) => compareSummaries(left.summary, right.summary));
1092
- return { search(query) {
1093
- const limit = Math.max(0, Math.floor(query.limit));
1094
- const namespaceFilter = new Set(query.namespaces ?? []);
1095
- const terms = normalizeSearchText(query.query ?? "").split(/\s+/).map((term) => term.trim()).filter(Boolean);
1096
- return { results: entries.filter((entry) => namespaceFilter.size === 0 || namespaceFilter.has(entry.summary.namespace)).map((entry) => ({
1097
- entry,
1098
- score: terms.length === 0 ? 1 : scoreEntry(entry, terms)
1099
- })).filter(({ score }) => score > 0).toSorted((left, right) => {
1100
- if (right.score !== left.score) return right.score - left.score;
1101
- return compareSummaries(left.entry.summary, right.entry.summary);
1102
- }).slice(0, limit).map(({ entry }) => {
1103
- const schemaFieldMatches = entry.inputFields.filter((fieldName) => terms.some((term) => normalizeSearchText(fieldName).includes(term))).toSorted();
1104
- return withSchemaFieldMatches(entry.summary, schemaFieldMatches);
1105
- }) };
1106
- } };
1107
- }
1108
- //#endregion
1109
- //#region src/tool-graph.ts
1110
- const genericLinkFields = new Set([
1111
- "id",
1112
- "name",
1113
- "title",
1114
- "url",
1115
- "uri"
1116
- ]);
1117
- function compareCodePoint(left, right) {
1118
- if (left < right) return -1;
1119
- if (left > right) return 1;
1120
- return 0;
1121
- }
1122
- function objectPropertiesFromSchema(schema) {
1123
- if (!schema) return [];
1124
- const properties = schema.properties;
1125
- if (typeof properties !== "object" || properties === null || Array.isArray(properties)) return [];
1126
- return Object.keys(properties).toSorted();
1127
- }
1128
- function collectEntityNames(schema) {
1129
- if (!schema) return [];
1130
- const names = [];
1131
- const schemaId = schema.$id;
1132
- const title = schema.title;
1133
- const entityName = schema.entityName;
1134
- if (typeof schemaId === "string" && schemaId.length > 0) names.push(schemaId);
1135
- if (typeof title === "string" && title.length > 0) names.push(title);
1136
- if (typeof entityName === "string" && entityName.length > 0) names.push(entityName);
1137
- return names.map((name) => name.toLowerCase()).toSorted();
1138
- }
1139
- function createGraphTool(tool) {
1140
- const record = portalToolRecordSchema.parse(tool);
1141
- const toolRef = encodeToolRef({
1142
- namespace: record.namespace,
1143
- toolName: record.toolName
1144
- });
1145
- return {
1146
- entityNames: [...collectEntityNames(record.inputSchema), ...collectEntityNames(record.outputSchema)].toSorted(),
1147
- inputFields: objectPropertiesFromSchema(record.inputSchema),
1148
- outputFields: objectPropertiesFromSchema(record.outputSchema),
1149
- record,
1150
- ref: {
1151
- namespace: record.namespace,
1152
- toolName: record.toolName,
1153
- toolRef
1154
- }
1155
- };
1156
- }
1157
- function sharesEntity(left, right) {
1158
- const rightEntities = new Set(right.entityNames);
1159
- return left.entityNames.some((entityName) => rightEntities.has(entityName));
1160
- }
1161
- function shouldLinkField(field, fromTool, toTool) {
1162
- if (!genericLinkFields.has(field.toLowerCase())) return true;
1163
- return fromTool.record.namespace === toTool.record.namespace && sharesEntity(fromTool, toTool);
1164
- }
1165
- function createSchemaRelationships(tools) {
1166
- const relationships = [];
1167
- for (const fromTool of tools) for (const toTool of tools) {
1168
- if (fromTool.ref.toolRef === toTool.ref.toolRef) continue;
1169
- const inputFields = new Set(toTool.inputFields);
1170
- for (const field of fromTool.outputFields) {
1171
- if (!inputFields.has(field) || !shouldLinkField(field, fromTool, toTool)) continue;
1172
- relationships.push({
1173
- field,
1174
- from: fromTool.ref,
1175
- reason: `Output field "${field}" matches input field "${field}".`,
1176
- to: toTool.ref,
1177
- type: "schema-field"
1178
- });
1179
- }
1180
- }
1181
- return relationships;
1182
- }
1183
- function createEntityRelationships(tools) {
1184
- const relationships = [];
1185
- for (const fromTool of tools) for (const toTool of tools) {
1186
- if (fromTool.ref.toolRef === toTool.ref.toolRef || !sharesEntity(fromTool, toTool)) continue;
1187
- relationships.push({
1188
- from: fromTool.ref,
1189
- reason: "Tools declare a shared schema entity through title, $id, or entityName.",
1190
- to: toTool.ref,
1191
- type: "entity"
1192
- });
1193
- }
1194
- return relationships;
1195
- }
1196
- function createSkillEntries(skills, allowedToolRefs) {
1197
- const entries = [];
1198
- for (const skill of skills) {
1199
- const scopedToolRefs = skill.toolRefs.filter((toolRef) => allowedToolRefs.has(toolRef)).toSorted();
1200
- if (scopedToolRefs.length === 0) continue;
1201
- entries.push({
1202
- ...skill.description !== void 0 ? { description: skill.description } : {},
1203
- tags: [...skill.tags ?? []].toSorted(),
1204
- title: skill.title,
1205
- toolRefs: scopedToolRefs
1206
- });
1207
- }
1208
- return entries.toSorted((left, right) => left.title.localeCompare(right.title));
1209
- }
1210
- function createSkillRelationships(skills, toolsByRef) {
1211
- const relationships = [];
1212
- for (const skill of skills) for (const fromToolRef of skill.toolRefs) for (const toToolRef of skill.toolRefs) {
1213
- if (fromToolRef === toToolRef) continue;
1214
- const fromTool = toolsByRef.get(fromToolRef);
1215
- const toTool = toolsByRef.get(toToolRef);
1216
- if (!fromTool || !toTool) continue;
1217
- relationships.push({
1218
- from: fromTool.ref,
1219
- reason: `Both tools are referenced by skill "${skill.title}".`,
1220
- to: toTool.ref,
1221
- type: "skill"
1222
- });
1223
- }
1224
- return relationships;
1225
- }
1226
- function compareRelationships(left, right) {
1227
- const typeOrder = compareCodePoint(left.type, right.type);
1228
- if (typeOrder !== 0) return typeOrder;
1229
- const targetOrder = compareCodePoint(left.to.toolRef, right.to.toolRef);
1230
- if (targetOrder !== 0) return targetOrder;
1231
- return compareCodePoint(left.from.toolRef, right.from.toolRef);
1232
- }
1233
- function buildToolGraph(input) {
1234
- const tools = input.tools.map((tool) => createGraphTool(tool)).toSorted((left, right) => compareCodePoint(left.ref.toolRef, right.ref.toolRef));
1235
- const allowedToolRefs = new Set(tools.map((tool) => tool.ref.toolRef));
1236
- const skills = createSkillEntries(input.skills ?? [], allowedToolRefs);
1237
- const toolsByRef = new Map(tools.map((tool) => [tool.ref.toolRef, tool]));
1238
- return {
1239
- relationships: [
1240
- ...createEntityRelationships(tools),
1241
- ...createSchemaRelationships(tools),
1242
- ...createSkillRelationships(skills, toolsByRef)
1243
- ].toSorted(compareRelationships),
1244
- skills
1245
- };
1246
- }
1247
- //#endregion
1248
- //#region src/portal-session.ts
1249
- function isHiddenTool(tool, hiddenTools) {
1250
- return hiddenTools.some((hiddenTool) => hiddenTool.namespace === tool.namespace && hiddenTool.toolName === tool.toolName);
1251
- }
1252
- function isEnabledTool(tool, enabledTools) {
1253
- if (enabledTools.length === 0) return true;
1254
- return enabledTools.some((enabledTool) => enabledTool.namespace === tool.namespace && enabledTool.toolName === tool.toolName);
1255
- }
1256
- function portalToolFromMcpTool(namespace, tool) {
1257
- return portalToolRecordSchema.parse({
1258
- ...tool.annotations !== void 0 ? { annotations: tool.annotations } : {},
1259
- ...tool.description !== void 0 ? { description: tool.description } : {},
1260
- inputSchema: tool.inputSchema,
1261
- namespace,
1262
- ...tool.outputSchema !== void 0 ? { outputSchema: tool.outputSchema } : {},
1263
- ...tool.title !== void 0 ? { title: tool.title } : {},
1264
- toolName: tool.name
1265
- });
1266
- }
1267
- function createSourceHash(tools) {
1268
- return createHash("sha256").update(JSON.stringify(tools)).digest("hex");
1269
- }
1270
- function messageFromError(error) {
1271
- return error instanceof Error ? error.message : String(error);
1272
- }
1273
- function createPortalSessionManager(options) {
1274
- const sessions = /* @__PURE__ */ new Map();
1275
- const agentScopeGenerations = /* @__PURE__ */ new Map();
1276
- const getNow = options.now ?? (() => Date.now());
1277
- function generationForAgentScope(agentScopeId) {
1278
- return agentScopeGenerations.get(agentScopeId) ?? 0;
1279
- }
1280
- function incrementAgentScopeGeneration(agentScopeId) {
1281
- agentScopeGenerations.set(agentScopeId, generationForAgentScope(agentScopeId) + 1);
1282
- }
1283
- function generationForScope(scopeKey) {
1284
- const agentScopeId = scopeKey.split("\n", 1)[0] ?? scopeKey;
1285
- return (agentScopeGenerations.get(scopeKey) ?? 0) + generationForAgentScope(agentScopeId);
1286
- }
1287
- function incrementScopeGeneration(scopeKey) {
1288
- agentScopeGenerations.set(scopeKey, generationForScope(scopeKey) + 1);
1289
- }
1290
- async function buildSession(identity) {
1291
- const policy = resolvePortalAccessPolicy({
1292
- config: options.accessPolicy,
1293
- identity,
1294
- upstreamNamespaces: options.upstreamNamespaces
1295
- });
1296
- const tools = [];
1297
- const discoveryFailures = [...options.discoveryFailures ?? []];
1298
- const allowedNamespaces = policy.allowedNamespaces;
1299
- const namespaceToolGroups = await Promise.allSettled(allowedNamespaces.map(async (namespace) => ({
1300
- mcpTools: await options.runtime.listTools({
1301
- agentScopeId: portalAgentScopeKey(identity),
1302
- namespace
1303
- }),
1304
- namespace
1305
- })));
1306
- for (const [index, namespaceToolGroup] of namespaceToolGroups.entries()) {
1307
- if (namespaceToolGroup.status === "rejected") {
1308
- const namespace = allowedNamespaces[index] ?? "unknown";
1309
- discoveryFailures.push({
1310
- message: messageFromError(namespaceToolGroup.reason),
1311
- namespace
1312
- });
1313
- continue;
1314
- }
1315
- const { mcpTools, namespace } = namespaceToolGroup.value;
1316
- for (const mcpTool of mcpTools) {
1317
- const portalTool = portalToolFromMcpTool(namespace, mcpTool);
1318
- if (isEnabledTool(portalTool, policy.enabledTools) && !isHiddenTool(portalTool, policy.hiddenTools)) tools.push(portalTool);
1319
- }
1320
- }
1321
- const sortedTools = tools.toSorted((left, right) => {
1322
- const namespaceOrder = left.namespace.localeCompare(right.namespace);
1323
- return namespaceOrder === 0 ? left.toolName.localeCompare(right.toolName) : namespaceOrder;
1324
- });
1325
- const graph = buildToolGraph({
1326
- skills: options.skills ?? [],
1327
- tools: sortedTools
1328
- });
1329
- return {
1330
- catalog: {
1331
- agentScopeId: identity.agentScopeId,
1332
- discoveryFailures,
1333
- generatedAt: new Date(getNow()).toISOString(),
1334
- sourceHash: createSourceHash(sortedTools),
1335
- tools: sortedTools
1336
- },
1337
- graph,
1338
- identity,
1339
- searchIndex: createSearchIndex(sortedTools, graph)
1340
- };
1341
- }
1342
- return {
1343
- async getSession(identity) {
1344
- const key = portalAgentScopeKey(identity);
1345
- const now = getNow();
1346
- const cached = sessions.get(key);
1347
- if (cached && cached.expiresAt > now) return cached.session;
1348
- const generation = generationForScope(key);
1349
- const session = await buildSession(identity);
1350
- if (generationForScope(key) === generation && session.catalog.discoveryFailures.length === 0) sessions.set(key, {
1351
- expiresAt: now + options.catalogTtlMs,
1352
- session
1353
- });
1354
- return session;
1355
- },
1356
- async invalidateAgentScope(agentScopeId) {
1357
- incrementAgentScopeGeneration(agentScopeId);
1358
- for (const key of sessions.keys()) if (key === agentScopeId || key.startsWith(`${agentScopeId}\n`)) sessions.delete(key);
1359
- await options.runtime.closeAgentScope(agentScopeId);
1360
- },
1361
- async invalidateSession(identity) {
1362
- const scopeKey = portalAgentScopeKey(identity);
1363
- incrementScopeGeneration(scopeKey);
1364
- sessions.delete(scopeKey);
1365
- if (options.runtime.closeSession) {
1366
- await options.runtime.closeSession(scopeKey);
1367
- return;
1368
- }
1369
- await options.runtime.closeAgentScope(scopeKey);
1370
- }
1371
- };
1372
- }
1373
- //#endregion
1374
- //#region src/upstream-response-middleware.ts
1375
- const credentialPatterns = [
1376
- /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi,
1377
- /\bBasic\s+[A-Za-z0-9._~+/=-]+/gi,
1378
- /\b(api[_-]?key|token|password|secret)=([^&\s]+)/gi
1379
- ];
1380
- function isCredentialConfigKey(key) {
1381
- const normalizedKey = key.toLowerCase().replace(/[^a-z0-9]/g, "");
1382
- return normalizedKey === "auth" || normalizedKey === "authorization" || normalizedKey === "apikey" || normalizedKey.endsWith("apikey") || normalizedKey.endsWith("token") || normalizedKey.endsWith("password") || normalizedKey.endsWith("secret");
1383
- }
1384
- function redactExactValues(text, exactValues) {
1385
- return exactValues.filter((value) => value.length > 0).reduce((currentText, value) => currentText.split(value).join("[REDACTED]"), text);
1386
- }
1387
- function redactExactCredentialText(text, options = {}) {
1388
- return redactExactValues(text, options.exactValues ?? []);
1389
- }
1390
- function redactCredentialText(text, options = {}) {
1391
- return redactExactValues(credentialPatterns.reduce((current, pattern) => current.replace(pattern, "[REDACTED]"), text), options.exactValues ?? []);
1392
- }
1393
- function redactJsonValue(value, options, keyHint) {
1394
- if (typeof value === "string") return keyHint && isCredentialConfigKey(keyHint) ? "[REDACTED]" : redactCredentialText(value, options);
1395
- if (Array.isArray(value)) return value.map((entry) => redactJsonValue(entry, options));
1396
- if (typeof value !== "object" || value === null) return value;
1397
- return Object.fromEntries(Object.entries(value).map(([key, childValue]) => [key, redactJsonValue(childValue, options, key)]));
1398
- }
1399
- function redactExactJsonValue(value, options) {
1400
- if (typeof value === "string") return redactExactCredentialText(value, options);
1401
- if (Array.isArray(value)) return value.map((entry) => redactExactJsonValue(entry, options));
1402
- if (typeof value !== "object" || value === null) return value;
1403
- return Object.fromEntries(Object.entries(value).map(([key, childValue]) => [key, redactExactJsonValue(childValue, options)]));
1404
- }
1405
- function isJsonValue(value) {
1406
- if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return true;
1407
- if (Array.isArray(value)) return value.every(isJsonValue);
1408
- if (typeof value !== "object") return false;
1409
- return Object.values(value).every(isJsonValue);
1410
- }
1411
- function redactUpstreamResponse(response, options = {}) {
1412
- return redactJsonValue(response, options);
1413
- }
1414
- function redactUpstreamCatalogValue(response, options = {}) {
1415
- return redactExactJsonValue(response, options);
1416
- }
1417
- function copyRedactedErrorField(props) {
1418
- const descriptor = Object.getOwnPropertyDescriptor(props.source, props.fieldName);
1419
- if (!descriptor || !("value" in descriptor)) return;
1420
- Object.defineProperty(props.target, props.fieldName, {
1421
- ...descriptor,
1422
- value: redactJsonValue(descriptor.value, props.options)
1423
- });
1424
- }
1425
- function redactThrownError(error, options = {}) {
1426
- const message = error instanceof Error ? error.message : String(error);
1427
- if (!(error instanceof Error)) return new Error(redactCredentialText(message, options));
1428
- const redactedError = new Error(redactCredentialText(message, options), { cause: error });
1429
- redactedError.name = error.name;
1430
- if (error.stack) redactedError.stack = redactCredentialText(error.stack, options);
1431
- copyRedactedErrorField({
1432
- fieldName: "code",
1433
- options,
1434
- source: error,
1435
- target: redactedError
1436
- });
1437
- copyRedactedErrorField({
1438
- fieldName: "data",
1439
- options,
1440
- source: error,
1441
- target: redactedError
1442
- });
1443
- return redactedError;
1444
- }
1445
- function toRedactedJsonValue(value, options = {}) {
1446
- const redacted = redactJsonValue(value, options);
1447
- if (isJsonValue(redacted)) return redacted;
1448
- return null;
1449
- }
1450
- //#endregion
1451
- //#region src/upstream-mcp-client-runtime.ts
1452
- const defaultConnectionTimeoutMs = 3e4;
1453
- function isObjectRecord(value) {
1454
- return typeof value === "object" && value !== null && !Array.isArray(value);
1455
- }
1456
- function isTransport(value) {
1457
- return isObjectRecord(value) && typeof value.start === "function" && typeof value.send === "function" && typeof value.close === "function";
1458
- }
1459
- function createSdkClient() {
1460
- const client = new Client({
1461
- name: "agent-vm-mcp-portal",
1462
- version: "1.0.0"
1463
- });
1464
- return {
1465
- callTool: async (params) => await client.callTool(params),
1466
- close: async () => {
1467
- await client.close();
1468
- },
1469
- connect: async (transport) => {
1470
- if (!isTransport(transport)) throw new Error("SDK MCP client requires a valid MCP transport.");
1471
- await client.connect(transport);
1472
- },
1473
- listTools: async (params) => {
1474
- const result = await client.listTools(params);
1475
- return {
1476
- ...result.nextCursor !== void 0 ? { nextCursor: result.nextCursor } : {},
1477
- tools: result.tools
1478
- };
1479
- }
1480
- };
1481
- }
1482
- function withRemoteHeaders(server) {
1483
- if (!server.headers) return server;
1484
- return {
1485
- ...server,
1486
- requestInit: {
1487
- ...server.requestInit,
1488
- headers: {
1489
- ...normalizeHeaders(server.requestInit?.headers),
1490
- ...server.headers
1491
- }
1492
- }
1493
- };
1494
- }
1495
- function createSdkTransport(server, transport) {
1496
- if (transport === "stdio") {
1497
- if (server.transport !== "stdio") throw new Error("Stdio transport requires stdio server config.");
1498
- return new StdioClientTransport({
1499
- ...server.args ? { args: [...server.args] } : {},
1500
- command: server.command,
1501
- ...server.cwd !== void 0 ? { cwd: server.cwd } : {},
1502
- ...server.env ? { env: { ...server.env } } : {}
1503
- });
1504
- }
1505
- if (server.transport === "stdio") throw new Error("Remote transport requires remote server config.");
1506
- const remoteServer = withRemoteHeaders(server);
1507
- if (transport === "sse") {
1508
- const options = {};
1509
- if (remoteServer.eventSourceInit !== void 0) options.eventSourceInit = remoteServer.eventSourceInit;
1510
- if (remoteServer.requestInit !== void 0) options.requestInit = remoteServer.requestInit;
1511
- return new SSEClientTransport(new URL(remoteServer.url), options);
1512
- }
1513
- const options = {};
1514
- if (remoteServer.requestInit !== void 0) options.requestInit = remoteServer.requestInit;
1515
- return new StreamableHTTPClientTransport(new URL(remoteServer.url), options);
1516
- }
1517
- function cacheKey(agentScopeId, namespace) {
1518
- return `${agentScopeId}\n${namespace}`;
1519
- }
1520
- function rootAgentScopeId(agentScopeId) {
1521
- return agentScopeId.split("\n", 1)[0] ?? agentScopeId;
1522
- }
1523
- function transportAttempts(server) {
1524
- if (server.transport === "auto-http") return ["streamable-http", "sse"];
1525
- return [server.transport];
1526
- }
1527
- function redactionValuesFromServer(server) {
1528
- if (server.transport === "stdio") return Object.entries(server.env ?? {}).filter(([key, value]) => isCredentialConfigKey(key) && value.length > 0).map(([, value]) => value);
1529
- return Object.entries(server.headers ?? {}).filter(([key, value]) => isCredentialConfigKey(key) && value.length > 0).map(([, value]) => value);
1530
- }
1531
- function timeoutMsForServer(server) {
1532
- return server.connectionTimeoutMs ?? defaultConnectionTimeoutMs;
1533
- }
1534
- async function withTimeout(promise, props) {
1535
- let timeout;
1536
- try {
1537
- return await Promise.race([promise, new Promise((_resolve, reject) => {
1538
- timeout = setTimeout(() => {
1539
- reject(/* @__PURE__ */ new Error(`${props.operation} timed out after ${props.timeoutMs}ms.`));
1540
- }, props.timeoutMs);
1541
- })]);
1542
- } finally {
1543
- if (timeout) clearTimeout(timeout);
1544
- }
1545
- }
1546
- async function listAllTools(client, cursor, collectedTools, timeoutMs) {
1547
- const result = await withTimeout(client.listTools(cursor ? { cursor } : void 0), {
1548
- operation: "MCP listTools",
1549
- timeoutMs
1550
- });
1551
- const nextTools = [...collectedTools, ...result.tools];
1552
- return result.nextCursor ? listAllTools(client, result.nextCursor, nextTools, timeoutMs) : nextTools;
1553
- }
1554
- async function closeClientAfterFailure(client) {
1555
- if (!client) return;
1556
- try {
1557
- await client.close();
1558
- } catch {}
1559
- }
1560
- async function closeClientForTeardown(client, props) {
1561
- if (!client) return;
1562
- try {
1563
- await client.close();
1564
- } catch (error) {
1565
- props.onCloseError?.(redactThrownError(error), props.context);
1566
- }
1567
- }
1568
- function closeErrorContextFromCacheKey(cacheKeyValue) {
1569
- const keyParts = cacheKeyValue.split("\n");
1570
- const namespace = keyParts.length > 1 ? keyParts[keyParts.length - 1] : void 0;
1571
- return {
1572
- agentScopeId: namespace !== void 0 ? keyParts.slice(0, keyParts.length - 1).join("\n") : cacheKeyValue,
1573
- ...namespace !== void 0 ? { namespace } : {}
1574
- };
1575
- }
1576
- function redactToolCatalog(tools, options) {
1577
- return tools.map((tool) => ToolSchema.parse(redactUpstreamCatalogValue(tool, options)));
1578
- }
1579
- function createUpstreamMcpClientRuntime(options) {
1580
- const serversByNamespace = new Map(options.servers.map((server) => [server.namespace, server]));
1581
- const clients = /* @__PURE__ */ new Map();
1582
- const pendingClients = /* @__PURE__ */ new Map();
1583
- const agentScopeGenerations = /* @__PURE__ */ new Map();
1584
- const createClient = options.createClient ?? createSdkClient;
1585
- const createTransport = options.createTransport ?? createSdkTransport;
1586
- const redactionValues = [...options.additionalRedactionValues ?? [], ...options.servers.flatMap((server) => redactionValuesFromServer(server))];
1587
- function generationForAgentScope(agentScopeId) {
1588
- return agentScopeGenerations.get(agentScopeId) ?? agentScopeGenerations.get(rootAgentScopeId(agentScopeId)) ?? 0;
1589
- }
1590
- function incrementAgentScopeGeneration(agentScopeId) {
1591
- const rootAgentScope = rootAgentScopeId(agentScopeId);
1592
- agentScopeGenerations.set(rootAgentScope, (agentScopeGenerations.get(rootAgentScope) ?? 0) + 1);
1593
- }
1594
- function incrementScopeGeneration(scopeKey) {
1595
- agentScopeGenerations.set(scopeKey, (agentScopeGenerations.get(scopeKey) ?? 0) + 1);
1596
- }
1597
- async function createConnectedClient(server) {
1598
- const attempts = transportAttempts(server);
1599
- async function tryAttempt(attemptIndex, lastError) {
1600
- const transportKind = attempts[attemptIndex];
1601
- if (transportKind === void 0) throw lastError ?? /* @__PURE__ */ new Error(`Could not connect to upstream MCP namespace "${server.namespace}".`);
1602
- const client = createClient();
1603
- const transport = createTransport(transportKind === "sse" && server.transport !== "stdio" ? withRemoteHeaders(server) : server, transportKind);
1604
- try {
1605
- await withTimeout(client.connect(transport), {
1606
- operation: `MCP ${transportKind} connect for namespace "${server.namespace}"`,
1607
- timeoutMs: timeoutMsForServer(server)
1608
- });
1609
- return client;
1610
- } catch (error) {
1611
- const redactedError = redactThrownError(error, { exactValues: redactionValues });
1612
- await closeClientAfterFailure(client);
1613
- return tryAttempt(attemptIndex + 1, redactedError);
1614
- }
1615
- }
1616
- return tryAttempt(0, null);
1617
- }
1618
- async function getClient(agentScopeId, namespace) {
1619
- const key = cacheKey(agentScopeId, namespace);
1620
- const cachedClient = clients.get(key);
1621
- if (cachedClient) return cachedClient.client;
1622
- const pendingClient = pendingClients.get(key);
1623
- const generation = generationForAgentScope(agentScopeId);
1624
- if (pendingClient && pendingClient.generation === generation) return pendingClient.promise;
1625
- if (pendingClient) {
1626
- pendingClients.delete(key);
1627
- pendingClient.promise.then(closeClientAfterFailure, () => void 0);
1628
- }
1629
- const server = serversByNamespace.get(namespace);
1630
- if (!server) throw new Error(`Unknown upstream MCP namespace "${namespace}".`);
1631
- const pending = createConnectedClient(server);
1632
- const pendingRecord = {
1633
- generation,
1634
- promise: pending
1635
- };
1636
- pendingClients.set(key, pendingRecord);
1637
- try {
1638
- const client = await pending;
1639
- if (generationForAgentScope(agentScopeId) !== generation) {
1640
- await closeClientAfterFailure(client);
1641
- throw new Error(`MCP client for agent scope "${rootAgentScopeId(agentScopeId)}" was invalidated.`);
1642
- }
1643
- clients.set(key, { client });
1644
- return client;
1645
- } finally {
1646
- if (pendingClients.get(key) === pendingRecord) pendingClients.delete(key);
1647
- }
1648
- }
1649
- return {
1650
- async callTool(call) {
1651
- const key = cacheKey(call.agentScopeId, call.namespace);
1652
- let client = null;
1653
- try {
1654
- client = await getClient(call.agentScopeId, call.namespace);
1655
- const server = serversByNamespace.get(call.namespace);
1656
- return redactUpstreamResponse(await withTimeout(client.callTool({
1657
- arguments: call.arguments,
1658
- name: call.toolName
1659
- }), {
1660
- operation: `MCP callTool ${call.namespace}.${call.toolName}`,
1661
- timeoutMs: server ? timeoutMsForServer(server) : defaultConnectionTimeoutMs
1662
- }), { exactValues: redactionValues });
1663
- } catch (error) {
1664
- clients.delete(key);
1665
- await closeClientAfterFailure(client);
1666
- throw redactThrownError(error, { exactValues: redactionValues });
1667
- }
1668
- },
1669
- async closeAgentScope(agentScopeId) {
1670
- incrementAgentScopeGeneration(agentScopeId);
1671
- const closePromises = [];
1672
- for (const [key, cachedClient] of clients.entries()) {
1673
- if (key !== agentScopeId && !key.startsWith(`${agentScopeId}\n`)) continue;
1674
- clients.delete(key);
1675
- closePromises.push(closeClientForTeardown(cachedClient.client, {
1676
- context: closeErrorContextFromCacheKey(key),
1677
- onCloseError: options.onCloseError
1678
- }));
1679
- }
1680
- for (const [key, pendingClient] of pendingClients.entries()) {
1681
- if (key !== agentScopeId && !key.startsWith(`${agentScopeId}\n`)) continue;
1682
- pendingClients.delete(key);
1683
- closePromises.push(pendingClient.promise.then((client) => closeClientForTeardown(client, {
1684
- context: closeErrorContextFromCacheKey(key),
1685
- onCloseError: options.onCloseError
1686
- }), () => void 0));
1687
- }
1688
- await Promise.all(closePromises);
1689
- },
1690
- async closeSession(scopeKey) {
1691
- incrementScopeGeneration(scopeKey);
1692
- const closePromises = [];
1693
- for (const [key, cachedClient] of clients.entries()) {
1694
- if (key !== scopeKey && !key.startsWith(`${scopeKey}\n`)) continue;
1695
- clients.delete(key);
1696
- closePromises.push(closeClientForTeardown(cachedClient.client, {
1697
- context: closeErrorContextFromCacheKey(key),
1698
- onCloseError: options.onCloseError
1699
- }));
1700
- }
1701
- for (const [key, pendingClient] of pendingClients.entries()) {
1702
- if (key !== scopeKey && !key.startsWith(`${scopeKey}\n`)) continue;
1703
- pendingClients.delete(key);
1704
- closePromises.push(pendingClient.promise.then((client) => closeClientForTeardown(client, {
1705
- context: closeErrorContextFromCacheKey(key),
1706
- onCloseError: options.onCloseError
1707
- }), () => void 0));
1708
- }
1709
- await Promise.all(closePromises);
1710
- },
1711
- async listTools(call) {
1712
- const key = cacheKey(call.agentScopeId, call.namespace);
1713
- let client = null;
1714
- try {
1715
- client = await getClient(call.agentScopeId, call.namespace);
1716
- const server = serversByNamespace.get(call.namespace);
1717
- return redactToolCatalog(await listAllTools(client, void 0, [], server ? timeoutMsForServer(server) : defaultConnectionTimeoutMs), { exactValues: redactionValues });
1718
- } catch (error) {
1719
- clients.delete(key);
1720
- await closeClientAfterFailure(client);
1721
- throw redactThrownError(error, { exactValues: redactionValues });
1722
- }
1723
- }
1724
- };
1725
- }
1726
- //#endregion
1727
- export { verifyApprovalToken as A, summarizeJsonSchema as C, validatePortalToolArguments as D, resolvePortalAccessPolicy as E, portalHmacKeyEnvName as M, hashCallArguments as O, createToolSummary as S, portalAgentScopeKey as T, createPortalMcpServer as _, redactThrownError as a, createPortalToolHandlers as b, toRedactedJsonValue as c, createSearchIndex as d, createPortalAgentRuntimeRecords as f, createPortalHttpApp as g, resolveAgentHmacKeys as h, redactExactCredentialText as i, parseHmacKeysFromEnv as j, signApprovalToken as k, createPortalSessionManager as l, createPortalHttpAgentResolver as m, isCredentialConfigKey as n, redactUpstreamCatalogValue as o, createPortalApprovalVerifier as p, redactCredentialText as r, redactUpstreamResponse as s, createUpstreamMcpClientRuntime as t, buildToolGraph as u, listPortalMcpTools as v, createPortalAgentIdentity as w, portalToolInputSchemas as x, portalMcpToolNames as y };
1728
-
1729
- //# sourceMappingURL=upstream-mcp-client-runtime-DiBCBsDj.js.map