@contractspec/lib.ai-agent 2.6.0 → 2.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 (39) hide show
  1. package/README.md +33 -2
  2. package/dist/agent/agent-factory.d.ts +5 -0
  3. package/dist/agent/agent-factory.js +221 -11
  4. package/dist/agent/contract-spec-agent.d.ts +8 -0
  5. package/dist/agent/contract-spec-agent.js +210 -10
  6. package/dist/agent/index.js +334 -39
  7. package/dist/agent/json-runner.js +210 -10
  8. package/dist/agent/unified-agent.d.ts +4 -0
  9. package/dist/agent/unified-agent.js +334 -39
  10. package/dist/exporters/claude-agent-exporter.d.ts +1 -0
  11. package/dist/exporters/claude-agent-exporter.js +11 -1
  12. package/dist/exporters/index.js +11 -1
  13. package/dist/exporters/types.d.ts +3 -10
  14. package/dist/node/agent/agent-factory.js +221 -11
  15. package/dist/node/agent/contract-spec-agent.js +210 -10
  16. package/dist/node/agent/index.js +334 -39
  17. package/dist/node/agent/json-runner.js +210 -10
  18. package/dist/node/agent/unified-agent.js +334 -39
  19. package/dist/node/exporters/claude-agent-exporter.js +11 -1
  20. package/dist/node/exporters/index.js +11 -1
  21. package/dist/node/providers/claude-agent-sdk/adapter.js +260 -23
  22. package/dist/node/providers/claude-agent-sdk/index.js +260 -23
  23. package/dist/node/providers/index.js +260 -23
  24. package/dist/node/tools/index.js +154 -18
  25. package/dist/node/tools/mcp-client-helpers.js +106 -0
  26. package/dist/node/tools/mcp-client.js +155 -18
  27. package/dist/providers/claude-agent-sdk/adapter.d.ts +4 -0
  28. package/dist/providers/claude-agent-sdk/adapter.js +260 -23
  29. package/dist/providers/claude-agent-sdk/index.d.ts +8 -0
  30. package/dist/providers/claude-agent-sdk/index.js +260 -23
  31. package/dist/providers/index.js +260 -23
  32. package/dist/providers/types.d.ts +1 -1
  33. package/dist/tools/index.js +154 -18
  34. package/dist/tools/mcp-client-helpers.d.ts +12 -0
  35. package/dist/tools/mcp-client-helpers.js +106 -0
  36. package/dist/tools/mcp-client.d.ts +55 -3
  37. package/dist/tools/mcp-client.js +155 -18
  38. package/dist/tools/mcp-client.test.d.ts +1 -0
  39. package/package.json +24 -12
@@ -2161,6 +2161,171 @@ var init_spec = __esm(() => {
2161
2161
  init_i18n();
2162
2162
  });
2163
2163
 
