@contractspec/lib.ai-agent 2.5.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.
- package/README.md +33 -2
- package/dist/agent/agent-factory.d.ts +5 -0
- package/dist/agent/agent-factory.js +221 -11
- package/dist/agent/contract-spec-agent.d.ts +8 -0
- package/dist/agent/contract-spec-agent.js +210 -10
- package/dist/agent/index.js +334 -39
- package/dist/agent/json-runner.js +210 -10
- package/dist/agent/unified-agent.d.ts +4 -0
- package/dist/agent/unified-agent.js +334 -39
- package/dist/exporters/claude-agent-exporter.d.ts +1 -0
- package/dist/exporters/claude-agent-exporter.js +11 -1
- package/dist/exporters/index.js +11 -1
- package/dist/exporters/types.d.ts +3 -10
- package/dist/node/agent/agent-factory.js +221 -11
- package/dist/node/agent/contract-spec-agent.js +210 -10
- package/dist/node/agent/index.js +334 -39
- package/dist/node/agent/json-runner.js +210 -10
- package/dist/node/agent/unified-agent.js +334 -39
- package/dist/node/exporters/claude-agent-exporter.js +11 -1
- package/dist/node/exporters/index.js +11 -1
- package/dist/node/providers/claude-agent-sdk/adapter.js +260 -23
- package/dist/node/providers/claude-agent-sdk/index.js +260 -23
- package/dist/node/providers/index.js +260 -23
- package/dist/node/tools/index.js +154 -18
- package/dist/node/tools/mcp-client-helpers.js +106 -0
- package/dist/node/tools/mcp-client.js +155 -18
- package/dist/providers/claude-agent-sdk/adapter.d.ts +4 -0
- package/dist/providers/claude-agent-sdk/adapter.js +260 -23
- package/dist/providers/claude-agent-sdk/index.d.ts +8 -0
- package/dist/providers/claude-agent-sdk/index.js +260 -23
- package/dist/providers/index.js +260 -23
- package/dist/providers/types.d.ts +1 -1
- package/dist/tools/index.js +154 -18
- package/dist/tools/mcp-client-helpers.d.ts +12 -0
- package/dist/tools/mcp-client-helpers.js +106 -0
- package/dist/tools/mcp-client.d.ts +55 -3
- package/dist/tools/mcp-client.js +155 -18
- package/dist/tools/mcp-client.test.d.ts +1 -0
- package/package.json +24 -12
|
@@ -2400,6 +2400,171 @@ var init_knowledge_tool = __esm(() => {
|
|
|
2400
2400
|
init_i18n();
|
|
2401
2401
|
});
|
|
2402
2402
|
|
|
2403
|
+
// src/tools/mcp-client-helpers.ts
|
|
2404
|
+
import {
|
|
2405
|
+
Experimental_StdioMCPTransport as StdioClientTransport
|
|
2406
|
+
} from "@ai-sdk/mcp/mcp-stdio";
|
|
2407
|
+
function buildMcpTransport(config) {
|
|
2408
|
+
const transport = resolveTransportType(config);
|
|
2409
|
+
if (transport === "stdio") {
|
|
2410
|
+
const stdioConfig = resolveStdioConfig(config);
|
|
2411
|
+
return new StdioClientTransport(stdioConfig);
|
|
2412
|
+
}
|
|
2413
|
+
const remoteConfig = config;
|
|
2414
|
+
const headers = resolveRemoteHeaders(remoteConfig);
|
|
2415
|
+
const remoteTransport = {
|
|
2416
|
+
type: transport,
|
|
2417
|
+
url: requireNonEmptyString(remoteConfig.url, "url", config.name)
|
|
2418
|
+
};
|
|
2419
|
+
if (headers) {
|
|
2420
|
+
remoteTransport.headers = headers;
|
|
2421
|
+
}
|
|
2422
|
+
if (remoteConfig.authProvider) {
|
|
2423
|
+
remoteTransport.authProvider = remoteConfig.authProvider;
|
|
2424
|
+
}
|
|
2425
|
+
return remoteTransport;
|
|
2426
|
+
}
|
|
2427
|
+
function prefixToolNames(config, tools) {
|
|
2428
|
+
const prefix = config.toolPrefix?.trim();
|
|
2429
|
+
if (!prefix) {
|
|
2430
|
+
return tools;
|
|
2431
|
+
}
|
|
2432
|
+
const prefixedTools = {};
|
|
2433
|
+
for (const [toolName, tool3] of Object.entries(tools)) {
|
|
2434
|
+
prefixedTools[`${prefix}_${toolName}`] = tool3;
|
|
2435
|
+
}
|
|
2436
|
+
return prefixedTools;
|
|
2437
|
+
}
|
|
2438
|
+
function getErrorMessage(error) {
|
|
2439
|
+
if (error instanceof Error) {
|
|
2440
|
+
return error.message;
|
|
2441
|
+
}
|
|
2442
|
+
return String(error);
|
|
2443
|
+
}
|
|
2444
|
+
function resolveTransportType(config) {
|
|
2445
|
+
return config.transport ?? "stdio";
|
|
2446
|
+
}
|
|
2447
|
+
function resolveStdioConfig(config) {
|
|
2448
|
+
const stdioConfig = config;
|
|
2449
|
+
return {
|
|
2450
|
+
command: requireNonEmptyString(stdioConfig.command, "command", config.name),
|
|
2451
|
+
args: stdioConfig.args,
|
|
2452
|
+
env: stdioConfig.env,
|
|
2453
|
+
cwd: stdioConfig.cwd
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
function resolveRemoteHeaders(config) {
|
|
2457
|
+
const headers = {
|
|
2458
|
+
...config.headers ?? {}
|
|
2459
|
+
};
|
|
2460
|
+
const accessToken = config.accessToken ?? resolveEnvToken(config.accessTokenEnvVar);
|
|
2461
|
+
if (accessToken && headers.Authorization === undefined) {
|
|
2462
|
+
headers.Authorization = `Bearer ${accessToken}`;
|
|
2463
|
+
}
|
|
2464
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
2465
|
+
}
|
|
2466
|
+
function resolveEnvToken(envVarName) {
|
|
2467
|
+
if (!envVarName) {
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
const value = process.env[envVarName];
|
|
2471
|
+
if (!value) {
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
const trimmed = value.trim();
|
|
2475
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
2476
|
+
}
|
|
2477
|
+
function requireNonEmptyString(value, field, serverName) {
|
|
2478
|
+
if (!value) {
|
|
2479
|
+
throw new Error(`MCP server "${serverName}" is missing required "${field}".`);
|
|
2480
|
+
}
|
|
2481
|
+
const trimmed = value.trim();
|
|
2482
|
+
if (trimmed.length === 0) {
|
|
2483
|
+
throw new Error(`MCP server "${serverName}" has an empty "${field}".`);
|
|
2484
|
+
}
|
|
2485
|
+
return trimmed;
|
|
2486
|
+
}
|
|
2487
|
+
var init_mcp_client_helpers = () => {};
|
|
2488
|
+
|
|
2489
|
+
// src/tools/mcp-client.ts
|
|
2490
|
+
import {
|
|
2491
|
+
experimental_createMCPClient
|
|
2492
|
+
} from "@ai-sdk/mcp";
|
|
2493
|
+
async function mcpServerToTools(config) {
|
|
2494
|
+
let client = null;
|
|
2495
|
+
try {
|
|
2496
|
+
const transport = buildMcpTransport(config);
|
|
2497
|
+
client = await experimental_createMCPClient({
|
|
2498
|
+
transport,
|
|
2499
|
+
name: config.clientName,
|
|
2500
|
+
version: config.clientVersion
|
|
2501
|
+
});
|
|
2502
|
+
const tools = await client.tools();
|
|
2503
|
+
const prefixedTools = prefixToolNames(config, tools);
|
|
2504
|
+
const connectedClient = client;
|
|
2505
|
+
return {
|
|
2506
|
+
tools: prefixedTools,
|
|
2507
|
+
cleanup: () => connectedClient.close(),
|
|
2508
|
+
serverToolNames: {
|
|
2509
|
+
[config.name]: Object.keys(prefixedTools)
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
} catch (error) {
|
|
2513
|
+
if (client) {
|
|
2514
|
+
await client.close().catch(() => {
|
|
2515
|
+
return;
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
throw new Error(`[MCP:${config.name}] Failed to connect tools: ${getErrorMessage(error)}`);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
async function createMcpToolsets(configs, options = {}) {
|
|
2522
|
+
const connected = [];
|
|
2523
|
+
try {
|
|
2524
|
+
for (const config of configs) {
|
|
2525
|
+
const result = await mcpServerToTools(config);
|
|
2526
|
+
connected.push(result);
|
|
2527
|
+
}
|
|
2528
|
+
} catch (error) {
|
|
2529
|
+
await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2530
|
+
throw error;
|
|
2531
|
+
}
|
|
2532
|
+
const combinedTools = {};
|
|
2533
|
+
const serverToolNames = {};
|
|
2534
|
+
const collisionStrategy = options.onNameCollision ?? "overwrite";
|
|
2535
|
+
try {
|
|
2536
|
+
for (const result of connected) {
|
|
2537
|
+
for (const [serverName, toolNames] of Object.entries(result.serverToolNames)) {
|
|
2538
|
+
serverToolNames[serverName] = toolNames;
|
|
2539
|
+
}
|
|
2540
|
+
for (const [toolName, tool3] of Object.entries(result.tools)) {
|
|
2541
|
+
const hasCollision = combinedTools[toolName] !== undefined;
|
|
2542
|
+
if (hasCollision && collisionStrategy === "error") {
|
|
2543
|
+
throw new Error(`Duplicate MCP tool name "${toolName}" detected. Use "toolPrefix" or set onNameCollision to "overwrite".`);
|
|
2544
|
+
}
|
|
2545
|
+
combinedTools[toolName] = tool3;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
} catch (error) {
|
|
2549
|
+
await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2550
|
+
throw error;
|
|
2551
|
+
}
|
|
2552
|
+
return {
|
|
2553
|
+
tools: combinedTools,
|
|
2554
|
+
serverToolNames,
|
|
2555
|
+
cleanup: async () => {
|
|
2556
|
+
const cleanupResults = await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2557
|
+
const failures = cleanupResults.filter((result) => result.status === "rejected");
|
|
2558
|
+
if (failures.length > 0) {
|
|
2559
|
+
throw new Error(`Failed to cleanup ${failures.length} MCP client connection(s).`);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
var init_mcp_client = __esm(() => {
|
|
2565
|
+
init_mcp_client_helpers();
|
|
2566
|
+
});
|
|
2567
|
+
|
|
2403
2568
|
// src/knowledge/injector.ts
|
|
2404
2569
|
async function injectStaticKnowledge(instructions, knowledgeRefs, retriever, locale) {
|
|
2405
2570
|
if (!retriever)
|
|
@@ -2909,25 +3074,59 @@ class ContractSpecAgent {
|
|
|
2909
3074
|
tools;
|
|
2910
3075
|
config;
|
|
2911
3076
|
instructions;
|
|
3077
|
+
mcpCleanup;
|
|
2912
3078
|
activeStepContexts = new Map;
|
|
2913
|
-
constructor(config, instructions, tools) {
|
|
3079
|
+
constructor(config, instructions, tools, mcpCleanup) {
|
|
2914
3080
|
this.config = config;
|
|
2915
3081
|
this.spec = config.spec;
|
|
2916
3082
|
this.id = agentKey(config.spec.meta);
|
|
2917
3083
|
this.tools = tools;
|
|
2918
3084
|
this.instructions = instructions;
|
|
3085
|
+
this.mcpCleanup = mcpCleanup;
|
|
2919
3086
|
}
|
|
2920
3087
|
static async create(config) {
|
|
2921
3088
|
const effectiveConfig = config;
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
3089
|
+
let mcpToolset = null;
|
|
3090
|
+
if ((effectiveConfig.mcpServers?.length ?? 0) > 0) {
|
|
3091
|
+
mcpToolset = await createMcpToolsets(effectiveConfig.mcpServers ?? [], {
|
|
3092
|
+
onNameCollision: "error"
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
try {
|
|
3096
|
+
const instructions = await injectStaticKnowledge(effectiveConfig.spec.instructions, effectiveConfig.spec.knowledge ?? [], effectiveConfig.knowledgeRetriever);
|
|
3097
|
+
const specTools = specToolsToAISDKTools(effectiveConfig.spec.tools, effectiveConfig.toolHandlers, { agentId: agentKey(effectiveConfig.spec.meta) });
|
|
3098
|
+
const knowledgeTool = effectiveConfig.knowledgeRetriever ? createKnowledgeQueryTool(effectiveConfig.knowledgeRetriever, effectiveConfig.spec.knowledge ?? []) : null;
|
|
3099
|
+
const reservedToolNames = new Set(Object.keys(specTools));
|
|
3100
|
+
if (knowledgeTool) {
|
|
3101
|
+
reservedToolNames.add("query_knowledge");
|
|
3102
|
+
}
|
|
3103
|
+
const conflictingMcpTools = Object.keys(mcpToolset?.tools ?? {}).filter((toolName) => reservedToolNames.has(toolName));
|
|
3104
|
+
if (conflictingMcpTools.length > 0) {
|
|
3105
|
+
throw new Error(`MCP tools conflict with agent tools: ${conflictingMcpTools.join(", ")}. Configure MCP toolPrefix values to avoid collisions.`);
|
|
3106
|
+
}
|
|
3107
|
+
const tools = {
|
|
3108
|
+
...specTools,
|
|
3109
|
+
...knowledgeTool ? { query_knowledge: knowledgeTool } : {},
|
|
3110
|
+
...mcpToolset?.tools ?? {},
|
|
3111
|
+
...effectiveConfig.additionalTools ?? {}
|
|
3112
|
+
};
|
|
3113
|
+
return new ContractSpecAgent(effectiveConfig, instructions, tools, mcpToolset?.cleanup);
|
|
3114
|
+
} catch (error) {
|
|
3115
|
+
if (mcpToolset) {
|
|
3116
|
+
await mcpToolset.cleanup().catch(() => {
|
|
3117
|
+
return;
|
|
3118
|
+
});
|
|
3119
|
+
}
|
|
3120
|
+
throw error;
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
async cleanup() {
|
|
3124
|
+
if (!this.mcpCleanup) {
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
const cleanup = this.mcpCleanup;
|
|
3128
|
+
this.mcpCleanup = undefined;
|
|
3129
|
+
await cleanup();
|
|
2931
3130
|
}
|
|
2932
3131
|
async generate(params) {
|
|
2933
3132
|
const sessionId = params.options?.sessionId ?? generateSessionId();
|
|
@@ -3102,6 +3301,7 @@ var init_contract_spec_agent = __esm(() => {
|
|
|
3102
3301
|
init_spec();
|
|
3103
3302
|
init_tool_adapter();
|
|
3104
3303
|
init_knowledge_tool();
|
|
3304
|
+
init_mcp_client();
|
|
3105
3305
|
init_injector();
|
|
3106
3306
|
init_adapter();
|
|
3107
3307
|
ContractSpecCallOptionsSchema = z3.object({
|
|
@@ -3136,6 +3336,10 @@ class AgentFactory {
|
|
|
3136
3336
|
...this.config.additionalTools,
|
|
3137
3337
|
...options?.additionalTools
|
|
3138
3338
|
};
|
|
3339
|
+
const mergedMcpServers = [
|
|
3340
|
+
...this.config.mcpServers ?? [],
|
|
3341
|
+
...options?.mcpServers ?? []
|
|
3342
|
+
];
|
|
3139
3343
|
return ContractSpecAgent.create({
|
|
3140
3344
|
spec,
|
|
3141
3345
|
model: options?.model ?? this.config.defaultModel,
|
|
@@ -3144,7 +3348,8 @@ class AgentFactory {
|
|
|
3144
3348
|
sessionStore: this.config.sessionStore,
|
|
3145
3349
|
telemetryCollector: this.config.telemetryCollector,
|
|
3146
3350
|
posthogConfig: this.config.posthogConfig,
|
|
3147
|
-
additionalTools: mergedTools
|
|
3351
|
+
additionalTools: mergedTools,
|
|
3352
|
+
mcpServers: mergedMcpServers.length > 0 ? mergedMcpServers : undefined
|
|
3148
3353
|
});
|
|
3149
3354
|
}
|
|
3150
3355
|
async getOrCreate(name, version) {
|
|
@@ -3158,6 +3363,11 @@ class AgentFactory {
|
|
|
3158
3363
|
return agent;
|
|
3159
3364
|
}
|
|
3160
3365
|
clearCache() {
|
|
3366
|
+
for (const agent of this.cache.values()) {
|
|
3367
|
+
agent.cleanup().catch(() => {
|
|
3368
|
+
return;
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3161
3371
|
this.cache.clear();
|
|
3162
3372
|
}
|
|
3163
3373
|
listAvailable() {
|
|
@@ -2400,6 +2400,171 @@ var init_knowledge_tool = __esm(() => {
|
|
|
2400
2400
|
init_i18n();
|
|
2401
2401
|
});
|
|
2402
2402
|
|
|
2403
|
+
// src/tools/mcp-client-helpers.ts
|
|
2404
|
+
import {
|
|
2405
|
+
Experimental_StdioMCPTransport as StdioClientTransport
|
|
2406
|
+
} from "@ai-sdk/mcp/mcp-stdio";
|
|
2407
|
+
function buildMcpTransport(config) {
|
|
2408
|
+
const transport = resolveTransportType(config);
|
|
2409
|
+
if (transport === "stdio") {
|
|
2410
|
+
const stdioConfig = resolveStdioConfig(config);
|
|
2411
|
+
return new StdioClientTransport(stdioConfig);
|
|
2412
|
+
}
|
|
2413
|
+
const remoteConfig = config;
|
|
2414
|
+
const headers = resolveRemoteHeaders(remoteConfig);
|
|
2415
|
+
const remoteTransport = {
|
|
2416
|
+
type: transport,
|
|
2417
|
+
url: requireNonEmptyString(remoteConfig.url, "url", config.name)
|
|
2418
|
+
};
|
|
2419
|
+
if (headers) {
|
|
2420
|
+
remoteTransport.headers = headers;
|
|
2421
|
+
}
|
|
2422
|
+
if (remoteConfig.authProvider) {
|
|
2423
|
+
remoteTransport.authProvider = remoteConfig.authProvider;
|
|
2424
|
+
}
|
|
2425
|
+
return remoteTransport;
|
|
2426
|
+
}
|
|
2427
|
+
function prefixToolNames(config, tools) {
|
|
2428
|
+
const prefix = config.toolPrefix?.trim();
|
|
2429
|
+
if (!prefix) {
|
|
2430
|
+
return tools;
|
|
2431
|
+
}
|
|
2432
|
+
const prefixedTools = {};
|
|
2433
|
+
for (const [toolName, tool3] of Object.entries(tools)) {
|
|
2434
|
+
prefixedTools[`${prefix}_${toolName}`] = tool3;
|
|
2435
|
+
}
|
|
2436
|
+
return prefixedTools;
|
|
2437
|
+
}
|
|
2438
|
+
function getErrorMessage(error) {
|
|
2439
|
+
if (error instanceof Error) {
|
|
2440
|
+
return error.message;
|
|
2441
|
+
}
|
|
2442
|
+
return String(error);
|
|
2443
|
+
}
|
|
2444
|
+
function resolveTransportType(config) {
|
|
2445
|
+
return config.transport ?? "stdio";
|
|
2446
|
+
}
|
|
2447
|
+
function resolveStdioConfig(config) {
|
|
2448
|
+
const stdioConfig = config;
|
|
2449
|
+
return {
|
|
2450
|
+
command: requireNonEmptyString(stdioConfig.command, "command", config.name),
|
|
2451
|
+
args: stdioConfig.args,
|
|
2452
|
+
env: stdioConfig.env,
|
|
2453
|
+
cwd: stdioConfig.cwd
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
function resolveRemoteHeaders(config) {
|
|
2457
|
+
const headers = {
|
|
2458
|
+
...config.headers ?? {}
|
|
2459
|
+
};
|
|
2460
|
+
const accessToken = config.accessToken ?? resolveEnvToken(config.accessTokenEnvVar);
|
|
2461
|
+
if (accessToken && headers.Authorization === undefined) {
|
|
2462
|
+
headers.Authorization = `Bearer ${accessToken}`;
|
|
2463
|
+
}
|
|
2464
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
2465
|
+
}
|
|
2466
|
+
function resolveEnvToken(envVarName) {
|
|
2467
|
+
if (!envVarName) {
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
const value = process.env[envVarName];
|
|
2471
|
+
if (!value) {
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
const trimmed = value.trim();
|
|
2475
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
2476
|
+
}
|
|
2477
|
+
function requireNonEmptyString(value, field, serverName) {
|
|
2478
|
+
if (!value) {
|
|
2479
|
+
throw new Error(`MCP server "${serverName}" is missing required "${field}".`);
|
|
2480
|
+
}
|
|
2481
|
+
const trimmed = value.trim();
|
|
2482
|
+
if (trimmed.length === 0) {
|
|
2483
|
+
throw new Error(`MCP server "${serverName}" has an empty "${field}".`);
|
|
2484
|
+
}
|
|
2485
|
+
return trimmed;
|
|
2486
|
+
}
|
|
2487
|
+
var init_mcp_client_helpers = () => {};
|
|
2488
|
+
|
|
2489
|
+
// src/tools/mcp-client.ts
|
|
2490
|
+
import {
|
|
2491
|
+
experimental_createMCPClient
|
|
2492
|
+
} from "@ai-sdk/mcp";
|
|
2493
|
+
async function mcpServerToTools(config) {
|
|
2494
|
+
let client = null;
|
|
2495
|
+
try {
|
|
2496
|
+
const transport = buildMcpTransport(config);
|
|
2497
|
+
client = await experimental_createMCPClient({
|
|
2498
|
+
transport,
|
|
2499
|
+
name: config.clientName,
|
|
2500
|
+
version: config.clientVersion
|
|
2501
|
+
});
|
|
2502
|
+
const tools = await client.tools();
|
|
2503
|
+
const prefixedTools = prefixToolNames(config, tools);
|
|
2504
|
+
const connectedClient = client;
|
|
2505
|
+
return {
|
|
2506
|
+
tools: prefixedTools,
|
|
2507
|
+
cleanup: () => connectedClient.close(),
|
|
2508
|
+
serverToolNames: {
|
|
2509
|
+
[config.name]: Object.keys(prefixedTools)
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
} catch (error) {
|
|
2513
|
+
if (client) {
|
|
2514
|
+
await client.close().catch(() => {
|
|
2515
|
+
return;
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
throw new Error(`[MCP:${config.name}] Failed to connect tools: ${getErrorMessage(error)}`);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
async function createMcpToolsets(configs, options = {}) {
|
|
2522
|
+
const connected = [];
|
|
2523
|
+
try {
|
|
2524
|
+
for (const config of configs) {
|
|
2525
|
+
const result = await mcpServerToTools(config);
|
|
2526
|
+
connected.push(result);
|
|
2527
|
+
}
|
|
2528
|
+
} catch (error) {
|
|
2529
|
+
await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2530
|
+
throw error;
|
|
2531
|
+
}
|
|
2532
|
+
const combinedTools = {};
|
|
2533
|
+
const serverToolNames = {};
|
|
2534
|
+
const collisionStrategy = options.onNameCollision ?? "overwrite";
|
|
2535
|
+
try {
|
|
2536
|
+
for (const result of connected) {
|
|
2537
|
+
for (const [serverName, toolNames] of Object.entries(result.serverToolNames)) {
|
|
2538
|
+
serverToolNames[serverName] = toolNames;
|
|
2539
|
+
}
|
|
2540
|
+
for (const [toolName, tool3] of Object.entries(result.tools)) {
|
|
2541
|
+
const hasCollision = combinedTools[toolName] !== undefined;
|
|
2542
|
+
if (hasCollision && collisionStrategy === "error") {
|
|
2543
|
+
throw new Error(`Duplicate MCP tool name "${toolName}" detected. Use "toolPrefix" or set onNameCollision to "overwrite".`);
|
|
2544
|
+
}
|
|
2545
|
+
combinedTools[toolName] = tool3;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
} catch (error) {
|
|
2549
|
+
await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2550
|
+
throw error;
|
|
2551
|
+
}
|
|
2552
|
+
return {
|
|
2553
|
+
tools: combinedTools,
|
|
2554
|
+
serverToolNames,
|
|
2555
|
+
cleanup: async () => {
|
|
2556
|
+
const cleanupResults = await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2557
|
+
const failures = cleanupResults.filter((result) => result.status === "rejected");
|
|
2558
|
+
if (failures.length > 0) {
|
|
2559
|
+
throw new Error(`Failed to cleanup ${failures.length} MCP client connection(s).`);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
var init_mcp_client = __esm(() => {
|
|
2565
|
+
init_mcp_client_helpers();
|
|
2566
|
+
});
|
|
2567
|
+
|
|
2403
2568
|
// src/knowledge/injector.ts
|
|
2404
2569
|
async function injectStaticKnowledge(instructions, knowledgeRefs, retriever, locale) {
|
|
2405
2570
|
if (!retriever)
|
|
@@ -2909,25 +3074,59 @@ class ContractSpecAgent {
|
|
|
2909
3074
|
tools;
|
|
2910
3075
|
config;
|
|
2911
3076
|
instructions;
|
|
3077
|
+
mcpCleanup;
|
|
2912
3078
|
activeStepContexts = new Map;
|
|
2913
|
-
constructor(config, instructions, tools) {
|
|
3079
|
+
constructor(config, instructions, tools, mcpCleanup) {
|
|
2914
3080
|
this.config = config;
|
|
2915
3081
|
this.spec = config.spec;
|
|
2916
3082
|
this.id = agentKey(config.spec.meta);
|
|
2917
3083
|
this.tools = tools;
|
|
2918
3084
|
this.instructions = instructions;
|
|
3085
|
+
this.mcpCleanup = mcpCleanup;
|
|
2919
3086
|
}
|
|
2920
3087
|
static async create(config) {
|
|
2921
3088
|
const effectiveConfig = config;
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
3089
|
+
let mcpToolset = null;
|
|
3090
|
+
if ((effectiveConfig.mcpServers?.length ?? 0) > 0) {
|
|
3091
|
+
mcpToolset = await createMcpToolsets(effectiveConfig.mcpServers ?? [], {
|
|
3092
|
+
onNameCollision: "error"
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
try {
|
|
3096
|
+
const instructions = await injectStaticKnowledge(effectiveConfig.spec.instructions, effectiveConfig.spec.knowledge ?? [], effectiveConfig.knowledgeRetriever);
|
|
3097
|
+
const specTools = specToolsToAISDKTools(effectiveConfig.spec.tools, effectiveConfig.toolHandlers, { agentId: agentKey(effectiveConfig.spec.meta) });
|
|
3098
|
+
const knowledgeTool = effectiveConfig.knowledgeRetriever ? createKnowledgeQueryTool(effectiveConfig.knowledgeRetriever, effectiveConfig.spec.knowledge ?? []) : null;
|
|
3099
|
+
const reservedToolNames = new Set(Object.keys(specTools));
|
|
3100
|
+
if (knowledgeTool) {
|
|
3101
|
+
reservedToolNames.add("query_knowledge");
|
|
3102
|
+
}
|
|
3103
|
+
const conflictingMcpTools = Object.keys(mcpToolset?.tools ?? {}).filter((toolName) => reservedToolNames.has(toolName));
|
|
3104
|
+
if (conflictingMcpTools.length > 0) {
|
|
3105
|
+
throw new Error(`MCP tools conflict with agent tools: ${conflictingMcpTools.join(", ")}. Configure MCP toolPrefix values to avoid collisions.`);
|
|
3106
|
+
}
|
|
3107
|
+
const tools = {
|
|
3108
|
+
...specTools,
|
|
3109
|
+
...knowledgeTool ? { query_knowledge: knowledgeTool } : {},
|
|
3110
|
+
...mcpToolset?.tools ?? {},
|
|
3111
|
+
...effectiveConfig.additionalTools ?? {}
|
|
3112
|
+
};
|
|
3113
|
+
return new ContractSpecAgent(effectiveConfig, instructions, tools, mcpToolset?.cleanup);
|
|
3114
|
+
} catch (error) {
|
|
3115
|
+
if (mcpToolset) {
|
|
3116
|
+
await mcpToolset.cleanup().catch(() => {
|
|
3117
|
+
return;
|
|
3118
|
+
});
|
|
3119
|
+
}
|
|
3120
|
+
throw error;
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
async cleanup() {
|
|
3124
|
+
if (!this.mcpCleanup) {
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
const cleanup = this.mcpCleanup;
|
|
3128
|
+
this.mcpCleanup = undefined;
|
|
3129
|
+
await cleanup();
|
|
2931
3130
|
}
|
|
2932
3131
|
async generate(params) {
|
|
2933
3132
|
const sessionId = params.options?.sessionId ?? generateSessionId();
|
|
@@ -3102,6 +3301,7 @@ var init_contract_spec_agent = __esm(() => {
|
|
|
3102
3301
|
init_spec();
|
|
3103
3302
|
init_tool_adapter();
|
|
3104
3303
|
init_knowledge_tool();
|
|
3304
|
+
init_mcp_client();
|
|
3105
3305
|
init_injector();
|
|
3106
3306
|
init_adapter();
|
|
3107
3307
|
ContractSpecCallOptionsSchema = z3.object({
|