@graphorin/mcp 0.6.0 → 0.7.0

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 (70) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +73 -5
  3. package/dist/client/adapt-result.d.ts +9 -1
  4. package/dist/client/adapt-result.d.ts.map +1 -1
  5. package/dist/client/adapt-result.js +28 -10
  6. package/dist/client/adapt-result.js.map +1 -1
  7. package/dist/client/client-handlers.js +7 -1
  8. package/dist/client/client-handlers.js.map +1 -1
  9. package/dist/client/client.d.ts.map +1 -1
  10. package/dist/client/client.js +47 -71
  11. package/dist/client/client.js.map +1 -1
  12. package/dist/client/inbound-filters.js +101 -1
  13. package/dist/client/inbound-filters.js.map +1 -1
  14. package/dist/client/index.d.ts +2 -1
  15. package/dist/client/index.js +2 -1
  16. package/dist/client/managed.d.ts +35 -0
  17. package/dist/client/managed.d.ts.map +1 -0
  18. package/dist/client/managed.js +136 -0
  19. package/dist/client/managed.js.map +1 -0
  20. package/dist/client/mcp-resource-reader.js +1 -1
  21. package/dist/client/mcp-resource-reader.js.map +1 -1
  22. package/dist/client/to-tools-run.js +119 -0
  23. package/dist/client/to-tools-run.js.map +1 -0
  24. package/dist/client/to-tools.d.ts +8 -0
  25. package/dist/client/to-tools.d.ts.map +1 -1
  26. package/dist/client/to-tools.js +27 -4
  27. package/dist/client/to-tools.js.map +1 -1
  28. package/dist/client/types.d.ts +12 -3
  29. package/dist/client/types.d.ts.map +1 -1
  30. package/dist/errors/index.d.ts +3 -3
  31. package/dist/errors/index.js +1 -1
  32. package/dist/errors/index.js.map +1 -1
  33. package/dist/helpers/identity.d.ts +11 -0
  34. package/dist/helpers/identity.d.ts.map +1 -1
  35. package/dist/helpers/identity.js +13 -2
  36. package/dist/helpers/identity.js.map +1 -1
  37. package/dist/index.d.ts +4 -4
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +6 -6
  40. package/dist/index.js.map +1 -1
  41. package/dist/package.js +6 -0
  42. package/dist/package.js.map +1 -0
  43. package/dist/registry/json-schema.js +26 -8
  44. package/dist/registry/json-schema.js.map +1 -1
  45. package/dist/transport/types.d.ts +9 -0
  46. package/package.json +13 -12
  47. package/src/client/adapt-result.ts +302 -0
  48. package/src/client/client-handlers.ts +215 -0
  49. package/src/client/client.ts +725 -0
  50. package/src/client/defer-loading.ts +108 -0
  51. package/src/client/inbound-filters.ts +246 -0
  52. package/src/client/index.ts +42 -0
  53. package/src/client/managed.ts +222 -0
  54. package/src/client/mcp-resource-reader.ts +183 -0
  55. package/src/client/pinning.ts +48 -0
  56. package/src/client/to-tools-run.ts +178 -0
  57. package/src/client/to-tools.ts +294 -0
  58. package/src/client/transport-factory.ts +117 -0
  59. package/src/client/types.ts +422 -0
  60. package/src/errors/index.ts +170 -0
  61. package/src/helpers/identity.ts +128 -0
  62. package/src/helpers/index.ts +8 -0
  63. package/src/helpers/validate-config.ts +95 -0
  64. package/src/index.ts +49 -0
  65. package/src/oauth/bridge.ts +143 -0
  66. package/src/oauth/index.ts +18 -0
  67. package/src/oauth/library.ts +61 -0
  68. package/src/registry/json-schema.ts +402 -0
  69. package/src/transport/index.ts +15 -0
  70. package/src/transport/types.ts +108 -0
@@ -1,16 +1,17 @@
1
- import { MCPCallTimeoutError, MCPCancelledError, MCPConnectionError, MCPInvalidConfigError, MCPProtocolError, MCPToolNotFoundError, MCPToolPinningError } from "../errors/index.js";
1
+ import { version } from "../package.js";
2
+ import { MCPCallTimeoutError, MCPCancelledError, MCPConnectionError, MCPInvalidConfigError, MCPProtocolError, MCPToolNotFoundError } from "../errors/index.js";
2
3
  import { deriveServerIdentity } from "../helpers/identity.js";
3
4
  import { validateMCPServerConfig } from "../helpers/validate-config.js";
4
5
  import { computeClientCapabilities, registerClientRequestHandlers } from "./client-handlers.js";
5
- import { adaptMCPTools } from "./to-tools.js";
6
+ import { runToTools } from "./to-tools-run.js";
6
7
  import { buildTransport } from "./transport-factory.js";
7
8
  import { incrementCounter } from "@graphorin/tools/audit";