2164
+ // src/tools/mcp-client-helpers.ts
2165
+ import {
2166
+ Experimental_StdioMCPTransport as StdioClientTransport
2167
+ } from "@ai-sdk/mcp/mcp-stdio";
2168
+ function buildMcpTransport(config) {
2169
+ const transport = resolveTransportType(config);
2170
+ if (transport === "stdio") {
2171
+ const stdioConfig = resolveStdioConfig(config);
2172
+ return new StdioClientTransport(stdioConfig);
2173
+ }
2174
+ const remoteConfig = config;
2175
+ const headers = resolveRemoteHeaders(remoteConfig);
2176
+ const remoteTransport = {
2177
+ type: transport,
2178
+ url: requireNonEmptyString(remoteConfig.url, "url", config.name)
2179
+ };
2180
+ if (headers) {
2181
+ remoteTransport.headers = headers;
2182
+ }
2183
+ if (remoteConfig.authProvider) {
2184
+ remoteTransport.authProvider = remoteConfig.authProvider;
2185
+ }
2186
+ return remoteTransport;
2187
+ }
2188
+ function prefixToolNames(config, tools) {
2189
+ const prefix = config.toolPrefix?.trim();
2190
+ if (!prefix) {
2191
+ return tools;
2192
+ }
2193
+ const prefixedTools = {};
2194
+ for (const [toolName, tool] of Object.entries(tools)) {
2195
+ prefixedTools[`${prefix}_${toolName}`] = tool;
2196
+ }
2197
+ return prefixedTools;
2198
+ }
2199
+ function getErrorMessage(error) {
2200
+ if (error instanceof Error) {
2201
+ return error.message;
2202
+ }
2203
+ return String(error);
2204
+ }
2205
+ function resolveTransportType(config) {
2206
+ return config.transport ?? "stdio";
2207
+ }
2208
+ function resolveStdioConfig(config) {
2209
+ const stdioConfig = config;
2210
+ return {
2211
+ command: requireNonEmptyString(stdioConfig.command, "command", config.name),
2212
+ args: stdioConfig.args,
2213
+ env: stdioConfig.env,
2214
+ cwd: stdioConfig.cwd
2215
+ };
2216
+ }
2217
+ function resolveRemoteHeaders(config) {
2218
+ const headers = {
2219
+ ...config.headers ?? {}
2220
+ };
2221
+ const accessToken = config.accessToken ?? resolveEnvToken(config.accessTokenEnvVar);
2222
+ if (accessToken && headers.Authorization === undefined) {
2223
+ headers.Authorization = `Bearer ${accessToken}`;
2224
+ }
2225
+ return Object.keys(headers).length > 0 ? headers : undefined;
2226
+ }
2227
+ function resolveEnvToken(envVarName) {
2228
+ if (!envVarName) {
2229
+ return;
2230
+ }
2231
+ const value = process.env[envVarName];
2232
+ if (!value) {
2233
+ return;
2234
+ }
2235
+ const trimmed = value.trim();
2236
+ return trimmed.length > 0 ? trimmed : undefined;
2237
+ }
2238
+ function requireNonEmptyString(value, field, serverName) {
2239
+ if (!value) {
2240
+ throw new Error(`MCP server "${serverName}" is missing required "${field}".`);
2241
+ }
2242
+ const trimmed = value.trim();
2243
+ if (trimmed.length === 0) {
2244
+ throw new Error(`MCP server "${serverName}" has an empty "${field}".`);
2245
+ }
2246
+ return trimmed;
2247
+ }
2248
+ var init_mcp_client_helpers = () => {};
2249
+
2250
+ // src/tools/mcp-client.ts
2251
+ import {
2252
+ experimental_createMCPClient
2253
+ } from "@ai-sdk/mcp";
2254
+ async function mcpServerToTools(config) {
2255
+ let client = null;
2256
+ try {
2257
+ const transport = buildMcpTransport(config);
2258
+ client = await experimental_createMCPClient({
2259
+ transport,
2260
+ name: config.clientName,
2261
+ version: config.clientVersion
2262
+ });
2263
+ const tools = await client.tools();
2264
+ const prefixedTools = prefixToolNames(config, tools);
2265
+ const connectedClient = client;
2266
+ return {
2267
+ tools: prefixedTools,
2268
+ cleanup: () => connectedClient.close(),
2269
+ serverToolNames: {
2270
+ [config.name]: Object.keys(prefixedTools)
2271
+ }
2272
+ };
2273
+ } catch (error) {
2274
+ if (client) {
2275
+ await client.close().catch(() => {
2276
+ return;
2277
+ });
2278
+ }
2279
+ throw new Error(`[MCP:${config.name}] Failed to connect tools: ${getErrorMessage(error)}`);
2280
+ }
2281
+ }
2282
+ async function createMcpToolsets(configs, options = {}) {
2283
+ const connected = [];
2284
+ try {
2285
+ for (const config of configs) {
2286
+ const result = await mcpServerToTools(config);
2287
+ connected.push(result);
2288
+ }
2289
+ } catch (error) {
2290
+ await Promise.allSettled(connected.map((result) => result.cleanup()));
2291
+ throw error;
2292
+ }
2293
+ const combinedTools = {};
2294
+ const serverToolNames = {};
2295
+ const collisionStrategy = options.onNameCollision ?? "overwrite";
2296
+ try {
2297
+ for (const result of connected) {
2298
+ for (const [serverName, toolNames] of Object.entries(result.serverToolNames)) {
2299
+ serverToolNames[serverName] = toolNames;
2300
+ }
2301
+ for (const [toolName, tool] of Object.entries(result.tools)) {
2302
+ const hasCollision = combinedTools[toolName] !== undefined;
2303
+ if (hasCollision && collisionStrategy === "error") {
2304
+ throw new Error(`Duplicate MCP tool name "${toolName}" detected. Use "toolPrefix" or set onNameCollision to "overwrite".`);
2305
+ }
2306
+ combinedTools[toolName] = tool;
2307
+ }
2308
+ }
2309
+ } catch (error) {
2310
+ await Promise.allSettled(connected.map((result) => result.cleanup()));
2311
+ throw error;
2312
+ }
2313
+ return {
2314
+ tools: combinedTools,
2315
+ serverToolNames,
2316
+ cleanup: async () => {
2317
+ const cleanupResults = await Promise.allSettled(connected.map((result) => result.cleanup()));
2318
+ const failures = cleanupResults.filter((result) => result.status === "rejected");
2319
+ if (failures.length > 0) {
2320
+ throw new Error(`Failed to cleanup ${failures.length} MCP client connection(s).`);
2321
+ }
2322
+ }
2323
+ };
2324
+ }
2325
+ var init_mcp_client = __esm(() => {
2326
+ init_mcp_client_helpers();
2327
+ });
2328
+
2164
2329
  // src/knowledge/injector.ts
2165
2330
  async function injectStaticKnowledge(instructions, knowledgeRefs, retriever, locale) {
2166
2331
  if (!retriever)
@@ -2478,6 +2643,8 @@ function summarizeSession(session) {
2478
2643
  }
2479
2644
 
2480
2645
  // src/providers/claude-agent-sdk/adapter.ts
2646
+ import { randomUUID } from "node:crypto";
2647
+
2481
2648
  class ClaudeAgentSDKProvider {
2482
2649
  name = "claude-agent-sdk";
2483
2650
  version = "1.0.0";
@@ -2510,11 +2677,23 @@ class ClaudeAgentSDKProvider {
2510
2677
  if (!this.isAvailable()) {
2511
2678
  throw new ProviderNotAvailableError(this.name, createAgentI18n(this.config.locale).t("error.provider.sdkNotConfigured"));
2512
2679
  }
2680
+ let mcpToolset = null;
2513
2681
  try {
2514
2682
  const toolSet = {};
2515
2683
  for (const tool of spec.tools) {
2516
2684
  toolSet[tool.name] = specToolToExternalTool(tool);
2517
2685
  }
2686
+ if ((this.config.mcpServers?.length ?? 0) > 0) {
2687
+ mcpToolset = await createMcpToolsets(this.config.mcpServers ?? [], {
2688
+ onNameCollision: "error"
2689
+ });
2690
+ for (const [toolName, mcpTool] of Object.entries(mcpToolset.tools)) {
2691
+ if (toolSet[toolName]) {
2692
+ throw new Error(`MCP tool "${toolName}" collides with a ContractSpec tool. Configure MCP toolPrefix values to avoid collisions.`);
2693
+ }
2694
+ toolSet[toolName] = this.mcpToolToExternalTool(toolName, mcpTool);
2695
+ }
2696
+ }
2518
2697
  const instructions = await injectStaticKnowledge(spec.instructions, spec.knowledge ?? [], undefined);
2519
2698
  const contextId = `claude-${agentKey(spec.meta)}-${Date.now()}`;
2520
2699
  const metadata = {
@@ -2522,6 +2701,7 @@ class ClaudeAgentSDKProvider {
2522
2701
  extendedThinkingEnabled: this.config.extendedThinking ?? false,
2523
2702
  mcpServerIds: this.config.mcpServers?.map((s) => s.name) ?? []
2524
2703
  };
2704
+ const cleanupMcp = mcpToolset?.cleanup;
2525
2705
  return {
2526
2706
  id: contextId,
2527
2707
  spec: {
@@ -2530,9 +2710,18 @@ class ClaudeAgentSDKProvider {
2530
2710
  },
2531
2711
  tools: toolSet,
2532
2712
  metadata,
2533
- cleanup: async () => {}
2713
+ cleanup: async () => {
2714
+ if (cleanupMcp) {
2715
+ await cleanupMcp();
2716
+ }
2717
+ }
2534
2718
  };
2535
2719
  } catch (error) {
2720
+ if (mcpToolset) {
2721
+ await mcpToolset.cleanup().catch(() => {
2722
+ return;
2723
+ });
2724
+ }
2536
2725
  throw new ContextCreationError(this.name, createAgentI18n(this.config.locale).t("error.provider.contextCreation", {
2537
2726
  error: error instanceof Error ? error.message : String(error)
2538
2727
  }), error instanceof Error ? error : undefined);
@@ -2547,7 +2736,7 @@ ${params.systemOverride}` : context.spec.instructions;
2547
2736
  const claudeContext = buildClaudeAgentContext(params.options);
2548
2737
  let session = createEmptyClaudeSession();
2549
2738
  session = appendUserMessage(session, params.prompt);
2550
- const claudeTools = this.prepareToolsForSDK(context, params);
2739
+ const claudeTools = this.prepareToolsForSDK(context);
2551
2740
  const rawResponse = await sdk.execute({
2552
2741
  model: this.config.model,
2553
2742
  system: systemPrompt,
@@ -2603,7 +2792,7 @@ ${params.systemOverride}` : context.spec.instructions;
2603
2792
 
2604
2793
  ${params.systemOverride}` : context.spec.instructions;
2605
2794
  const claudeContext = buildClaudeAgentContext(params.options);
2606
- const claudeTools = this.prepareToolsForSDK(context, params);
2795
+ const claudeTools = this.prepareToolsForSDK(context);
2607
2796
  const stream = await sdk.stream({
2608
2797
  model: this.config.model,
2609
2798
  system: systemPrompt,
@@ -2686,30 +2875,77 @@ ${params.systemOverride}` : context.spec.instructions;
2686
2875
  throw new ProviderNotAvailableError(this.name, createAgentI18n(this.config.locale).t("error.provider.claudeSdkMissing"));
2687
2876
  }
2688
2877
  }
2689
- prepareToolsForSDK(context, params) {
2690
- const handlers = new Map;
2691
- for (const tool of context.spec.tools) {
2692
- const externalTool = context.tools[tool.name];
2693
- if (externalTool?.execute) {
2694
- handlers.set(tool.name, async (input) => {
2695
- if (!externalTool.execute) {
2696
- throw new Error(createAgentI18n(this.config.locale).t("error.toolNoExecuteHandler", {
2697
- name: tool.name
2698
- }));
2699
- }
2700
- const result = await externalTool.execute(input);
2878
+ prepareToolsForSDK(context) {
2879
+ const i18n = createAgentI18n(this.config.locale);
2880
+ const toolsForSdk = [];
2881
+ for (const [toolName, externalTool] of Object.entries(context.tools)) {
2882
+ if (!externalTool.execute) {
2883
+ continue;
2884
+ }
2885
+ toolsForSdk.push({
2886
+ name: toolName,
2887
+ description: externalTool.description ?? i18n.t("tool.fallbackDescription", { name: toolName }),
2888
+ input_schema: this.normalizeToolSchemaForClaude(externalTool.inputSchema),
2889
+ requires_confirmation: externalTool.requiresApproval,
2890
+ execute: async (input) => {
2891
+ const result = await externalTool.execute?.(input);
2701
2892
  return typeof result === "string" ? result : JSON.stringify(result);
2893
+ }
2894
+ });
2895
+ }
2896
+ return toolsForSdk;
2897
+ }
2898
+ mcpToolToExternalTool(toolName, tool) {
2899
+ return {
2900
+ name: toolName,
2901
+ description: tool.description ?? createAgentI18n(this.config.locale).t("tool.fallbackDescription", {
2902
+ name: toolName
2903
+ }),
2904
+ inputSchema: this.normalizeExternalInputSchema(tool.inputSchema),
2905
+ execute: async (input) => {
2906
+ if (!tool.execute) {
2907
+ throw new Error(createAgentI18n(this.config.locale).t("error.toolNoExecuteHandler", {
2908
+ name: toolName
2909
+ }));
2910
+ }
2911
+ return tool.execute(input, {
2912
+ toolCallId: `mcp-${randomUUID()}`,
2913
+ messages: []
2702
2914
  });
2703
2915
  }
2916
+ };
2917
+ }
2918
+ normalizeExternalInputSchema(schema) {
2919
+ if (this.isRecord(schema)) {
2920
+ const type = schema["type"];
2921
+ if (type === "object" || schema["properties"]) {
2922
+ return schema;
2923
+ }
2704
2924
  }
2705
- return specToolsToClaudeAgentTools(context.spec.tools, handlers, {
2706
- agentId: context.id,
2707
- sessionId: params.options?.sessionId,
2708
- tenantId: params.options?.tenantId,
2709
- actorId: params.options?.actorId,
2710
- metadata: params.options?.metadata,
2711
- signal: params.signal
2712
- });
2925
+ return {
2926
+ type: "object",
2927
+ properties: {}
2928
+ };
2929
+ }
2930
+ normalizeToolSchemaForClaude(schema) {
2931
+ if (schema.type === "object") {
2932
+ return {
2933
+ type: "object",
2934
+ properties: schema.properties,
2935
+ required: schema.required,
2936
+ additionalProperties: schema.additionalProperties
2937
+ };
2938
+ }
2939
+ return {
2940
+ type: "object",
2941
+ properties: {
2942
+ value: schema
2943
+ },
2944
+ required: ["value"]
2945
+ };
2946
+ }
2947
+ isRecord(value) {
2948
+ return typeof value === "object" && value !== null;
2713
2949
  }
2714
2950
  async executeTool(toolCall, context, _params) {
2715
2951
  const tool = context.tools[toolCall.toolName];
@@ -2765,6 +3001,7 @@ var init_adapter = __esm(() => {
2765
3001
  init_types();
2766
3002
  init_tool_bridge();
2767
3003
  init_injector();
3004
+ init_mcp_client();
2768
3005
  init_i18n();
2769
3006
  });
2770
3007