8
9
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
9
- import { ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
10
+ import { ErrorCode, McpError, ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
10
11
 
11
12
  //#region src/client/client.ts
12
13
  const DEFAULT_CLIENT_NAME = "graphorin-mcp-client";
13
- const DEFAULT_CLIENT_VERSION = "0.6.0";
14
+ const DEFAULT_CLIENT_VERSION = version;
14
15
  /**
15
16
  * Process-scoped dedup flag for the deprecated SSE transport WARN.
16
17
  * Once set, subsequent {@link createMCPClient} calls with the SSE
@@ -95,7 +96,11 @@ async function createMCPClientFromSdkTransport(options) {
95
96
  name: "unknown",
96
97
  version: "0.0.0"
97
98
  };
98
- const serverIdentity = deriveServerIdentity(options.transportConfig, options.serverInfoName ?? serverInfo.name);
99
+ const derivedIdentity = deriveServerIdentity(options.transportConfig, options.serverInfoName);
100
+ const serverIdentity = Object.freeze({
101
+ ...derivedIdentity,
102
+ ...typeof serverInfo.name === "string" && serverInfo.name.length > 0 ? { reportedServerName: serverInfo.name } : {}
103
+ });
99
104
  serverIdRef.current = serverIdentity.id;
100
105
  sdkClient.onclose = () => {
101
106
  incrementCounter("mcp.transport.closed.total", { server: serverIdentity.id });
@@ -144,7 +149,7 @@ async function createMCPClientFromSdkTransport(options) {
144
149
  try {
145
150
  result = await sdkClient.listTools(cursor === void 0 ? {} : { cursor }, requestOptions);
146
151
  } catch (cause) {
147
- throw mapSdkError(cause, {});
152
+ throw mapSdkError(cause, { aborted: opts?.signal?.aborted === true });
148
153
  }
149
154
  tools.push(...result.tools ?? []);
150
155
  cursor = result.nextCursor;
@@ -175,7 +180,7 @@ async function createMCPClientFromSdkTransport(options) {
175
180
  try {
176
181
  result = await sdkClient.listResources(cursor === void 0 ? {} : { cursor }, requestOptions);
177
182
  } catch (cause) {
178
- throw mapSdkError(cause, {});
183
+ throw mapSdkError(cause, { aborted: opts?.signal?.aborted === true });
179
184
  }
180
185
  items.push(...result.resources ?? []);
181
186
  cursor = result.nextCursor;
@@ -201,7 +206,7 @@ async function createMCPClientFromSdkTransport(options) {
201
206
  try {
202
207
  result = await sdkClient.listPrompts(cursor === void 0 ? {} : { cursor }, requestOptions);
203
208
  } catch (cause) {
204
- throw mapSdkError(cause, {});
209
+ throw mapSdkError(cause, { aborted: opts?.signal?.aborted === true });
205
210
  }
206
211
  items.push(...result.prompts ?? []);
207
212
  cursor = result.nextCursor;
@@ -236,7 +241,10 @@ async function createMCPClientFromSdkTransport(options) {
236
241
  arguments: args
237
242
  }, void 0, requestOptions);
238
243
  } catch (cause) {
239
- const mapped = mapSdkError(cause, { tool: name });
244
+ const mapped = mapSdkError(cause, {
245
+ tool: name,
246
+ aborted: opts?.signal?.aborted === true
247
+ });
240
248
  if (mapped instanceof MCPCancelledError) incrementCounter("mcp.call.cancelled.total", {
241
249
  server: serverIdentity.id,
242
250
  tool: name
@@ -260,7 +268,7 @@ async function createMCPClientFromSdkTransport(options) {
260
268
  try {
261
269
  result = await sdkClient.readResource({ uri }, requestOptions);
262
270
  } catch (cause) {
263
- throw mapSdkError(cause, {});
271
+ throw mapSdkError(cause, { aborted: opts?.signal?.aborted === true });
264
272
  }
265
273
  const contents = result.contents ?? [];
266
274
  if (contents.length === 0) throw new MCPProtocolError(`MCP server returned no contents for resource '${uri}'.`, { metadata: { server: serverIdentity.id } });
@@ -283,7 +291,7 @@ async function createMCPClientFromSdkTransport(options) {
283
291
  ...args === void 0 ? {} : { arguments: args }
284
292
  }, requestOptions);
285
293
  } catch (cause) {
286
- throw mapSdkError(cause, {});
294
+ throw mapSdkError(cause, { aborted: opts?.signal?.aborted === true });
287
295
  }
288
296
  const messages = (result.messages ?? []).map((m) => Object.freeze({
289
297
  role: m.role,
@@ -291,68 +299,14 @@ async function createMCPClientFromSdkTransport(options) {
291
299
  }));
292
300
  return Object.freeze({ messages: Object.freeze(messages) });
293
301
  }
294
- let lastToolFingerprints;
302
+ const toolFingerprintRef = { current: void 0 };
295
303
  async function toTools(toolsOpts) {
296
- const adapted = adaptMCPTools({
304
+ return runToTools({
297
305
  client: clientApi,
298
- serverIdentity,
299
- catalogue: await listTools(),
300
- ...toolsOpts === void 0 ? {} : { options: toolsOpts },
301
- ...options.logger === void 0 ? {} : { logger: options.logger }
306
+ fingerprintRef: toolFingerprintRef,
307
+ ...options.logger === void 0 ? {} : { logger: options.logger },
308
+ ...toolsOpts === void 0 ? {} : { toolsOpts }
302
309
  });
303
- if (lastToolFingerprints !== void 0) for (const [name, hash] of adapted.fingerprints) {
304
- const previous = lastToolFingerprints.get(name);
305
- if (previous !== void 0 && previous !== hash) {
306
- incrementCounter("mcp.tools.changed.total", {
307
- server: serverIdentity.id,
308
- tool: name
309
- });
310
- options.logger?.("warn", "mcp.tools.changed: definition drifted between snapshots", {
311
- server: serverIdentity.id,
312
- tool: name,
313
- previous,
314
- current: hash
315
- });
316
- }
317
- }
318
- lastToolFingerprints = adapted.fingerprints;
319
- let pins = toolsOpts?.pinnedFingerprints;
320
- let mismatchAction = toolsOpts?.onPinMismatch ?? "warn";
321
- const pinStore = toolsOpts?.pinStore;
322
- if (pins === void 0 && pinStore !== void 0) {
323
- const stored = await pinStore.get(serverIdentity.id);
324
- if (stored === void 0) {
325
- const recorded = {};
326
- for (const [name, hash] of adapted.fingerprints) recorded[name] = hash;
327
- await pinStore.set(serverIdentity.id, recorded);
328
- incrementCounter("mcp.tools.pins-recorded.total", { server: serverIdentity.id });
329
- options.logger?.("info", "mcp.tools.pins-recorded: first-use fingerprints stored", {
330
- server: serverIdentity.id,
331
- tools: Object.keys(recorded).length
332
- });
333
- } else {
334
- pins = stored;
335
- mismatchAction = toolsOpts?.onPinMismatch ?? "reject";
336
- }
337
- }
338
- if (pins !== void 0) for (const [name, pinned] of Object.entries(pins)) {
339
- const current = adapted.fingerprints.get(name);
340
- if (current !== void 0 && current !== pinned) {
341
- if (mismatchAction === "reject") throw new MCPToolPinningError(`MCP tool '${name}' no longer matches its pinned definition fingerprint - the server changed the definition behind an approved name.`, { metadata: {
342
- server: serverIdentity.id,
343
- tool: name
344
- } });
345
- incrementCounter("mcp.tools.pin-mismatch.total", {
346
- server: serverIdentity.id,
347
- tool: name
348
- });
349
- options.logger?.("warn", "mcp.tools.pin-mismatch: pinned fingerprint diverged", {
350
- server: serverIdentity.id,
351
- tool: name
352
- });
353
- }
354
- }
355
- return adapted.tools;
356
310
  }
357
311
  async function close() {
358
312
  try {
@@ -407,10 +361,32 @@ function resolveTransportAuth(options) {
407
361
  }
408
362
  function mapSdkError(cause, ctx) {
409
363
  const metadata = ctx.tool === void 0 ? {} : { tool: ctx.tool };
364
+ if (ctx.aborted === true) return new MCPCancelledError("MCP request was cancelled.", {
365
+ metadata,
366
+ cause
367
+ });
368
+ if (cause instanceof McpError) {
369
+ if (cause.code === ErrorCode.RequestTimeout) return new MCPCallTimeoutError(`MCP request timed out${ctx.tool ? ` (tool: ${ctx.tool})` : ""}.`, {
370
+ metadata,
371
+ cause
372
+ });
373
+ if (cause.code === ErrorCode.MethodNotFound || /unknown\s+tool|tool\s+not\s+found|method\s+not\s+found/i.test(cause.message)) return new MCPToolNotFoundError(`MCP tool not found${ctx.tool ? `: ${ctx.tool}` : ""}.`, {
374
+ metadata,
375
+ cause
376
+ });
377
+ return new MCPProtocolError(cause.message.length === 0 ? cause.toString() : cause.message, {
378
+ metadata,
379
+ cause
380
+ });
381
+ }
410
382
  if (cause instanceof Error) {
411
383
  const name = cause.name;
412
384
  const message = cause.message ?? "";
413
- if (name === "AbortError" || /aborted|cancell/i.test(message)) return new MCPCancelledError("MCP request was cancelled.", {
385
+ if (name === "AbortError") return new MCPCancelledError("MCP request was cancelled.", {
386
+ metadata,
387
+ cause
388
+ });
389
+ if (/aborted|cancell/i.test(message)) return new MCPCancelledError("MCP request was cancelled.", {
414
390
  metadata,
415
391
  cause
416
392
  });
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["collisionStrategy: CollisionStrategy","tools: SdkTool[]","cursor: string | undefined","result: Awaited<ReturnType<typeof sdkClient.listTools>>","items: SdkResource[]","result: Awaited<ReturnType<typeof sdkClient.listResources>>","items: SdkPrompt[]","result: Awaited<ReturnType<typeof sdkClient.listPrompts>>","result: Awaited<ReturnType<typeof sdkClient.callTool>>","result: Awaited<ReturnType<typeof sdkClient.readResource>>","result: Awaited<ReturnType<typeof sdkClient.getPrompt>>","lastToolFingerprints: ReadonlyMap<string, string> | undefined","recorded: Record<string, string>","clientApi: MCPClient"],"sources":["../../src/client/client.ts"],"sourcesContent":["/**\n * `createMCPClient(...)` - the entry point for opening a typed MCP\n * client connection.\n *\n * The returned {@link MCPClient}:\n *\n * - Wraps the `@modelcontextprotocol/sdk` `Client` instance and the\n * selected SDK transport.\n * - Exposes `listTools` / `listResources` / `listPrompts` /\n * `callTool` / `readResource` / `getPrompt` / `close` plus the\n * strategy-aware `toTools(...)` adapter.\n * - Emits one INFO-log per server when the connected transport is\n * the deprecated SSE transport, on the resolved resumable\n * capability, and when the structured-content + outputSchema\n * round-trip first succeeds.\n *\n * @packageDocumentation\n */\n\nimport type { Tool } from '@graphorin/core';\nimport { incrementCounter } from '@graphorin/tools/audit';\nimport type { CollisionStrategy } from '@graphorin/tools/registry';\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport type { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport { ToolListChangedNotificationSchema } from '@modelcontextprotocol/sdk/types.js';\nimport {\n MCPCallTimeoutError,\n MCPCancelledError,\n MCPConnectionError,\n MCPInvalidConfigError,\n MCPProtocolError,\n MCPToolNotFoundError,\n MCPToolPinningError,\n} from '../errors/index.js';\nimport { deriveServerIdentity } from '../helpers/identity.js';\nimport { validateMCPServerConfig } from '../helpers/validate-config.js';\nimport type { ServerIdentity } from '../transport/types.js';\nimport { computeClientCapabilities, registerClientRequestHandlers } from './client-handlers.js';\nimport { adaptMCPTools } from './to-tools.js';\nimport { buildTransport, type TransportAuthSource } from './transport-factory.js';\nimport type {\n CreateMCPClientOptions,\n MCPCallToolResult,\n MCPClient,\n MCPContentPart,\n MCPPromptDefinition,\n MCPPromptMessage,\n MCPResourceContent,\n MCPResourceDefinition,\n MCPToolDefinition,\n MCPToToolsOptions,\n} from './types.js';\n\nconst DEFAULT_CLIENT_NAME = 'graphorin-mcp-client';\nconst DEFAULT_CLIENT_VERSION = '0.6.0';\n\n/**\n * Process-scoped dedup flag for the deprecated SSE transport WARN.\n * Once set, subsequent {@link createMCPClient} calls with the SSE\n * transport do not re-emit the warning. Tests reset via\n * {@link _resetSseWarnDedupForTesting}.\n */\nlet sseWarnEmittedThisProcess = false;\n\n/**\n * Reset the SSE WARN dedup flag. Used by tests.\n *\n * @experimental\n */\nexport function _resetSseWarnDedupForTesting(): void {\n sseWarnEmittedThisProcess = false;\n}\n\n/**\n * Open a typed MCP client connection.\n *\n * @stable\n */\nexport async function createMCPClient(options: CreateMCPClientOptions): Promise<MCPClient> {\n validateMCPServerConfig({ transport: options.transport });\n\n // Mutually exclusive (documented on `CreateMCPClientOptions`): a live\n // OAuth provider and a static pre-shared token cannot both drive the\n // outbound `Authorization` header.\n if (options.authProvider !== undefined && options.bearerToken !== undefined) {\n throw new MCPInvalidConfigError(\n '`authProvider` and `bearerToken` are mutually exclusive; supply at most one.',\n { metadata: { transport: options.transport.kind } },\n );\n }\n const auth = resolveTransportAuth(options);\n if (auth !== undefined && options.transport.kind === 'stdio') {\n throw new MCPInvalidConfigError(\n 'authProvider / bearerToken require an HTTP transport (streamable-http or sse); the stdio transport carries no Authorization header.',\n { metadata: { transport: 'stdio' } },\n );\n }\n\n if (\n options.transport.kind === 'sse' &&\n options.suppressDeprecatedTransportWarning !== true &&\n !sseWarnEmittedThisProcess\n ) {\n sseWarnEmittedThisProcess = true;\n if (options.logger !== undefined) {\n options.logger(\n 'warn',\n 'MCP SSE transport is deprecated; migrate the server to the streamable-http transport when possible.',\n );\n }\n incrementCounter('mcp.transport.deprecated.warn.total', { transport: 'sse' });\n }\n\n const built = buildTransport(options.transport, auth === undefined ? undefined : { auth });\n return createMCPClientFromSdkTransport({\n transport: built.transport,\n transportConfig: options.transport,\n ...(options.collisionStrategy === undefined\n ? {}\n : { collisionStrategy: options.collisionStrategy }),\n ...(options.priority === undefined ? {} : { priority: options.priority }),\n ...(options.serverInfoName === undefined ? {} : { serverInfoName: options.serverInfoName }),\n ...(options.logger === undefined ? {} : { logger: options.logger }),\n ...(options.clientName === undefined ? {} : { clientName: options.clientName }),\n ...(options.clientVersion === undefined ? {} : { clientVersion: options.clientVersion }),\n ...(options.elicitation === undefined ? {} : { elicitation: options.elicitation }),\n ...(options.sampling === undefined ? {} : { sampling: options.sampling }),\n ...(options.onTransportClose === undefined\n ? {}\n : { onTransportClose: options.onTransportClose }),\n ...(options.onTransportError === undefined\n ? {}\n : { onTransportError: options.onTransportError }),\n });\n}\n\n/**\n * Internal factory that takes a pre-built SDK `Transport`. Exposed\n * for the test seam - production code uses {@link createMCPClient}.\n *\n * @internal\n */\nexport interface CreateMCPClientFromSdkTransportOptions {\n readonly transport: Transport;\n readonly transportConfig: import('../transport/types.js').MCPTransportConfig;\n readonly collisionStrategy?: CollisionStrategy;\n readonly priority?: number;\n readonly serverInfoName?: string;\n readonly logger?: CreateMCPClientOptions['logger'];\n readonly clientName?: string;\n readonly clientVersion?: string;\n readonly elicitation?: CreateMCPClientOptions['elicitation'];\n readonly sampling?: CreateMCPClientOptions['sampling'];\n readonly onTransportClose?: CreateMCPClientOptions['onTransportClose'];\n readonly onTransportError?: CreateMCPClientOptions['onTransportError'];\n}\n\n/**\n * Build an {@link MCPClient} from a pre-built SDK transport. The\n * production {@link createMCPClient} delegates here after building\n * the SDK transport from a {@link MCPTransportConfig}.\n *\n * @internal\n */\nexport async function createMCPClientFromSdkTransport(\n options: CreateMCPClientFromSdkTransportOptions,\n): Promise<MCPClient> {\n const sdkClient = new Client({\n name: options.clientName ?? DEFAULT_CLIENT_NAME,\n version: options.clientVersion ?? DEFAULT_CLIENT_VERSION,\n });\n\n // WI-13 (P2-2): advertise + register the server-initiated request\n // handlers (elicitation / sampling) before connecting, so they are in\n // place when the session starts. Both are gated - capabilities are\n // advertised only when the operator supplied the matching callback.\n const clientCapabilities = computeClientCapabilities({\n elicitation: options.elicitation,\n sampling: options.sampling,\n });\n if (clientCapabilities !== undefined) {\n sdkClient.registerCapabilities(clientCapabilities);\n }\n const serverIdRef = { current: 'unknown' };\n registerClientRequestHandlers(sdkClient, {\n ...(options.elicitation === undefined ? {} : { elicitation: options.elicitation }),\n ...(options.sampling === undefined ? {} : { sampling: options.sampling }),\n serverIdRef,\n });\n // MC-6: surface server-side catalogue churn - at minimum an audit\n // counter + log line; operators re-run toTools() to refresh and\n // re-sanitize the catalogue (which also re-runs the drift diff).\n sdkClient.setNotificationHandler(ToolListChangedNotificationSchema, async () => {\n incrementCounter('mcp.tools.list-changed.total', {\n server: serverIdRef.current ?? 'unknown',\n });\n options.logger?.(\n 'warn',\n 'mcp.tools.list_changed received - re-run toTools() to refresh + re-sanitize the catalogue',\n { server: serverIdRef.current },\n );\n });\n\n try {\n await sdkClient.connect(options.transport);\n } catch (cause) {\n throw new MCPConnectionError(\n `MCP transport could not be established: ${(cause as Error).message ?? String(cause)}`,\n {\n metadata: { transport: options.transportConfig.kind },\n cause,\n },\n );\n }\n\n const serverInfo = sdkClient.getServerVersion() ?? {\n name: 'unknown',\n version: '0.0.0',\n };\n const serverIdentity = deriveServerIdentity(\n options.transportConfig,\n options.serverInfoName ?? serverInfo.name,\n );\n // Backfill the server id so the client-side request handlers\n // (registered before connect) label their counters with it.\n serverIdRef.current = serverIdentity.id;\n // mcp-skills-10: surface transport lifecycle. Without these a dead\n // stdio child / dropped HTTP session is observable only as\n // MCPProtocolErrors on subsequent calls. The SDK Protocol base\n // exposes assignable onclose/onerror callbacks (the transport's own\n // handlers are managed by the SDK - never overwrite those).\n sdkClient.onclose = () => {\n incrementCounter('mcp.transport.closed.total', { server: serverIdentity.id });\n options.logger?.('warn', 'MCP transport closed - rebuild the client to reconnect', {\n server: serverIdentity.id,\n });\n options.onTransportClose?.({ server: serverIdentity.id });\n };\n sdkClient.onerror = (error: Error) => {\n incrementCounter('mcp.transport.error.total', { server: serverIdentity.id });\n options.onTransportError?.(error, { server: serverIdentity.id });\n };\n const collisionStrategy: CollisionStrategy = options.collisionStrategy ?? 'auto-prefix';\n\n // MC-1: the client-side eventStore option was removed - per the\n // Streamable HTTP spec event replay is the SERVER's responsibility,\n // and the SDK transport auto-reconnects with Last-Event-ID on its\n // own. Warn legacy callers instead of silently ignoring the option.\n if ((options as { readonly eventStore?: unknown }).eventStore !== undefined) {\n options.logger?.(\n 'warn',\n \"the client 'eventStore' option was removed (MC-1): event replay is a server responsibility; the SDK transport auto-reconnects with Last-Event-ID. Remove the option.\",\n { server: serverIdentity.id },\n );\n }\n // MC-9: a session id means stateful routing, NOT a replay guarantee.\n const sessionIdPresent =\n isStreamableHttp(options.transport) &&\n (options.transport as StreamableHTTPClientTransport).sessionId !== undefined;\n const resumable = sessionIdPresent;\n if (options.logger !== undefined) {\n options.logger('info', 'mcp.session.session-id.resolved', {\n server: serverIdentity.id,\n value: sessionIdPresent,\n source: sessionIdPresent ? 'session-id-present' : 'transport-default',\n });\n }\n let structuredContentSeenLogged = false;\n\n /**\n * mcp-skills-02: MCP list operations are cursor-paginated (protocol\n * 2024-11-05+) and the SDK does NOT auto-paginate - a single call\n * returns page 1 only. Ignoring `nextCursor` silently truncated the\n * catalogue: tools beyond the first page never reached `toTools()`,\n * defer-loading thresholds counted a partial catalogue, pin\n * fingerprints covered a partial catalogue, and resources/prompts were\n * under-listed. Every list surface now drains the cursor chain, with a\n * defensive page cap against buggy/adversarial servers that never\n * terminate it.\n */\n const MAX_LIST_PAGES = 100;\n\n function warnListPageCap(what: 'tools' | 'resources' | 'prompts', collected: number): void {\n options.logger?.(\n 'warn',\n `mcp.list.${what}.page-cap-reached: server kept returning nextCursor after ${MAX_LIST_PAGES} pages; the ${what} catalogue is truncated at ${collected} entries.`,\n { server: serverIdentity.id },\n );\n }\n\n async function listTools(opts?: {\n signal?: AbortSignal;\n }): Promise<ReadonlyArray<MCPToolDefinition>> {\n const requestOptions = opts?.signal === undefined ? {} : { signal: opts.signal };\n type SdkTool = {\n name: string;\n description?: string;\n inputSchema: Readonly<Record<string, unknown>>;\n outputSchema?: Readonly<Record<string, unknown>>;\n title?: string;\n };\n const tools: SdkTool[] = [];\n let cursor: string | undefined;\n for (let page = 0; ; page++) {\n if (page >= MAX_LIST_PAGES) {\n warnListPageCap('tools', tools.length);\n break;\n }\n let result: Awaited<ReturnType<typeof sdkClient.listTools>>;\n try {\n result = await sdkClient.listTools(cursor === undefined ? {} : { cursor }, requestOptions);\n } catch (cause) {\n throw mapSdkError(cause, {});\n }\n tools.push(...((result.tools ?? []) as ReadonlyArray<SdkTool>));\n cursor = (result as { nextCursor?: string }).nextCursor;\n if (cursor === undefined) break;\n }\n if (!structuredContentSeenLogged && tools.some((t) => t.outputSchema !== undefined)) {\n structuredContentSeenLogged = true;\n if (options.logger !== undefined) {\n options.logger('info', 'mcp.server.structured-content.detected', {\n server: serverIdentity.id,\n });\n }\n }\n return Object.freeze(\n tools.map((t) =>\n Object.freeze({\n name: t.name,\n description: t.description ?? '',\n inputSchema: t.inputSchema,\n ...(t.outputSchema === undefined ? {} : { outputSchema: t.outputSchema }),\n ...(t.title === undefined ? {} : { title: t.title }),\n }),\n ),\n );\n }\n\n async function listResources(opts?: {\n signal?: AbortSignal;\n }): Promise<ReadonlyArray<MCPResourceDefinition>> {\n const requestOptions = opts?.signal === undefined ? {} : { signal: opts.signal };\n type SdkResource = {\n uri: string;\n name?: string;\n description?: string;\n mimeType?: string;\n };\n const items: SdkResource[] = [];\n let cursor: string | undefined;\n for (let page = 0; ; page++) {\n if (page >= MAX_LIST_PAGES) {\n warnListPageCap('resources', items.length);\n break;\n }\n let result: Awaited<ReturnType<typeof sdkClient.listResources>>;\n try {\n result = await sdkClient.listResources(\n cursor === undefined ? {} : { cursor },\n requestOptions,\n );\n } catch (cause) {\n throw mapSdkError(cause, {});\n }\n items.push(...((result.resources ?? []) as ReadonlyArray<SdkResource>));\n cursor = (result as { nextCursor?: string }).nextCursor;\n if (cursor === undefined) break;\n }\n return Object.freeze(\n items.map((r) =>\n Object.freeze({\n uri: r.uri,\n ...(r.name === undefined ? {} : { name: r.name }),\n ...(r.description === undefined ? {} : { description: r.description }),\n ...(r.mimeType === undefined ? {} : { mimeType: r.mimeType }),\n }),\n ),\n );\n }\n\n async function listPrompts(opts?: {\n signal?: AbortSignal;\n }): Promise<ReadonlyArray<MCPPromptDefinition>> {\n const requestOptions = opts?.signal === undefined ? {} : { signal: opts.signal };\n type SdkPrompt = {\n name: string;\n description?: string;\n arguments?: ReadonlyArray<{ name: string; description?: string; required?: boolean }>;\n };\n const items: SdkPrompt[] = [];\n let cursor: string | undefined;\n for (let page = 0; ; page++) {\n if (page >= MAX_LIST_PAGES) {\n warnListPageCap('prompts', items.length);\n break;\n }\n let result: Awaited<ReturnType<typeof sdkClient.listPrompts>>;\n try {\n result = await sdkClient.listPrompts(\n cursor === undefined ? {} : { cursor },\n requestOptions,\n );\n } catch (cause) {\n throw mapSdkError(cause, {});\n }\n items.push(...((result.prompts ?? []) as ReadonlyArray<SdkPrompt>));\n cursor = (result as { nextCursor?: string }).nextCursor;\n if (cursor === undefined) break;\n }\n return Object.freeze(\n items.map((p) =>\n Object.freeze({\n name: p.name,\n ...(p.description === undefined ? {} : { description: p.description }),\n ...(p.arguments === undefined\n ? {}\n : {\n arguments: Object.freeze(\n p.arguments.map((a) =>\n Object.freeze({\n name: a.name,\n ...(a.description === undefined ? {} : { description: a.description }),\n ...(a.required === undefined ? {} : { required: a.required }),\n }),\n ),\n ),\n }),\n }),\n ),\n );\n }\n\n async function callTool(\n name: string,\n args: unknown,\n opts?: { signal?: AbortSignal; timeoutMs?: number },\n ): Promise<MCPCallToolResult> {\n // MC-3: `timeoutMs` maps onto the SDK's RequestOptions - both the\n // per-attempt and the total ceiling, so progress notifications\n // cannot extend past the caller's budget.\n const requestOptions = {\n ...(opts?.signal === undefined ? {} : { signal: opts.signal }),\n ...(opts?.timeoutMs === undefined\n ? {}\n : { timeout: opts.timeoutMs, maxTotalTimeout: opts.timeoutMs }),\n };\n incrementCounter('mcp.call.invoked.total', { server: serverIdentity.id, tool: name });\n let result: Awaited<ReturnType<typeof sdkClient.callTool>>;\n try {\n result = await sdkClient.callTool(\n { name, arguments: args as Record<string, unknown> },\n undefined,\n requestOptions,\n );\n } catch (cause) {\n const mapped = mapSdkError(cause, { tool: name });\n if (mapped instanceof MCPCancelledError) {\n incrementCounter('mcp.call.cancelled.total', { server: serverIdentity.id, tool: name });\n } else {\n incrementCounter('mcp.call.failed.total', { server: serverIdentity.id, tool: name });\n }\n throw mapped;\n }\n const content = (result.content ?? []) as ReadonlyArray<MCPContentPart>;\n return Object.freeze({\n content: Object.freeze([...content]),\n ...(result.structuredContent === undefined\n ? {}\n : { structuredContent: result.structuredContent as Readonly<Record<string, unknown>> }),\n ...(result.isError === undefined ? {} : { isError: Boolean(result.isError) }),\n });\n }\n\n async function readResourceContents(\n uri: string,\n opts?: { signal?: AbortSignal },\n ): Promise<ReadonlyArray<MCPResourceContent>> {\n const requestOptions = opts?.signal === undefined ? {} : { signal: opts.signal };\n let result: Awaited<ReturnType<typeof sdkClient.readResource>>;\n try {\n result = await sdkClient.readResource({ uri }, requestOptions);\n } catch (cause) {\n throw mapSdkError(cause, {});\n }\n const contents = (result.contents ?? []) as ReadonlyArray<MCPResourceContent>;\n if (contents.length === 0) {\n throw new MCPProtocolError(`MCP server returned no contents for resource '${uri}'.`, {\n metadata: { server: serverIdentity.id },\n });\n }\n return Object.freeze(contents.map((c) => Object.freeze(c)));\n }\n\n async function readResource(\n uri: string,\n opts?: { signal?: AbortSignal },\n ): Promise<MCPResourceContent> {\n const contents = await readResourceContents(uri, opts);\n // mcp-skills-11: the `resources/read` result is an ARRAY precisely\n // because one URI can yield multiple contents. The single-content\n // convenience keeps its shape, but a silent truncation is now a\n // visible one.\n if (contents.length > 1) {\n incrementCounter('mcp.resource.multi-content-truncated.total', {\n server: serverIdentity.id,\n });\n options.logger?.(\n 'warn',\n `resource '${uri}' returned ${contents.length} content items; readResource() surfaces only the first - use readResourceContents() for all of them`,\n { server: serverIdentity.id },\n );\n }\n return contents[0] as MCPResourceContent;\n }\n\n async function getPrompt(\n name: string,\n args?: unknown,\n opts?: { signal?: AbortSignal },\n ): Promise<{ readonly messages: ReadonlyArray<MCPPromptMessage> }> {\n const requestOptions = opts?.signal === undefined ? {} : { signal: opts.signal };\n let result: Awaited<ReturnType<typeof sdkClient.getPrompt>>;\n try {\n result = await sdkClient.getPrompt(\n {\n name,\n ...(args === undefined ? {} : { arguments: args as Record<string, string> }),\n },\n requestOptions,\n );\n } catch (cause) {\n throw mapSdkError(cause, {});\n }\n const messages = (result.messages ?? []).map(\n (m): MCPPromptMessage =>\n Object.freeze({\n role: m.role,\n content: m.content as MCPContentPart,\n }),\n );\n return Object.freeze({ messages: Object.freeze(messages) });\n }\n\n let lastToolFingerprints: ReadonlyMap<string, string> | undefined;\n\n async function toTools(toolsOpts?: MCPToToolsOptions): Promise<ReadonlyArray<Tool>> {\n const catalogue = await listTools();\n const adapted = adaptMCPTools({\n client: clientApi,\n serverIdentity,\n catalogue,\n ...(toolsOpts === undefined ? {} : { options: toolsOpts }),\n ...(options.logger === undefined ? {} : { logger: options.logger }),\n });\n // MC-6: cross-snapshot drift - a definition changing behind an\n // already-seen name within this client's lifetime is audited.\n if (lastToolFingerprints !== undefined) {\n for (const [name, hash] of adapted.fingerprints) {\n const previous = lastToolFingerprints.get(name);\n if (previous !== undefined && previous !== hash) {\n incrementCounter('mcp.tools.changed.total', {\n server: serverIdentity.id,\n tool: name,\n });\n options.logger?.('warn', 'mcp.tools.changed: definition drifted between snapshots', {\n server: serverIdentity.id,\n tool: name,\n previous,\n current: hash,\n });\n }\n }\n }\n lastToolFingerprints = adapted.fingerprints;\n // MC-6: operator pins from a previously approved snapshot - the\n // rug-pull (approve-then-swap across restarts) posture. C6 extends it\n // with durable trust-on-first-use via `pinStore`: the first snapshot\n // is RECORDED, later snapshots are COMPARED, and a store-backed\n // mismatch defaults to 'reject' (a persisted first approval is an\n // explicit trust decision).\n let pins = toolsOpts?.pinnedFingerprints;\n let mismatchAction = toolsOpts?.onPinMismatch ?? 'warn';\n const pinStore = toolsOpts?.pinStore;\n if (pins === undefined && pinStore !== undefined) {\n const stored = await pinStore.get(serverIdentity.id);\n if (stored === undefined) {\n const recorded: Record<string, string> = {};\n for (const [name, hash] of adapted.fingerprints) recorded[name] = hash;\n await pinStore.set(serverIdentity.id, recorded);\n incrementCounter('mcp.tools.pins-recorded.total', { server: serverIdentity.id });\n options.logger?.('info', 'mcp.tools.pins-recorded: first-use fingerprints stored', {\n server: serverIdentity.id,\n tools: Object.keys(recorded).length,\n });\n } else {\n pins = stored;\n mismatchAction = toolsOpts?.onPinMismatch ?? 'reject';\n }\n }\n if (pins !== undefined) {\n for (const [name, pinned] of Object.entries(pins)) {\n const current = adapted.fingerprints.get(name);\n if (current !== undefined && current !== pinned) {\n if (mismatchAction === 'reject') {\n throw new MCPToolPinningError(\n `MCP tool '${name}' no longer matches its pinned definition fingerprint - the server changed the definition behind an approved name.`,\n { metadata: { server: serverIdentity.id, tool: name } },\n );\n }\n incrementCounter('mcp.tools.pin-mismatch.total', {\n server: serverIdentity.id,\n tool: name,\n });\n options.logger?.('warn', 'mcp.tools.pin-mismatch: pinned fingerprint diverged', {\n server: serverIdentity.id,\n tool: name,\n });\n }\n }\n }\n return adapted.tools;\n }\n\n async function close(): Promise<void> {\n try {\n await sdkClient.close();\n } catch (cause) {\n // Treat double-close as a no-op; surface other failures.\n if (cause instanceof Error && cause.message.toLowerCase().includes('already')) return;\n throw new MCPConnectionError('MCP transport could not be closed cleanly.', {\n metadata: { transport: options.transportConfig.kind, server: serverIdentity.id },\n cause,\n });\n }\n }\n\n const clientApi: MCPClient = Object.freeze({\n id: serverIdentity.id,\n serverInfo,\n serverIdentity,\n collisionStrategy,\n ...(options.priority === undefined ? {} : { priority: options.priority }),\n sessionIdPresent,\n resumable,\n listTools,\n listResources,\n listPrompts,\n callTool,\n readResource,\n readResourceContents,\n getPrompt,\n toTools,\n close,\n });\n return clientApi;\n}\n\n/**\n * Resolve the live {@link TransportAuthSource} for the outbound\n * `Authorization` header from the mutually-exclusive `authProvider` /\n * `bearerToken` options. `authProvider.resolveHeader()` already returns\n * the full header value (`Bearer …`); a static `bearerToken` is wrapped\n * into a constant `Bearer`-prefixed resolver. Returns `undefined` when\n * neither is supplied (no header injection).\n */\nfunction resolveTransportAuth(options: CreateMCPClientOptions): TransportAuthSource | undefined {\n const provider = options.authProvider;\n if (provider !== undefined) {\n return { resolveHeader: () => provider.resolveHeader() };\n }\n if (options.bearerToken !== undefined) {\n const token = options.bearerToken;\n const header = /^bearer\\s/i.test(token) ? token : `Bearer ${token}`;\n return { resolveHeader: () => header };\n }\n return undefined;\n}\n\nfunction mapSdkError(cause: unknown, ctx: { readonly tool?: string }): Error {\n const metadata = ctx.tool === undefined ? {} : { tool: ctx.tool };\n if (cause instanceof Error) {\n const name = cause.name;\n const message = cause.message ?? '';\n if (name === 'AbortError' || /aborted|cancell/i.test(message)) {\n return new MCPCancelledError('MCP request was cancelled.', { metadata, cause });\n }\n // MC-3: the SDK reports request-timeout as a plain McpError - map it\n // onto the advertised typed class instead of MCPProtocolError.\n if (/request timed out|timed out/i.test(message)) {\n return new MCPCallTimeoutError(\n `MCP request timed out${ctx.tool ? ` (tool: ${ctx.tool})` : ''}.`,\n {\n metadata,\n cause,\n },\n );\n }\n if (/unknown\\s+tool|tool\\s+not\\s+found|method\\s+not\\s+found/i.test(message)) {\n return new MCPToolNotFoundError(`MCP tool not found${ctx.tool ? `: ${ctx.tool}` : ''}.`, {\n metadata,\n cause,\n });\n }\n return new MCPProtocolError(message.length === 0 ? cause.toString() : message, {\n metadata,\n cause,\n });\n }\n return new MCPProtocolError(`MCP request failed: ${String(cause)}`, { metadata, cause });\n}\n\nfunction isStreamableHttp(transport: unknown): transport is StreamableHTTPClientTransport {\n return (\n typeof transport === 'object' &&\n transport !== null &&\n transport.constructor !== undefined &&\n transport.constructor.name === 'StreamableHTTPClientTransport'\n );\n}\n\nexport type { ServerIdentity };\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["DEFAULT_CLIENT_VERSION: string","pkg.version","serverIdentity: typeof derivedIdentity","collisionStrategy: CollisionStrategy","tools: SdkTool[]","cursor: string | undefined","result: Awaited<ReturnType<typeof sdkClient.listTools>>","items: SdkResource[]","result: Awaited<ReturnType<typeof sdkClient.listResources>>","items: SdkPrompt[]","result: Awaited<ReturnType<typeof sdkClient.listPrompts>>","result: Awaited<ReturnType<typeof sdkClient.callTool>>","result: Awaited<ReturnType<typeof sdkClient.readResource>>","result: Awaited<ReturnType<typeof sdkClient.getPrompt>>","toolFingerprintRef: ToolFingerprintRef","clientApi: MCPClient"],"sources":["../../src/client/client.ts"],"sourcesContent":["import pkg from '../../package.json' with { type: 'json' };\n/**\n * `createMCPClient(...)` - the entry point for opening a typed MCP\n * client connection.\n *\n * The returned {@link MCPClient}:\n *\n * - Wraps the `@modelcontextprotocol/sdk` `Client` instance and the\n * selected SDK transport.\n * - Exposes `listTools` / `listResources` / `listPrompts` /\n * `callTool` / `readResource` / `getPrompt` / `close` plus the\n * strategy-aware `toTools(...)` adapter.\n * - Emits one INFO-log per server when the connected transport is\n * the deprecated SSE transport, on the resolved resumable\n * capability, and when the structured-content + outputSchema\n * round-trip first succeeds.\n *\n * @packageDocumentation\n */\n\nimport type { Tool } from '@graphorin/core';\nimport { incrementCounter } from '@graphorin/tools/audit';\nimport type { CollisionStrategy } from '@graphorin/tools/registry';\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport type { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport {\n ErrorCode,\n McpError,\n ToolListChangedNotificationSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport {\n MCPCallTimeoutError,\n MCPCancelledError,\n MCPConnectionError,\n MCPInvalidConfigError,\n MCPProtocolError,\n MCPToolNotFoundError,\n} from '../errors/index.js';\nimport { deriveServerIdentity } from '../helpers/identity.js';\nimport { validateMCPServerConfig } from '../helpers/validate-config.js';\nimport type { ServerIdentity } from '../transport/types.js';\nimport { computeClientCapabilities, registerClientRequestHandlers } from './client-handlers.js';\nimport { runToTools, type ToolFingerprintRef } from './to-tools-run.js';\nimport { buildTransport, type TransportAuthSource } from './transport-factory.js';\nimport type {\n CreateMCPClientOptions,\n MCPCallToolResult,\n MCPClient,\n MCPContentPart,\n MCPPromptDefinition,\n MCPPromptMessage,\n MCPResourceContent,\n MCPResourceDefinition,\n MCPToolDefinition,\n MCPToToolsOptions,\n} from './types.js';\n\nconst DEFAULT_CLIENT_NAME = 'graphorin-mcp-client';\nconst DEFAULT_CLIENT_VERSION: string = pkg.version;\n\n/**\n * Process-scoped dedup flag for the deprecated SSE transport WARN.\n * Once set, subsequent {@link createMCPClient} calls with the SSE\n * transport do not re-emit the warning. Tests reset via\n * {@link _resetSseWarnDedupForTesting}.\n */\nlet sseWarnEmittedThisProcess = false;\n\n/**\n * Reset the SSE WARN dedup flag. Used by tests.\n *\n * @experimental\n */\nexport function _resetSseWarnDedupForTesting(): void {\n sseWarnEmittedThisProcess = false;\n}\n\n/**\n * Open a typed MCP client connection.\n *\n * @stable\n */\nexport async function createMCPClient(options: CreateMCPClientOptions): Promise<MCPClient> {\n validateMCPServerConfig({ transport: options.transport });\n\n // Mutually exclusive (documented on `CreateMCPClientOptions`): a live\n // OAuth provider and a static pre-shared token cannot both drive the\n // outbound `Authorization` header.\n if (options.authProvider !== undefined && options.bearerToken !== undefined) {\n throw new MCPInvalidConfigError(\n '`authProvider` and `bearerToken` are mutually exclusive; supply at most one.',\n { metadata: { transport: options.transport.kind } },\n );\n }\n const auth = resolveTransportAuth(options);\n if (auth !== undefined && options.transport.kind === 'stdio') {\n throw new MCPInvalidConfigError(\n 'authProvider / bearerToken require an HTTP transport (streamable-http or sse); the stdio transport carries no Authorization header.',\n { metadata: { transport: 'stdio' } },\n );\n }\n\n if (\n options.transport.kind === 'sse' &&\n options.suppressDeprecatedTransportWarning !== true &&\n !sseWarnEmittedThisProcess\n ) {\n sseWarnEmittedThisProcess = true;\n if (options.logger !== undefined) {\n options.logger(\n 'warn',\n 'MCP SSE transport is deprecated; migrate the server to the streamable-http transport when possible.',\n );\n }\n incrementCounter('mcp.transport.deprecated.warn.total', { transport: 'sse' });\n }\n\n const built = buildTransport(options.transport, auth === undefined ? undefined : { auth });\n return createMCPClientFromSdkTransport({\n transport: built.transport,\n transportConfig: options.transport,\n ...(options.collisionStrategy === undefined\n ? {}\n : { collisionStrategy: options.collisionStrategy }),\n ...(options.priority === undefined ? {} : { priority: options.priority }),\n ...(options.serverInfoName === undefined ? {} : { serverInfoName: options.serverInfoName }),\n ...(options.logger === undefined ? {} : { logger: options.logger }),\n ...(options.clientName === undefined ? {} : { clientName: options.clientName }),\n ...(options.clientVersion === undefined ? {} : { clientVersion: options.clientVersion }),\n ...(options.elicitation === undefined ? {} : { elicitation: options.elicitation }),\n ...(options.sampling === undefined ? {} : { sampling: options.sampling }),\n ...(options.onTransportClose === undefined\n ? {}\n : { onTransportClose: options.onTransportClose }),\n ...(options.onTransportError === undefined\n ? {}\n : { onTransportError: options.onTransportError }),\n });\n}\n\n/**\n * Internal factory that takes a pre-built SDK `Transport`. Exposed\n * for the test seam - production code uses {@link createMCPClient}.\n *\n * @internal\n */\nexport interface CreateMCPClientFromSdkTransportOptions {\n readonly transport: Transport;\n readonly transportConfig: import('../transport/types.js').MCPTransportConfig;\n readonly collisionStrategy?: CollisionStrategy;\n readonly priority?: number;\n readonly serverInfoName?: string;\n readonly logger?: CreateMCPClientOptions['logger'];\n readonly clientName?: string;\n readonly clientVersion?: string;\n readonly elicitation?: CreateMCPClientOptions['elicitation'];\n readonly sampling?: CreateMCPClientOptions['sampling'];\n readonly onTransportClose?: CreateMCPClientOptions['onTransportClose'];\n readonly onTransportError?: CreateMCPClientOptions['onTransportError'];\n}\n\n/**\n * Build an {@link MCPClient} from a pre-built SDK transport. The\n * production {@link createMCPClient} delegates here after building\n * the SDK transport from a {@link MCPTransportConfig}.\n *\n * @internal\n */\nexport async function createMCPClientFromSdkTransport(\n options: CreateMCPClientFromSdkTransportOptions,\n): Promise<MCPClient> {\n const sdkClient = new Client({\n name: options.clientName ?? DEFAULT_CLIENT_NAME,\n version: options.clientVersion ?? DEFAULT_CLIENT_VERSION,\n });\n\n // WI-13 (P2-2): advertise + register the server-initiated request\n // handlers (elicitation / sampling) before connecting, so they are in\n // place when the session starts. Both are gated - capabilities are\n // advertised only when the operator supplied the matching callback.\n const clientCapabilities = computeClientCapabilities({\n elicitation: options.elicitation,\n sampling: options.sampling,\n });\n if (clientCapabilities !== undefined) {\n sdkClient.registerCapabilities(clientCapabilities);\n }\n const serverIdRef = { current: 'unknown' };\n registerClientRequestHandlers(sdkClient, {\n ...(options.elicitation === undefined ? {} : { elicitation: options.elicitation }),\n ...(options.sampling === undefined ? {} : { sampling: options.sampling }),\n serverIdRef,\n });\n // MC-6: surface server-side catalogue churn - at minimum an audit\n // counter + log line; operators re-run toTools() to refresh and\n // re-sanitize the catalogue (which also re-runs the drift diff).\n sdkClient.setNotificationHandler(ToolListChangedNotificationSchema, async () => {\n incrementCounter('mcp.tools.list-changed.total', {\n server: serverIdRef.current ?? 'unknown',\n });\n options.logger?.(\n 'warn',\n 'mcp.tools.list_changed received - re-run toTools() to refresh + re-sanitize the catalogue',\n { server: serverIdRef.current },\n );\n });\n\n try {\n await sdkClient.connect(options.transport);\n } catch (cause) {\n throw new MCPConnectionError(\n `MCP transport could not be established: ${(cause as Error).message ?? String(cause)}`,\n {\n metadata: { transport: options.transportConfig.kind },\n cause,\n },\n );\n }\n\n const serverInfo = sdkClient.getServerVersion() ?? {\n name: 'unknown',\n version: '0.0.0',\n };\n // W-016: the identity derives from the TRANSPORT config (operator-\n // controlled) plus the explicit operator `serverInfoName` override -\n // never from the name the remote server self-reports on initialize.\n // TOFU pins, handle scoping and taint labels all key off this id; a\n // server-controlled id defeats every one of them (rename = fresh pin,\n // claim a trusted name = inherit its scope). The self-reported name\n // survives for display only.\n const derivedIdentity = deriveServerIdentity(options.transportConfig, options.serverInfoName);\n const serverIdentity: typeof derivedIdentity = Object.freeze({\n ...derivedIdentity,\n ...(typeof serverInfo.name === 'string' && serverInfo.name.length > 0\n ? { reportedServerName: serverInfo.name }\n : {}),\n });\n // Backfill the server id so the client-side request handlers\n // (registered before connect) label their counters with it.\n serverIdRef.current = serverIdentity.id;\n // mcp-skills-10: surface transport lifecycle. Without these a dead\n // stdio child / dropped HTTP session is observable only as\n // MCPProtocolErrors on subsequent calls. The SDK Protocol base\n // exposes assignable onclose/onerror callbacks (the transport's own\n // handlers are managed by the SDK - never overwrite those).\n sdkClient.onclose = () => {\n incrementCounter('mcp.transport.closed.total', { server: serverIdentity.id });\n options.logger?.('warn', 'MCP transport closed - rebuild the client to reconnect', {\n server: serverIdentity.id,\n });\n options.onTransportClose?.({ server: serverIdentity.id });\n };\n sdkClient.onerror = (error: Error) => {\n incrementCounter('mcp.transport.error.total', { server: serverIdentity.id });\n options.onTransportError?.(error, { server: serverIdentity.id });\n };\n const collisionStrategy: CollisionStrategy = options.collisionStrategy ?? 'auto-prefix';\n\n // MC-1: the client-side eventStore option was removed - per the\n // Streamable HTTP spec event replay is the SERVER's responsibility,\n // and the SDK transport auto-reconnects with Last-Event-ID on its\n // own. Warn legacy callers instead of silently ignoring the option.\n if ((options as { readonly eventStore?: unknown }).eventStore !== undefined) {\n options.logger?.(\n 'warn',\n \"the client 'eventStore' option was removed (MC-1): event replay is a server responsibility; the SDK transport auto-reconnects with Last-Event-ID. Remove the option.\",\n { server: serverIdentity.id },\n );\n }\n // MC-9: a session id means stateful routing, NOT a replay guarantee.\n const sessionIdPresent =\n isStreamableHttp(options.transport) &&\n (options.transport as StreamableHTTPClientTransport).sessionId !== undefined;\n const resumable = sessionIdPresent;\n if (options.logger !== undefined) {\n options.logger('info', 'mcp.session.session-id.resolved', {\n server: serverIdentity.id,\n value: sessionIdPresent,\n source: sessionIdPresent ? 'session-id-present' : 'transport-default',\n });\n }\n let structuredContentSeenLogged = false;\n\n /**\n * mcp-skills-02: MCP list operations are cursor-paginated (protocol\n * 2024-11-05+) and the SDK does NOT auto-paginate - a single call\n * returns page 1 only. Ignoring `nextCursor` silently truncated the\n * catalogue: tools beyond the first page never reached `toTools()`,\n * defer-loading thresholds counted a partial catalogue, pin\n * fingerprints covered a partial catalogue, and resources/prompts were\n * under-listed. Every list surface now drains the cursor chain, with a\n * defensive page cap against buggy/adversarial servers that never\n * terminate it.\n */\n const MAX_LIST_PAGES = 100;\n\n function warnListPageCap(what: 'tools' | 'resources' | 'prompts', collected: number): void {\n options.logger?.(\n 'warn',\n `mcp.list.${what}.page-cap-reached: server kept returning nextCursor after ${MAX_LIST_PAGES} pages; the ${what} catalogue is truncated at ${collected} entries.`,\n { server: serverIdentity.id },\n );\n }\n\n async function listTools(opts?: {\n signal?: AbortSignal;\n }): Promise<ReadonlyArray<MCPToolDefinition>> {\n const requestOptions = opts?.signal === undefined ? {} : { signal: opts.signal };\n type SdkTool = {\n name: string;\n description?: string;\n inputSchema: Readonly<Record<string, unknown>>;\n outputSchema?: Readonly<Record<string, unknown>>;\n title?: string;\n };\n const tools: SdkTool[] = [];\n let cursor: string | undefined;\n for (let page = 0; ; page++) {\n if (page >= MAX_LIST_PAGES) {\n warnListPageCap('tools', tools.length);\n break;\n }\n let result: Awaited<ReturnType<typeof sdkClient.listTools>>;\n try {\n result = await sdkClient.listTools(cursor === undefined ? {} : { cursor }, requestOptions);\n } catch (cause) {\n throw mapSdkError(cause, { aborted: opts?.signal?.aborted === true });\n }\n tools.push(...((result.tools ?? []) as ReadonlyArray<SdkTool>));\n cursor = (result as { nextCursor?: string }).nextCursor;\n if (cursor === undefined) break;\n }\n if (!structuredContentSeenLogged && tools.some((t) => t.outputSchema !== undefined)) {\n structuredContentSeenLogged = true;\n if (options.logger !== undefined) {\n options.logger('info', 'mcp.server.structured-content.detected', {\n server: serverIdentity.id,\n });\n }\n }\n return Object.freeze(\n tools.map((t) =>\n Object.freeze({\n name: t.name,\n description: t.description ?? '',\n inputSchema: t.inputSchema,\n ...(t.outputSchema === undefined ? {} : { outputSchema: t.outputSchema }),\n ...(t.title === undefined ? {} : { title: t.title }),\n }),\n ),\n );\n }\n\n async function listResources(opts?: {\n signal?: AbortSignal;\n }): Promise<ReadonlyArray<MCPResourceDefinition>> {\n const requestOptions = opts?.signal === undefined ? {} : { signal: opts.signal };\n type SdkResource = {\n uri: string;\n name?: string;\n description?: string;\n mimeType?: string;\n };\n const items: SdkResource[] = [];\n let cursor: string | undefined;\n for (let page = 0; ; page++) {\n if (page >= MAX_LIST_PAGES) {\n warnListPageCap('resources', items.length);\n break;\n }\n let result: Awaited<ReturnType<typeof sdkClient.listResources>>;\n try {\n result = await sdkClient.listResources(\n cursor === undefined ? {} : { cursor },\n requestOptions,\n );\n } catch (cause) {\n throw mapSdkError(cause, { aborted: opts?.signal?.aborted === true });\n }\n items.push(...((result.resources ?? []) as ReadonlyArray<SdkResource>));\n cursor = (result as { nextCursor?: string }).nextCursor;\n if (cursor === undefined) break;\n }\n return Object.freeze(\n items.map((r) =>\n Object.freeze({\n uri: r.uri,\n ...(r.name === undefined ? {} : { name: r.name }),\n ...(r.description === undefined ? {} : { description: r.description }),\n ...(r.mimeType === undefined ? {} : { mimeType: r.mimeType }),\n }),\n ),\n );\n }\n\n async function listPrompts(opts?: {\n signal?: AbortSignal;\n }): Promise<ReadonlyArray<MCPPromptDefinition>> {\n const requestOptions = opts?.signal === undefined ? {} : { signal: opts.signal };\n type SdkPrompt = {\n name: string;\n description?: string;\n arguments?: ReadonlyArray<{ name: string; description?: string; required?: boolean }>;\n };\n const items: SdkPrompt[] = [];\n let cursor: string | undefined;\n for (let page = 0; ; page++) {\n if (page >= MAX_LIST_PAGES) {\n warnListPageCap('prompts', items.length);\n break;\n }\n let result: Awaited<ReturnType<typeof sdkClient.listPrompts>>;\n try {\n result = await sdkClient.listPrompts(\n cursor === undefined ? {} : { cursor },\n requestOptions,\n );\n } catch (cause) {\n throw mapSdkError(cause, { aborted: opts?.signal?.aborted === true });\n }\n items.push(...((result.prompts ?? []) as ReadonlyArray<SdkPrompt>));\n cursor = (result as { nextCursor?: string }).nextCursor;\n if (cursor === undefined) break;\n }\n return Object.freeze(\n items.map((p) =>\n Object.freeze({\n name: p.name,\n ...(p.description === undefined ? {} : { description: p.description }),\n ...(p.arguments === undefined\n ? {}\n : {\n arguments: Object.freeze(\n p.arguments.map((a) =>\n Object.freeze({\n name: a.name,\n ...(a.description === undefined ? {} : { description: a.description }),\n ...(a.required === undefined ? {} : { required: a.required }),\n }),\n ),\n ),\n }),\n }),\n ),\n );\n }\n\n async function callTool(\n name: string,\n args: unknown,\n opts?: { signal?: AbortSignal; timeoutMs?: number },\n ): Promise<MCPCallToolResult> {\n // MC-3: `timeoutMs` maps onto the SDK's RequestOptions - both the\n // per-attempt and the total ceiling, so progress notifications\n // cannot extend past the caller's budget.\n const requestOptions = {\n ...(opts?.signal === undefined ? {} : { signal: opts.signal }),\n ...(opts?.timeoutMs === undefined\n ? {}\n : { timeout: opts.timeoutMs, maxTotalTimeout: opts.timeoutMs }),\n };\n incrementCounter('mcp.call.invoked.total', { server: serverIdentity.id, tool: name });\n let result: Awaited<ReturnType<typeof sdkClient.callTool>>;\n try {\n result = await sdkClient.callTool(\n { name, arguments: args as Record<string, unknown> },\n undefined,\n requestOptions,\n );\n } catch (cause) {\n const mapped = mapSdkError(cause, {\n tool: name,\n aborted: opts?.signal?.aborted === true,\n });\n if (mapped instanceof MCPCancelledError) {\n incrementCounter('mcp.call.cancelled.total', { server: serverIdentity.id, tool: name });\n } else {\n incrementCounter('mcp.call.failed.total', { server: serverIdentity.id, tool: name });\n }\n throw mapped;\n }\n const content = (result.content ?? []) as ReadonlyArray<MCPContentPart>;\n return Object.freeze({\n content: Object.freeze([...content]),\n ...(result.structuredContent === undefined\n ? {}\n : { structuredContent: result.structuredContent as Readonly<Record<string, unknown>> }),\n ...(result.isError === undefined ? {} : { isError: Boolean(result.isError) }),\n });\n }\n\n async function readResourceContents(\n uri: string,\n opts?: { signal?: AbortSignal },\n ): Promise<ReadonlyArray<MCPResourceContent>> {\n const requestOptions = opts?.signal === undefined ? {} : { signal: opts.signal };\n let result: Awaited<ReturnType<typeof sdkClient.readResource>>;\n try {\n result = await sdkClient.readResource({ uri }, requestOptions);\n } catch (cause) {\n throw mapSdkError(cause, { aborted: opts?.signal?.aborted === true });\n }\n const contents = (result.contents ?? []) as ReadonlyArray<MCPResourceContent>;\n if (contents.length === 0) {\n throw new MCPProtocolError(`MCP server returned no contents for resource '${uri}'.`, {\n metadata: { server: serverIdentity.id },\n });\n }\n return Object.freeze(contents.map((c) => Object.freeze(c)));\n }\n\n async function readResource(\n uri: string,\n opts?: { signal?: AbortSignal },\n ): Promise<MCPResourceContent> {\n const contents = await readResourceContents(uri, opts);\n // mcp-skills-11: the `resources/read` result is an ARRAY precisely\n // because one URI can yield multiple contents. The single-content\n // convenience keeps its shape, but a silent truncation is now a\n // visible one.\n if (contents.length > 1) {\n incrementCounter('mcp.resource.multi-content-truncated.total', {\n server: serverIdentity.id,\n });\n options.logger?.(\n 'warn',\n `resource '${uri}' returned ${contents.length} content items; readResource() surfaces only the first - use readResourceContents() for all of them`,\n { server: serverIdentity.id },\n );\n }\n return contents[0] as MCPResourceContent;\n }\n\n async function getPrompt(\n name: string,\n args?: unknown,\n opts?: { signal?: AbortSignal },\n ): Promise<{ readonly messages: ReadonlyArray<MCPPromptMessage> }> {\n const requestOptions = opts?.signal === undefined ? {} : { signal: opts.signal };\n let result: Awaited<ReturnType<typeof sdkClient.getPrompt>>;\n try {\n result = await sdkClient.getPrompt(\n {\n name,\n ...(args === undefined ? {} : { arguments: args as Record<string, string> }),\n },\n requestOptions,\n );\n } catch (cause) {\n throw mapSdkError(cause, { aborted: opts?.signal?.aborted === true });\n }\n const messages = (result.messages ?? []).map(\n (m): MCPPromptMessage =>\n Object.freeze({\n role: m.role,\n content: m.content as MCPContentPart,\n }),\n );\n return Object.freeze({ messages: Object.freeze(messages) });\n }\n\n // W-080: cross-snapshot drift tracking spans this client's lifetime;\n // the pipeline itself lives in `runToTools` (shared with the managed\n // wrapper, which passes ITSELF as the client so adapted tools survive\n // an inner-client swap on reconnect).\n const toolFingerprintRef: ToolFingerprintRef = { current: undefined };\n\n async function toTools(toolsOpts?: MCPToToolsOptions): Promise<ReadonlyArray<Tool>> {\n return runToTools({\n client: clientApi,\n fingerprintRef: toolFingerprintRef,\n ...(options.logger === undefined ? {} : { logger: options.logger }),\n ...(toolsOpts === undefined ? {} : { toolsOpts }),\n });\n }\n\n async function close(): Promise<void> {\n try {\n await sdkClient.close();\n } catch (cause) {\n // Treat double-close as a no-op; surface other failures.\n if (cause instanceof Error && cause.message.toLowerCase().includes('already')) return;\n throw new MCPConnectionError('MCP transport could not be closed cleanly.', {\n metadata: { transport: options.transportConfig.kind, server: serverIdentity.id },\n cause,\n });\n }\n }\n\n const clientApi: MCPClient = Object.freeze({\n id: serverIdentity.id,\n serverInfo,\n serverIdentity,\n collisionStrategy,\n ...(options.priority === undefined ? {} : { priority: options.priority }),\n sessionIdPresent,\n resumable,\n listTools,\n listResources,\n listPrompts,\n callTool,\n readResource,\n readResourceContents,\n getPrompt,\n toTools,\n close,\n });\n return clientApi;\n}\n\n/**\n * Resolve the live {@link TransportAuthSource} for the outbound\n * `Authorization` header from the mutually-exclusive `authProvider` /\n * `bearerToken` options. `authProvider.resolveHeader()` already returns\n * the full header value (`Bearer …`); a static `bearerToken` is wrapped\n * into a constant `Bearer`-prefixed resolver. Returns `undefined` when\n * neither is supplied (no header injection).\n */\nfunction resolveTransportAuth(options: CreateMCPClientOptions): TransportAuthSource | undefined {\n const provider = options.authProvider;\n if (provider !== undefined) {\n return { resolveHeader: () => provider.resolveHeader() };\n }\n if (options.bearerToken !== undefined) {\n const token = options.bearerToken;\n const header = /^bearer\\s/i.test(token) ? token : `Bearer ${token}`;\n return { resolveHeader: () => header };\n }\n return undefined;\n}\n\nfunction mapSdkError(\n cause: unknown,\n ctx: { readonly tool?: string; readonly aborted?: boolean },\n): Error {\n const metadata = ctx.tool === undefined ? {} : { tool: ctx.tool };\n // W-141: cancellation is classified from OUR OWN AbortSignal state\n // (ctx.aborted), never from error text. SDK 1.29 wraps a local abort\n // as McpError(RequestTimeout, String(reason)) (protocol.js cancel()),\n // so neither the code nor the message distinguishes it - but the\n // caller's signal does, and it cannot be forged by a server.\n if (ctx.aborted === true) {\n return new MCPCancelledError('MCP request was cancelled.', { metadata, cause });\n }\n // Classify McpError by JSON-RPC CODE. The message is server-\n // controlled text: matching it would let a malicious server forge\n // the typed timeout/cancelled classes (and the operator counters\n // keyed on them) by phrasing an ordinary error as 'request timed\n // out'. Codes are the protocol's typed channel; the SDK raises its\n // own client-side timeout as -32001 (RequestTimeout).\n if (cause instanceof McpError) {\n if (cause.code === ErrorCode.RequestTimeout) {\n return new MCPCallTimeoutError(\n `MCP request timed out${ctx.tool ? ` (tool: ${ctx.tool})` : ''}.`,\n { metadata, cause },\n );\n }\n if (\n cause.code === ErrorCode.MethodNotFound ||\n // Tool-not-found stays message-matched as a fallback: real\n // servers signal an unknown tool via InvalidParams/InternalError\n // text at least as often as via MethodNotFound. This class is\n // benign to forge - it feeds no cancelled/timeout counter and\n // triggers no retry semantics, so DX wins over strictness here.\n /unknown\\s+tool|tool\\s+not\\s+found|method\\s+not\\s+found/i.test(cause.message)\n ) {\n return new MCPToolNotFoundError(`MCP tool not found${ctx.tool ? `: ${ctx.tool}` : ''}.`, {\n metadata,\n cause,\n });\n }\n return new MCPProtocolError(cause.message.length === 0 ? cause.toString() : cause.message, {\n metadata,\n cause,\n });\n }\n if (cause instanceof Error) {\n const name = cause.name;\n const message = cause.message ?? '';\n // Local cancellation can also surface as an AbortError (transport\n // paths); the name check is trustworthy for plain errors.\n if (name === 'AbortError') {\n return new MCPCancelledError('MCP request was cancelled.', { metadata, cause });\n }\n // Last-resort message fallbacks for PLAIN Errors thrown by\n // transports or SDK paths outside JSON-RPC (no code to go by).\n // Server-authored error text cannot reach this branch: RPC-level\n // failures arrive as McpError and are classified above.\n if (/aborted|cancell/i.test(message)) {\n return new MCPCancelledError('MCP request was cancelled.', { metadata, cause });\n }\n if (/request timed out|timed out/i.test(message)) {\n return new MCPCallTimeoutError(\n `MCP request timed out${ctx.tool ? ` (tool: ${ctx.tool})` : ''}.`,\n {\n metadata,\n cause,\n },\n );\n }\n if (/unknown\\s+tool|tool\\s+not\\s+found|method\\s+not\\s+found/i.test(message)) {\n return new MCPToolNotFoundError(`MCP tool not found${ctx.tool ? `: ${ctx.tool}` : ''}.`, {\n metadata,\n cause,\n });\n }\n return new MCPProtocolError(message.length === 0 ? cause.toString() : message, {\n metadata,\n cause,\n });\n }\n return new MCPProtocolError(`MCP request failed: ${String(cause)}`, { metadata, cause });\n}\n\nfunction isStreamableHttp(transport: unknown): transport is StreamableHTTPClientTransport {\n return (\n typeof transport === 'object' &&\n transport !== null &&\n transport.constructor !== undefined &&\n transport.constructor.name === 'StreamableHTTPClientTransport'\n );\n}\n\nexport type { ServerIdentity };\n"],"mappings":"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"}
@@ -64,7 +64,107 @@ function sanitizeDescription(args) {
64
64
  });
65
65
  return outcome.body;
66
66
  }
67
+ const ANNOTATION_STRING_KEYS = new Set([
68
+ "description",
69
+ "title",
70
+ "$comment"
71
+ ]);
72
+ const SCHEMA_VALUE_KEYS = new Set([
73
+ "items",
74
+ "additionalProperties",
75
+ "not",
76
+ "if",
77
+ "then",
78
+ "else",
79
+ "contains",
80
+ "propertyNames"
81
+ ]);
82
+ const SCHEMA_LIST_KEYS = new Set([
83
+ "oneOf",
84
+ "anyOf",
85
+ "allOf",
86
+ "prefixItems"
87
+ ]);
88
+ const SCHEMA_RECORD_KEYS = new Set([
89
+ "properties",
90
+ "$defs",
91
+ "definitions",
92
+ "patternProperties",
93
+ "dependentSchemas"
94
+ ]);
95
+ /**
96
+ * Strip imperative payloads from the ANNOTATION strings of an MCP tool
97
+ * JSON Schema (`description` / `title` / `$comment` / string
98
+ * `examples`) at any nesting depth, before the schema reaches the
99
+ * provider wire and the `tool_search` projection (W-018, the Invariant
100
+ * Labs tool-poisoning vector `properties.query.description`).
101
+ *
102
+ * Design choices:
103
+ *
104
+ * - Strip, never wrap: the result must remain a valid JSON Schema
105
+ * document; an `<<<untrusted_content>>>` envelope would break the
106
+ * structure.
107
+ * - Returns a recursively cloned COPY and never mutates the input.
108
+ * The caller keeps hashing the RAW definition
109
+ * (`computeToolDefinitionHash`), so existing TOFU pins stay valid
110
+ * and drift detection still sees the original bytes - two
111
+ * differently-poisoned schemas must not collapse into one redacted
112
+ * hash.
113
+ * - Semantic keywords and unknown vocabulary are cloned byte-identical
114
+ * (annotation keys are a whitelist, not a blacklist), so input
115
+ * validation behaves exactly as with the raw schema.
116
+ * - `'pass-through'` returns the input as-is (operator override, same
117
+ * contract as {@link sanitizeDescription}).
118
+ */
119
+ function sanitizeSchemaAnnotations(args) {
120
+ if (args.inboundSanitization === "pass-through") return {
121
+ schema: args.schema,
122
+ patternsHit: Object.freeze([])
123
+ };
124
+ const hits = /* @__PURE__ */ new Set();
125
+ const sanitizeString = (body) => {
126
+ const outcome = applyInboundSanitization({
127
+ body,
128
+ policy: "detect-and-strip",
129
+ trustClass: "mcp-derived",
130
+ toolName: args.toolName,
131
+ contentOrigin: `mcp:tool-schema:${args.serverIdentity.id}`,
132
+ failClosed: false
133
+ });
134
+ for (const hit of outcome.patternsHit) hits.add(hit);
135
+ return outcome.body;
136
+ };
137
+ const schema = walkSchema(args.schema, sanitizeString);
138
+ if (hits.size > 0) incrementCounter("mcp.tool-schema.injection-flagged.total", {
139
+ server: args.serverIdentity.id,
140
+ tool: args.toolName
141
+ });
142
+ return {
143
+ schema,
144
+ patternsHit: Object.freeze([...hits])
145
+ };
146
+ }
147
+ function walkSchema(node, sanitizeString) {
148
+ if (node === null || typeof node !== "object") return node;
149
+ if (Array.isArray(node)) return node.map((el) => walkSchema(el, sanitizeString));
150
+ const out = {};
151
+ for (const [key, value] of Object.entries(node)) if (ANNOTATION_STRING_KEYS.has(key) && typeof value === "string") out[key] = sanitizeString(value);
152
+ else if (key === "examples" && Array.isArray(value)) out[key] = value.map((el) => typeof el === "string" ? sanitizeString(el) : cloneJson(el));
153
+ else if (SCHEMA_VALUE_KEYS.has(key)) out[key] = walkSchema(value, sanitizeString);
154
+ else if (SCHEMA_LIST_KEYS.has(key) && Array.isArray(value)) out[key] = value.map((el) => walkSchema(el, sanitizeString));
155
+ else if (SCHEMA_RECORD_KEYS.has(key) && typeof value === "object" && value !== null && !Array.isArray(value)) {
156
+ const record = {};
157
+ for (const [name, sub] of Object.entries(value)) record[name] = walkSchema(sub, sanitizeString);
158
+ out[key] = record;
159
+ } else out[key] = cloneJson(value);
160
+ return out;
161
+ }
162
+ /** Deep JSON clone that keeps semantic keyword values byte-identical. */
163
+ function cloneJson(value) {
164
+ if (value === null || typeof value !== "object") return value;
165
+ return structuredClone(value);
166
+ }
67
167
 
68
168
  //#endregion
69
- export { _resetInboundFiltersDedupForTesting, resolveInboundPolicy, sanitizeDescription, warnOnPassthroughOverride };
169
+ export { _resetInboundFiltersDedupForTesting, resolveInboundPolicy, sanitizeDescription, sanitizeSchemaAnnotations, warnOnPassthroughOverride };
70
170
  //# sourceMappingURL=inbound-filters.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"inbound-filters.js","names":["stripPolicy: InboundSanitizationPolicy"],"sources":["../../src/client/inbound-filters.ts"],"sourcesContent":["/**\n * Inbound prompt-injection filtering for the `toTools()` adapter\n * (extracted from `to-tools.ts` per F-MCP-001).\n *\n * Owns: the per-server inbound-sanitization default, the once-per-server\n * `pass-through` override WARN, and the tool-description strip applied at\n * registration time. The trust class is pinned to `'mcp-derived'` for\n * every produced tool so the agent runtime's per-step preamble fires\n * regardless of the body-level policy chosen here.\n *\n * @packageDocumentation\n */\n\nimport type { InboundSanitizationPolicy } from '@graphorin/core';\nimport { incrementCounter } from '@graphorin/tools/audit';\nimport { applyInboundSanitization } from '@graphorin/tools/inbound';\nimport type { ServerIdentity } from '../transport/types.js';\n\n/** Operator-supplied structured logger (mirrors the client logger shape). */\ntype AdapterLogger = (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n fields?: Record<string, unknown>,\n) => void;\n\n/**\n * Process-scoped dedup keys for the `pass-through` override WARN. The\n * spec mandates exactly-one WARN per server identity per process - the\n * Set retains the keys for the lifetime of the process. Tests reset via\n * {@link import('./to-tools.js')._resetMcpAdapterDedupForTesting}.\n */\nconst passthroughWarnSeen = new Set<string>();\n\n/**\n * Reset the inbound-filter dedup set. Used by\n * {@link import('./to-tools.js')._resetMcpAdapterDedupForTesting}.\n *\n * @experimental\n */\nexport function _resetInboundFiltersDedupForTesting(): void {\n passthroughWarnSeen.clear();\n}\n\n/**\n * Resolve the effective per-server inbound-sanitization policy. MCP\n * tools default to the strictest body-level policy.\n */\nexport function resolveInboundPolicy(\n override: InboundSanitizationPolicy | undefined,\n): InboundSanitizationPolicy {\n return override ?? 'detect-and-strip-and-wrap';\n}\n\n/**\n * WARN-once per server when the operator opts out of body-level\n * sanitization. The trust class stays `'mcp-derived'` regardless so the\n * per-step preamble in the agent runtime still fires; the WARN exists so\n * the audit log records the operator's deliberate choice.\n */\nexport function warnOnPassthroughOverride(args: {\n readonly resolvedInbound: InboundSanitizationPolicy;\n readonly serverIdentity: ServerIdentity;\n readonly logger?: AdapterLogger;\n}): void {\n if (args.resolvedInbound !== 'pass-through') return;\n if (passthroughWarnSeen.has(args.serverIdentity.id)) return;\n passthroughWarnSeen.add(args.serverIdentity.id);\n incrementCounter('mcp.inbound.sanitization.passthrough-override.warn.total', {\n server: args.serverIdentity.id,\n });\n if (args.logger !== undefined) {\n args.logger('warn', 'mcp.inbound.sanitization.passthrough-override', {\n server: args.serverIdentity.id,\n policy: 'pass-through',\n note: \"Body-level prompt-injection sanitization is disabled for this MCP server; the trust class remains 'mcp-derived' so the per-step preamble still fires. The WARN cannot be silenced (deliberate operator-friction).\",\n });\n }\n}\n\n/**\n * Strip imperative payloads from a tool description before it enters the\n * per-step tool catalogue. The description is never wrapped in the\n * `<<<untrusted_content>>>` envelope (the wrap is reserved for tool\n * result bodies emitted into the conversation history).\n */\nexport function sanitizeDescription(args: {\n readonly description: string;\n readonly inboundSanitization: InboundSanitizationPolicy;\n readonly toolName: string;\n readonly serverIdentity: ServerIdentity;\n}): string {\n const stripPolicy: InboundSanitizationPolicy =\n args.inboundSanitization === 'pass-through' ? 'pass-through' : 'detect-and-strip';\n const outcome = applyInboundSanitization({\n body: args.description,\n policy: stripPolicy,\n trustClass: 'mcp-derived',\n toolName: args.toolName,\n contentOrigin: `mcp:tool-description:${args.serverIdentity.id}`,\n failClosed: false,\n });\n // C6: tool-description poisoning is a registration-time SIGNAL, not\n // just a silent strip - count it so operators see which server ships\n // imperative-laden descriptions (Invariant Labs tool-poisoning class).\n if (outcome.patternsHit.length > 0) {\n incrementCounter('mcp.tool-description.injection-flagged.total', {\n server: args.serverIdentity.id,\n tool: args.toolName,\n });\n }\n return outcome.body;\n}\n"],"mappings":";;;;;;;;;;AA+BA,MAAM,sCAAsB,IAAI,KAAa;;;;;;;AAQ7C,SAAgB,sCAA4C;AAC1D,qBAAoB,OAAO;;;;;;AAO7B,SAAgB,qBACd,UAC2B;AAC3B,QAAO,YAAY;;;;;;;;AASrB,SAAgB,0BAA0B,MAIjC;AACP,KAAI,KAAK,oBAAoB,eAAgB;AAC7C,KAAI,oBAAoB,IAAI,KAAK,eAAe,GAAG,CAAE;AACrD,qBAAoB,IAAI,KAAK,eAAe,GAAG;AAC/C,kBAAiB,4DAA4D,EAC3E,QAAQ,KAAK,eAAe,IAC7B,CAAC;AACF,KAAI,KAAK,WAAW,OAClB,MAAK,OAAO,QAAQ,iDAAiD;EACnE,QAAQ,KAAK,eAAe;EAC5B,QAAQ;EACR,MAAM;EACP,CAAC;;;;;;;;AAUN,SAAgB,oBAAoB,MAKzB;CACT,MAAMA,cACJ,KAAK,wBAAwB,iBAAiB,iBAAiB;CACjE,MAAM,UAAU,yBAAyB;EACvC,MAAM,KAAK;EACX,QAAQ;EACR,YAAY;EACZ,UAAU,KAAK;EACf,eAAe,wBAAwB,KAAK,eAAe;EAC3D,YAAY;EACb,CAAC;AAIF,KAAI,QAAQ,YAAY,SAAS,EAC/B,kBAAiB,gDAAgD;EAC/D,QAAQ,KAAK,eAAe;EAC5B,MAAM,KAAK;EACZ,CAAC;AAEJ,QAAO,QAAQ"}
1
+ {"version":3,"file":"inbound-filters.js","names":["stripPolicy: InboundSanitizationPolicy","out: Record<string, unknown>","record: Record<string, unknown>"],"sources":["../../src/client/inbound-filters.ts"],"sourcesContent":["/**\n * Inbound prompt-injection filtering for the `toTools()` adapter\n * (extracted from `to-tools.ts` per F-MCP-001).\n *\n * Owns: the per-server inbound-sanitization default, the once-per-server\n * `pass-through` override WARN, and the tool-description strip applied at\n * registration time. The trust class is pinned to `'mcp-derived'` for\n * every produced tool so the agent runtime's per-step preamble fires\n * regardless of the body-level policy chosen here.\n *\n * @packageDocumentation\n */\n\nimport type { InboundSanitizationPolicy } from '@graphorin/core';\nimport { incrementCounter } from '@graphorin/tools/audit';\nimport { applyInboundSanitization } from '@graphorin/tools/inbound';\nimport type { JsonSchemaLike } from '../registry/json-schema.js';\nimport type { ServerIdentity } from '../transport/types.js';\n\n/** Operator-supplied structured logger (mirrors the client logger shape). */\ntype AdapterLogger = (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n fields?: Record<string, unknown>,\n) => void;\n\n/**\n * Process-scoped dedup keys for the `pass-through` override WARN. The\n * spec mandates exactly-one WARN per server identity per process - the\n * Set retains the keys for the lifetime of the process. Tests reset via\n * {@link import('./to-tools.js')._resetMcpAdapterDedupForTesting}.\n */\nconst passthroughWarnSeen = new Set<string>();\n\n/**\n * Reset the inbound-filter dedup set. Used by\n * {@link import('./to-tools.js')._resetMcpAdapterDedupForTesting}.\n *\n * @experimental\n */\nexport function _resetInboundFiltersDedupForTesting(): void {\n passthroughWarnSeen.clear();\n}\n\n/**\n * Resolve the effective per-server inbound-sanitization policy. MCP\n * tools default to the strictest body-level policy.\n */\nexport function resolveInboundPolicy(\n override: InboundSanitizationPolicy | undefined,\n): InboundSanitizationPolicy {\n return override ?? 'detect-and-strip-and-wrap';\n}\n\n/**\n * WARN-once per server when the operator opts out of body-level\n * sanitization. The trust class stays `'mcp-derived'` regardless so the\n * per-step preamble in the agent runtime still fires; the WARN exists so\n * the audit log records the operator's deliberate choice.\n */\nexport function warnOnPassthroughOverride(args: {\n readonly resolvedInbound: InboundSanitizationPolicy;\n readonly serverIdentity: ServerIdentity;\n readonly logger?: AdapterLogger;\n}): void {\n if (args.resolvedInbound !== 'pass-through') return;\n if (passthroughWarnSeen.has(args.serverIdentity.id)) return;\n passthroughWarnSeen.add(args.serverIdentity.id);\n incrementCounter('mcp.inbound.sanitization.passthrough-override.warn.total', {\n server: args.serverIdentity.id,\n });\n if (args.logger !== undefined) {\n args.logger('warn', 'mcp.inbound.sanitization.passthrough-override', {\n server: args.serverIdentity.id,\n policy: 'pass-through',\n note: \"Body-level prompt-injection sanitization is disabled for this MCP server; the trust class remains 'mcp-derived' so the per-step preamble still fires. The WARN cannot be silenced (deliberate operator-friction).\",\n });\n }\n}\n\n/**\n * Strip imperative payloads from a tool description before it enters the\n * per-step tool catalogue. The description is never wrapped in the\n * `<<<untrusted_content>>>` envelope (the wrap is reserved for tool\n * result bodies emitted into the conversation history).\n */\nexport function sanitizeDescription(args: {\n readonly description: string;\n readonly inboundSanitization: InboundSanitizationPolicy;\n readonly toolName: string;\n readonly serverIdentity: ServerIdentity;\n}): string {\n const stripPolicy: InboundSanitizationPolicy =\n args.inboundSanitization === 'pass-through' ? 'pass-through' : 'detect-and-strip';\n const outcome = applyInboundSanitization({\n body: args.description,\n policy: stripPolicy,\n trustClass: 'mcp-derived',\n toolName: args.toolName,\n contentOrigin: `mcp:tool-description:${args.serverIdentity.id}`,\n failClosed: false,\n });\n // C6: tool-description poisoning is a registration-time SIGNAL, not\n // just a silent strip - count it so operators see which server ships\n // imperative-laden descriptions (Invariant Labs tool-poisoning class).\n if (outcome.patternsHit.length > 0) {\n incrementCounter('mcp.tool-description.injection-flagged.total', {\n server: args.serverIdentity.id,\n tool: args.toolName,\n });\n }\n return outcome.body;\n}\n\n/**\n * Result of {@link sanitizeSchemaAnnotations}: the sanitized COPY of\n * the schema (the input document is never mutated) plus the pattern\n * names that fired anywhere inside it.\n */\nexport interface SanitizedSchemaResult {\n readonly schema: JsonSchemaLike;\n readonly patternsHit: ReadonlyArray<string>;\n}\n\n// JSON Schema keys whose STRING values are pure annotations shown to\n// the model - the exact hiding place of the published Invariant Labs\n// tool-poisoning attacks. Only these are sanitized; semantic keywords\n// (`enum`, `const`, `pattern`, `required`, property names) participate\n// in input validation and MUST stay byte-identical.\nconst ANNOTATION_STRING_KEYS = new Set(['description', 'title', '$comment']);\n// Keys whose value is a single sub-schema (or, for `items`, a tuple of\n// sub-schemas).\nconst SCHEMA_VALUE_KEYS = new Set([\n 'items',\n 'additionalProperties',\n 'not',\n 'if',\n 'then',\n 'else',\n 'contains',\n 'propertyNames',\n]);\n// Keys whose value is a list of sub-schemas.\nconst SCHEMA_LIST_KEYS = new Set(['oneOf', 'anyOf', 'allOf', 'prefixItems']);\n// Keys whose value is a record of named sub-schemas.\nconst SCHEMA_RECORD_KEYS = new Set([\n 'properties',\n '$defs',\n 'definitions',\n 'patternProperties',\n 'dependentSchemas',\n]);\n\n/**\n * Strip imperative payloads from the ANNOTATION strings of an MCP tool\n * JSON Schema (`description` / `title` / `$comment` / string\n * `examples`) at any nesting depth, before the schema reaches the\n * provider wire and the `tool_search` projection (W-018, the Invariant\n * Labs tool-poisoning vector `properties.query.description`).\n *\n * Design choices:\n *\n * - Strip, never wrap: the result must remain a valid JSON Schema\n * document; an `<<<untrusted_content>>>` envelope would break the\n * structure.\n * - Returns a recursively cloned COPY and never mutates the input.\n * The caller keeps hashing the RAW definition\n * (`computeToolDefinitionHash`), so existing TOFU pins stay valid\n * and drift detection still sees the original bytes - two\n * differently-poisoned schemas must not collapse into one redacted\n * hash.\n * - Semantic keywords and unknown vocabulary are cloned byte-identical\n * (annotation keys are a whitelist, not a blacklist), so input\n * validation behaves exactly as with the raw schema.\n * - `'pass-through'` returns the input as-is (operator override, same\n * contract as {@link sanitizeDescription}).\n */\nexport function sanitizeSchemaAnnotations(args: {\n readonly schema: JsonSchemaLike;\n readonly inboundSanitization: InboundSanitizationPolicy;\n readonly toolName: string;\n readonly serverIdentity: ServerIdentity;\n}): SanitizedSchemaResult {\n if (args.inboundSanitization === 'pass-through') {\n return { schema: args.schema, patternsHit: Object.freeze<string[]>([]) };\n }\n const hits = new Set<string>();\n const sanitizeString = (body: string): string => {\n const outcome = applyInboundSanitization({\n body,\n policy: 'detect-and-strip',\n trustClass: 'mcp-derived',\n toolName: args.toolName,\n contentOrigin: `mcp:tool-schema:${args.serverIdentity.id}`,\n failClosed: false,\n });\n for (const hit of outcome.patternsHit) hits.add(hit);\n return outcome.body;\n };\n const schema = walkSchema(args.schema, sanitizeString) as JsonSchemaLike;\n if (hits.size > 0) {\n // Registration-time signal, mirroring the description counter (C6).\n incrementCounter('mcp.tool-schema.injection-flagged.total', {\n server: args.serverIdentity.id,\n tool: args.toolName,\n });\n }\n return { schema, patternsHit: Object.freeze([...hits]) };\n}\n\nfunction walkSchema(node: unknown, sanitizeString: (body: string) => string): unknown {\n if (node === null || typeof node !== 'object') return node;\n if (Array.isArray(node)) return node.map((el) => walkSchema(el, sanitizeString));\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node)) {\n if (ANNOTATION_STRING_KEYS.has(key) && typeof value === 'string') {\n out[key] = sanitizeString(value);\n } else if (key === 'examples' && Array.isArray(value)) {\n out[key] = value.map((el) => (typeof el === 'string' ? sanitizeString(el) : cloneJson(el)));\n } else if (SCHEMA_VALUE_KEYS.has(key)) {\n out[key] = walkSchema(value, sanitizeString);\n } else if (SCHEMA_LIST_KEYS.has(key) && Array.isArray(value)) {\n out[key] = value.map((el) => walkSchema(el, sanitizeString));\n } else if (\n SCHEMA_RECORD_KEYS.has(key) &&\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value)\n ) {\n const record: Record<string, unknown> = {};\n for (const [name, sub] of Object.entries(value)) {\n record[name] = walkSchema(sub, sanitizeString);\n }\n out[key] = record;\n } else {\n out[key] = cloneJson(value);\n }\n }\n return out;\n}\n\n/** Deep JSON clone that keeps semantic keyword values byte-identical. */\nfunction cloneJson(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value;\n return structuredClone(value);\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { CreateMCPClientOptions, MCPCallToolResult, MCPClient, MCPContentPart, MCPElicitationHandler, MCPElicitationRequest, MCPElicitationResult, MCPPromptDefinition, MCPPromptMessage, MCPResourceContent, MCPResourceDefinition, MCPSamplingContent, MCPSamplingHandler, MCPSamplingMessage, MCPSamplingRequest, MCPSamplingResult, MCPToToolsOptions, MCPToolDefinition } from "./types.js";
2
2
  import { _resetSseWarnDedupForTesting, createMCPClient } from "./client.js";
3
+ import { CreateManagedMCPClientOptions, ManagedReconnectOptions, createManagedMCPClient } from "./managed.js";
3
4
  import { McpResourceReaderOptions, createMcpResourceReader } from "./mcp-resource-reader.js";
4
5
  import { adaptCallResult } from "./adapt-result.js";
5
6
  import { DEFAULT_DEFER_LOADING_THRESHOLD } from "./defer-loading.js";
6
7
  import { _resetMcpAdapterDedupForTesting, adaptMCPTools } from "./to-tools.js";
7
- export { type CreateMCPClientOptions, DEFAULT_DEFER_LOADING_THRESHOLD, type MCPCallToolResult, type MCPClient, type MCPContentPart, type MCPElicitationHandler, type MCPElicitationRequest, type MCPElicitationResult, type MCPPromptDefinition, type MCPPromptMessage, type MCPResourceContent, type MCPResourceDefinition, type MCPSamplingContent, type MCPSamplingHandler, type MCPSamplingMessage, type MCPSamplingRequest, type MCPSamplingResult, type MCPToToolsOptions, type MCPToolDefinition, type McpResourceReaderOptions, _resetMcpAdapterDedupForTesting, _resetSseWarnDedupForTesting, adaptCallResult, adaptMCPTools, createMCPClient, createMcpResourceReader };
8
+ export { type CreateMCPClientOptions, type CreateManagedMCPClientOptions, DEFAULT_DEFER_LOADING_THRESHOLD, type MCPCallToolResult, type MCPClient, type MCPContentPart, type MCPElicitationHandler, type MCPElicitationRequest, type MCPElicitationResult, type MCPPromptDefinition, type MCPPromptMessage, type MCPResourceContent, type MCPResourceDefinition, type MCPSamplingContent, type MCPSamplingHandler, type MCPSamplingMessage, type MCPSamplingRequest, type MCPSamplingResult, type MCPToToolsOptions, type MCPToolDefinition, type ManagedReconnectOptions, type McpResourceReaderOptions, _resetMcpAdapterDedupForTesting, _resetSseWarnDedupForTesting, adaptCallResult, adaptMCPTools, createMCPClient, createManagedMCPClient, createMcpResourceReader };
@@ -2,6 +2,7 @@ import { adaptCallResult } from "./adapt-result.js";
2
2
  import { DEFAULT_DEFER_LOADING_THRESHOLD } from "./defer-loading.js";
3
3
  import { _resetMcpAdapterDedupForTesting, adaptMCPTools } from "./to-tools.js";
4
4
  import { _resetSseWarnDedupForTesting, createMCPClient } from "./client.js";
5
+ import { createManagedMCPClient } from "./managed.js";
5
6
  import { createMcpResourceReader } from "./mcp-resource-reader.js";
6
7
 
7
- export { DEFAULT_DEFER_LOADING_THRESHOLD, _resetMcpAdapterDedupForTesting, _resetSseWarnDedupForTesting, adaptCallResult, adaptMCPTools, createMCPClient, createMcpResourceReader };
8
+ export { DEFAULT_DEFER_LOADING_THRESHOLD, _resetMcpAdapterDedupForTesting, _resetSseWarnDedupForTesting, adaptCallResult, adaptMCPTools, createMCPClient, createManagedMCPClient, createMcpResourceReader };
@@ -0,0 +1,35 @@
1
+ import { CreateMCPClientOptions, MCPClient } from "./types.js";
2
+
3
+ //#region src/client/managed.d.ts
4
+
5
+ /** Reconnection tuning for {@link createManagedMCPClient}. @stable */
6
+ interface ManagedReconnectOptions {
7
+ /** Attempts per outage before giving up. Default `5`. */
8
+ readonly maxAttempts?: number;
9
+ /** First backoff delay (doubles per attempt, jittered). Default `500` ms. */
10
+ readonly initialDelayMs?: number;
11
+ /** Backoff ceiling. Default `30_000` ms. */
12
+ readonly maxDelayMs?: number;
13
+ }
14
+ /** Options for {@link createManagedMCPClient}. @stable */
15
+ type CreateManagedMCPClientOptions = CreateMCPClientOptions & {
16
+ readonly reconnect?: ManagedReconnectOptions;
17
+ /**
18
+ * Client factory seam - tests inject fake inner clients; production
19
+ * uses {@link createMCPClient}.
20
+ *
21
+ * @internal
22
+ */
23
+ readonly _clientFactory?: (options: CreateMCPClientOptions) => Promise<MCPClient>;
24
+ };
25
+ /**
26
+ * Open a managed (auto-reconnecting) MCP client. See the module doc for
27
+ * the exact semantics. `close()` is terminal: it stops any in-progress
28
+ * backoff and no further reconnects happen.
29
+ *
30
+ * @stable
31
+ */
32
+ declare function createManagedMCPClient(options: CreateManagedMCPClientOptions): Promise<MCPClient>;
33
+ //#endregion
34
+ export { CreateManagedMCPClientOptions, ManagedReconnectOptions, createManagedMCPClient };
35
+ //# sourceMappingURL=managed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed.d.ts","names":[],"sources":["../../src/client/managed.ts"],"sourcesContent":[],"mappings":";;;;;UAqCiB,uBAAA;;;;;;;;;KAUL,6BAAA,GAAgC;uBACrB;;;;;;;sCAOe,2BAA2B,QAAQ;;;;;;;;;iBAUnD,sBAAA,UACX,gCACR,QAAQ"}