@claude-code-kit/agent 0.2.0 → 0.3.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/dist/index.d.mts CHANGED
@@ -73,6 +73,9 @@ interface ToolDefinition<TInput = unknown> {
73
73
  isDestructive?: boolean;
74
74
  requiresConfirmation?: boolean;
75
75
  timeout?: number;
76
+ /** Original JSON Schema from an MCP server, used by toolToProviderFormat() to
77
+ * avoid a lossy Zod -> JSON Schema round-trip for MCP-discovered tools. */
78
+ rawInputSchema?: Record<string, unknown>;
76
79
  }
77
80
  type StreamChunk = {
78
81
  type: "text";
@@ -178,11 +181,34 @@ interface Session {
178
181
  interface CompactionStrategy {
179
182
  compact(messages: Message[], maxTokens: number): Message[] | Promise<Message[]>;
180
183
  }
184
+ /** Stdio-based MCP server: spawns a subprocess. */
185
+ interface MCPStdioServerConfig {
186
+ name: string;
187
+ command: string;
188
+ args?: string[];
189
+ env?: Record<string, string>;
190
+ cwd?: string;
191
+ /** Timeout in ms for the initial connection (default: 30000). */
192
+ connectTimeout?: number;
193
+ }
194
+ /** HTTP-based MCP server: connects via Streamable HTTP transport. */
195
+ interface MCPHttpServerConfig {
196
+ name: string;
197
+ url: string;
198
+ headers?: Record<string, string>;
199
+ /** Timeout in ms for the initial connection (default: 30000). */
200
+ connectTimeout?: number;
201
+ }
202
+ type MCPServerConfig = MCPStdioServerConfig | MCPHttpServerConfig;
203
+ interface MCPConfig {
204
+ servers: MCPServerConfig[];
205
+ }
181
206
  interface AgentConfig {
182
207
  provider: LLMProvider;
183
208
  model: string;
184
209
  systemPrompt?: string;
185
210
  tools?: ToolDefinition[];
211
+ mcp?: MCPConfig;
186
212
  maxTokens?: number;
187
213
  temperature?: number;
188
214
  contextLimit?: number;
@@ -191,6 +217,33 @@ interface AgentConfig {
191
217
  permissionHandler?: PermissionHandler;
192
218
  workingDirectory?: string;
193
219
  maxTurns?: number;
220
+ /** Max number of tools to execute concurrently (default: 5). */
221
+ maxConcurrentTools?: number;
222
+ }
223
+
224
+ declare class MCPClient {
225
+ private config;
226
+ private client;
227
+ private transport;
228
+ private _tools;
229
+ private _connected;
230
+ constructor(config: MCPServerConfig);
231
+ get name(): string;
232
+ get connected(): boolean;
233
+ get tools(): ToolDefinition[];
234
+ /**
235
+ * Connect to the MCP server and discover available tools.
236
+ * Throws if the SDK is not installed or the server fails to connect.
237
+ */
238
+ connect(): Promise<void>;
239
+ /**
240
+ * Refresh the tool list from the server.
241
+ */
242
+ discoverTools(): Promise<ToolDefinition[]>;
243
+ /**
244
+ * Disconnect from the MCP server and clean up resources.
245
+ */
246
+ disconnect(): Promise<void>;
194
247
  }
195
248
 
196
249
  /**
@@ -211,7 +264,12 @@ declare class Agent {
211
264
  private contextManager;
212
265
  private permissionHandler;
213
266
  private workingDirectory;
267
+ private maxConcurrentTools;
214
268
  private abortController;
269
+ private mcpClients;
270
+ private mcpConfig?;
271
+ private mcpInitialized;
272
+ private mcpInitPromise?;
215
273
  constructor(config: AgentConfig);
216
274
  /**
217
275
  * Run the agent loop. Yields events as the agent processes the input.
@@ -243,7 +301,18 @@ declare class Agent {
243
301
  removeTool(name: string): boolean;
244
302
  /** Replace the permission handler at runtime. */
245
303
  setPermissionHandler(handler: PermissionHandler): void;
246
- private executeToolCalls;
304
+ /** Get the list of active MCP clients. */
305
+ getMCPClients(): MCPClient[];
306
+ /**
307
+ * Disconnect all MCP servers and clean up resources.
308
+ * Call this when the agent is no longer needed.
309
+ */
310
+ disconnectMCP(): Promise<void>;
311
+ /**
312
+ * Connect to configured MCP servers and register their tools.
313
+ * Servers that fail to connect are skipped with a warning (non-fatal).
314
+ */
315
+ private initializeMCP;
247
316
  }
248
317
 
249
318
  /**
@@ -719,4 +788,4 @@ declare const PRESET_PROVIDERS: Record<string, ProviderRegistration>;
719
788
  */
720
789
  declare function createAuth(options?: AuthOptions): AuthRegistry;
721
790
 
722
- export { Agent, type AgentConfig, type AgentEvent, AnthropicProvider, type AssistantMessage, type AuthFlowProviderOption, type AuthFlowState, type AuthFlowStep, type AuthMethod, type AuthMethodApiKey, type AuthMethodBaseUrlKey, type AuthMethodNone, type AuthOptions, AuthRegistry, type AuthStorage, type AuthType, type ChatOptions, type CompactionResult, type CompactionStrategy, type ContentPart, ContextManager, type DoneEvent, type ErrorEvent, FileAuthStorage, FileSession, FileSessionStore, type ImageContentPart, InMemorySession, type LLMProvider, MemoryAuthStorage, type Message, MockProvider, NoopCompaction, OpenAIProvider, PRESET_PROVIDERS, type PermissionConfig, type PermissionHandler, type PermissionRequest, type PermissionResult, type ProviderInfo, type ProviderRegistration, type ProviderTool, type Session, SlidingWindowCompaction, type StreamChunk, SummarizationCompaction, type SystemMessage, type TextContentPart, type TextEvent, type ThinkingEvent, type ToolCall, type ToolCallEvent, type ToolContext, type ToolDefinition, type ToolProgress, ToolRegistry, type ToolResult, type ToolResultEvent, type ToolResultMessage, type UsageEvent, type UserMessage, allowAll, allowReadOnly, createAuth, createPermissionHandler, denyAll, estimateTokens, estimateTotalTokens };
791
+ export { Agent, type AgentConfig, type AgentEvent, AnthropicProvider, type AssistantMessage, type AuthFlowProviderOption, type AuthFlowState, type AuthFlowStep, type AuthMethod, type AuthMethodApiKey, type AuthMethodBaseUrlKey, type AuthMethodNone, type AuthOptions, AuthRegistry, type AuthStorage, type AuthType, type ChatOptions, type CompactionResult, type CompactionStrategy, type ContentPart, ContextManager, type DoneEvent, type ErrorEvent, FileAuthStorage, FileSession, FileSessionStore, type ImageContentPart, InMemorySession, type LLMProvider, MCPClient, type MCPConfig, type MCPHttpServerConfig, type MCPServerConfig, type MCPStdioServerConfig, MemoryAuthStorage, type Message, MockProvider, NoopCompaction, OpenAIProvider, PRESET_PROVIDERS, type PermissionConfig, type PermissionHandler, type PermissionRequest, type PermissionResult, type ProviderInfo, type ProviderRegistration, type ProviderTool, type Session, SlidingWindowCompaction, type StreamChunk, SummarizationCompaction, type SystemMessage, type TextContentPart, type TextEvent, type ThinkingEvent, type ToolCall, type ToolCallEvent, type ToolContext, type ToolDefinition, type ToolProgress, ToolRegistry, type ToolResult, type ToolResultEvent, type ToolResultMessage, type UsageEvent, type UserMessage, allowAll, allowReadOnly, createAuth, createPermissionHandler, denyAll, estimateTokens, estimateTotalTokens };
package/dist/index.d.ts CHANGED
@@ -73,6 +73,9 @@ interface ToolDefinition<TInput = unknown> {
73
73
  isDestructive?: boolean;
74
74
  requiresConfirmation?: boolean;
75
75
  timeout?: number;
76
+ /** Original JSON Schema from an MCP server, used by toolToProviderFormat() to
77
+ * avoid a lossy Zod -> JSON Schema round-trip for MCP-discovered tools. */
78
+ rawInputSchema?: Record<string, unknown>;
76
79
  }
77
80
  type StreamChunk = {
78
81
  type: "text";
@@ -178,11 +181,34 @@ interface Session {
178
181
  interface CompactionStrategy {
179
182
  compact(messages: Message[], maxTokens: number): Message[] | Promise<Message[]>;
180
183
  }
184
+ /** Stdio-based MCP server: spawns a subprocess. */
185
+ interface MCPStdioServerConfig {
186
+ name: string;
187
+ command: string;
188
+ args?: string[];
189
+ env?: Record<string, string>;
190
+ cwd?: string;
191
+ /** Timeout in ms for the initial connection (default: 30000). */
192
+ connectTimeout?: number;
193
+ }
194
+ /** HTTP-based MCP server: connects via Streamable HTTP transport. */
195
+ interface MCPHttpServerConfig {
196
+ name: string;
197
+ url: string;
198
+ headers?: Record<string, string>;
199
+ /** Timeout in ms for the initial connection (default: 30000). */
200
+ connectTimeout?: number;
201
+ }
202
+ type MCPServerConfig = MCPStdioServerConfig | MCPHttpServerConfig;
203
+ interface MCPConfig {
204
+ servers: MCPServerConfig[];
205
+ }
181
206
  interface AgentConfig {
182
207
  provider: LLMProvider;
183
208
  model: string;
184
209
  systemPrompt?: string;
185
210
  tools?: ToolDefinition[];
211
+ mcp?: MCPConfig;
186
212
  maxTokens?: number;
187
213
  temperature?: number;
188
214
  contextLimit?: number;
@@ -191,6 +217,33 @@ interface AgentConfig {
191
217
  permissionHandler?: PermissionHandler;
192
218
  workingDirectory?: string;
193
219
  maxTurns?: number;
220
+ /** Max number of tools to execute concurrently (default: 5). */
221
+ maxConcurrentTools?: number;
222
+ }
223
+
224
+ declare class MCPClient {
225
+ private config;
226
+ private client;
227
+ private transport;
228
+ private _tools;
229
+ private _connected;
230
+ constructor(config: MCPServerConfig);
231
+ get name(): string;
232
+ get connected(): boolean;
233
+ get tools(): ToolDefinition[];
234
+ /**
235
+ * Connect to the MCP server and discover available tools.
236
+ * Throws if the SDK is not installed or the server fails to connect.
237
+ */
238
+ connect(): Promise<void>;
239
+ /**
240
+ * Refresh the tool list from the server.
241
+ */
242
+ discoverTools(): Promise<ToolDefinition[]>;
243
+ /**
244
+ * Disconnect from the MCP server and clean up resources.
245
+ */
246
+ disconnect(): Promise<void>;
194
247
  }
195
248
 
196
249
  /**
@@ -211,7 +264,12 @@ declare class Agent {
211
264
  private contextManager;
212
265
  private permissionHandler;
213
266
  private workingDirectory;
267
+ private maxConcurrentTools;
214
268
  private abortController;
269
+ private mcpClients;
270
+ private mcpConfig?;
271
+ private mcpInitialized;
272
+ private mcpInitPromise?;
215
273
  constructor(config: AgentConfig);
216
274
  /**
217
275
  * Run the agent loop. Yields events as the agent processes the input.
@@ -243,7 +301,18 @@ declare class Agent {
243
301
  removeTool(name: string): boolean;
244
302
  /** Replace the permission handler at runtime. */
245
303
  setPermissionHandler(handler: PermissionHandler): void;
246
- private executeToolCalls;
304
+ /** Get the list of active MCP clients. */
305
+ getMCPClients(): MCPClient[];
306
+ /**
307
+ * Disconnect all MCP servers and clean up resources.
308
+ * Call this when the agent is no longer needed.
309
+ */
310
+ disconnectMCP(): Promise<void>;
311
+ /**
312
+ * Connect to configured MCP servers and register their tools.
313
+ * Servers that fail to connect are skipped with a warning (non-fatal).
314
+ */
315
+ private initializeMCP;
247
316
  }
248
317
 
249
318
  /**
@@ -719,4 +788,4 @@ declare const PRESET_PROVIDERS: Record<string, ProviderRegistration>;
719
788
  */
720
789
  declare function createAuth(options?: AuthOptions): AuthRegistry;
721
790
 
722
- export { Agent, type AgentConfig, type AgentEvent, AnthropicProvider, type AssistantMessage, type AuthFlowProviderOption, type AuthFlowState, type AuthFlowStep, type AuthMethod, type AuthMethodApiKey, type AuthMethodBaseUrlKey, type AuthMethodNone, type AuthOptions, AuthRegistry, type AuthStorage, type AuthType, type ChatOptions, type CompactionResult, type CompactionStrategy, type ContentPart, ContextManager, type DoneEvent, type ErrorEvent, FileAuthStorage, FileSession, FileSessionStore, type ImageContentPart, InMemorySession, type LLMProvider, MemoryAuthStorage, type Message, MockProvider, NoopCompaction, OpenAIProvider, PRESET_PROVIDERS, type PermissionConfig, type PermissionHandler, type PermissionRequest, type PermissionResult, type ProviderInfo, type ProviderRegistration, type ProviderTool, type Session, SlidingWindowCompaction, type StreamChunk, SummarizationCompaction, type SystemMessage, type TextContentPart, type TextEvent, type ThinkingEvent, type ToolCall, type ToolCallEvent, type ToolContext, type ToolDefinition, type ToolProgress, ToolRegistry, type ToolResult, type ToolResultEvent, type ToolResultMessage, type UsageEvent, type UserMessage, allowAll, allowReadOnly, createAuth, createPermissionHandler, denyAll, estimateTokens, estimateTotalTokens };
791
+ export { Agent, type AgentConfig, type AgentEvent, AnthropicProvider, type AssistantMessage, type AuthFlowProviderOption, type AuthFlowState, type AuthFlowStep, type AuthMethod, type AuthMethodApiKey, type AuthMethodBaseUrlKey, type AuthMethodNone, type AuthOptions, AuthRegistry, type AuthStorage, type AuthType, type ChatOptions, type CompactionResult, type CompactionStrategy, type ContentPart, ContextManager, type DoneEvent, type ErrorEvent, FileAuthStorage, FileSession, FileSessionStore, type ImageContentPart, InMemorySession, type LLMProvider, MCPClient, type MCPConfig, type MCPHttpServerConfig, type MCPServerConfig, type MCPStdioServerConfig, MemoryAuthStorage, type Message, MockProvider, NoopCompaction, OpenAIProvider, PRESET_PROVIDERS, type PermissionConfig, type PermissionHandler, type PermissionRequest, type PermissionResult, type ProviderInfo, type ProviderRegistration, type ProviderTool, type Session, SlidingWindowCompaction, type StreamChunk, SummarizationCompaction, type SystemMessage, type TextContentPart, type TextEvent, type ThinkingEvent, type ToolCall, type ToolCallEvent, type ToolContext, type ToolDefinition, type ToolProgress, ToolRegistry, type ToolResult, type ToolResultEvent, type ToolResultMessage, type UsageEvent, type UserMessage, allowAll, allowReadOnly, createAuth, createPermissionHandler, denyAll, estimateTokens, estimateTotalTokens };
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ __export(index_exports, {
38
38
  FileSession: () => FileSession,
39
39
  FileSessionStore: () => FileSessionStore,
40
40
  InMemorySession: () => InMemorySession,
41
+ MCPClient: () => MCPClient,
41
42
  MemoryAuthStorage: () => MemoryAuthStorage,
42
43
  MockProvider: () => MockProvider,
43
44
  NoopCompaction: () => NoopCompaction,
@@ -134,6 +135,284 @@ var ContextManager = class {
134
135
  }
135
136
  };
136
137
 
138
+ // src/mcp-client.ts
139
+ var import_zod = require("zod");
140
+ var VALID_SERVER_NAME = /^[a-zA-Z0-9]+([_-][a-zA-Z0-9]+)*$/;
141
+ var DEFAULT_CONNECT_TIMEOUT = 3e4;
142
+ var MCPClient = class {
143
+ config;
144
+ client = null;
145
+ transport = null;
146
+ _tools = [];
147
+ _connected = false;
148
+ constructor(config) {
149
+ if (!VALID_SERVER_NAME.test(config.name)) {
150
+ throw new Error(
151
+ `Invalid MCP server name "${config.name}": must match [a-zA-Z0-9_-] with no consecutive underscores (__).`
152
+ );
153
+ }
154
+ this.config = config;
155
+ }
156
+ get name() {
157
+ return this.config.name;
158
+ }
159
+ get connected() {
160
+ return this._connected;
161
+ }
162
+ get tools() {
163
+ return this._tools;
164
+ }
165
+ /**
166
+ * Connect to the MCP server and discover available tools.
167
+ * Throws if the SDK is not installed or the server fails to connect.
168
+ */
169
+ async connect() {
170
+ if (this._connected) return;
171
+ const sdk = await loadMCPSdk();
172
+ this.client = new sdk.Client(
173
+ { name: "claude-code-kit", version: "0.2.0" },
174
+ { capabilities: {} }
175
+ );
176
+ this.transport = isStdioConfig(this.config) ? new sdk.StdioClientTransport({
177
+ command: this.config.command,
178
+ args: this.config.args,
179
+ env: this.config.env,
180
+ cwd: this.config.cwd,
181
+ stderr: "pipe"
182
+ }) : new sdk.StreamableHTTPClientTransport(new URL(this.config.url), {
183
+ requestInit: this.config.headers ? { headers: this.config.headers } : void 0
184
+ });
185
+ const timeout = this.config.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
186
+ const connectPromise = this.client.connect(this.transport);
187
+ const timeoutPromise = new Promise((_, reject) => {
188
+ setTimeout(
189
+ () => reject(new Error(`MCP server "${this.config.name}" connection timed out after ${timeout}ms`)),
190
+ timeout
191
+ );
192
+ });
193
+ await Promise.race([connectPromise, timeoutPromise]);
194
+ this._connected = true;
195
+ await this.discoverTools();
196
+ }
197
+ /**
198
+ * Refresh the tool list from the server.
199
+ */
200
+ async discoverTools() {
201
+ if (!this.client) {
202
+ throw new Error(`MCP client "${this.config.name}" is not connected`);
203
+ }
204
+ const result = await this.client.listTools();
205
+ const serverName = this.config.name;
206
+ this._tools = result.tools.map(
207
+ (mcpTool) => convertMCPTool(mcpTool, serverName, this.client)
208
+ );
209
+ return this._tools;
210
+ }
211
+ /**
212
+ * Disconnect from the MCP server and clean up resources.
213
+ */
214
+ async disconnect() {
215
+ if (!this._connected) return;
216
+ try {
217
+ await this.transport?.close();
218
+ } catch {
219
+ }
220
+ this.client = null;
221
+ this.transport = null;
222
+ this._tools = [];
223
+ this._connected = false;
224
+ }
225
+ };
226
+ function convertMCPTool(mcpTool, serverName, client) {
227
+ const qualifiedName = `mcp__${serverName}__${mcpTool.name}`;
228
+ const inputSchema = import_zod.z.record(import_zod.z.string(), import_zod.z.unknown());
229
+ const originalJsonSchema = mcpTool.inputSchema;
230
+ const isReadOnly = mcpTool.annotations?.readOnlyHint === true;
231
+ const isDestructive = mcpTool.annotations?.destructiveHint === true;
232
+ const tool = {
233
+ name: qualifiedName,
234
+ description: mcpTool.description ?? `MCP tool from ${serverName}`,
235
+ inputSchema,
236
+ isReadOnly,
237
+ isDestructive,
238
+ rawInputSchema: originalJsonSchema,
239
+ async execute(input, _context) {
240
+ try {
241
+ const result = await client.callTool({
242
+ name: mcpTool.name,
243
+ arguments: input
244
+ });
245
+ const content = extractTextContent(result);
246
+ const isError = "isError" in result ? result.isError === true : false;
247
+ return { content, isError };
248
+ } catch (error) {
249
+ const message = error instanceof Error ? error.message : String(error);
250
+ return {
251
+ content: `MCP tool "${mcpTool.name}" (${serverName}) failed: ${message}`,
252
+ isError: true
253
+ };
254
+ }
255
+ }
256
+ };
257
+ return tool;
258
+ }
259
+ function extractTextContent(result) {
260
+ if (!result.content || !Array.isArray(result.content)) {
261
+ return JSON.stringify(result);
262
+ }
263
+ const parts = [];
264
+ for (const part of result.content) {
265
+ if (typeof part === "object" && part !== null) {
266
+ if ("text" in part && typeof part.text === "string") {
267
+ parts.push(part.text);
268
+ } else if ("data" in part && typeof part.data === "string") {
269
+ const mimeType = "mimeType" in part && typeof part.mimeType === "string" ? part.mimeType : "unknown";
270
+ parts.push(`[binary content: ${mimeType}]`);
271
+ } else {
272
+ parts.push(JSON.stringify(part));
273
+ }
274
+ }
275
+ }
276
+ return parts.join("\n") || "(empty result)";
277
+ }
278
+ var _sdkCache;
279
+ async function loadMCPSdk() {
280
+ if (_sdkCache) return _sdkCache;
281
+ try {
282
+ const clientMod = await import("@modelcontextprotocol/sdk/client");
283
+ const stdioMod = await import("@modelcontextprotocol/sdk/client/stdio.js");
284
+ const httpMod = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
285
+ const sdk = {
286
+ Client: clientMod.Client,
287
+ StdioClientTransport: stdioMod.StdioClientTransport,
288
+ StreamableHTTPClientTransport: httpMod.StreamableHTTPClientTransport
289
+ };
290
+ _sdkCache = sdk;
291
+ return sdk;
292
+ } catch {
293
+ throw new Error(
294
+ "MCP support requires @modelcontextprotocol/sdk. Install it: pnpm add @modelcontextprotocol/sdk"
295
+ );
296
+ }
297
+ }
298
+ function isStdioConfig(config) {
299
+ return "command" in config;
300
+ }
301
+
302
+ // src/parallel-tools.ts
303
+ var DEFAULT_MAX_CONCURRENT = 5;
304
+ async function executeToolCalls(options) {
305
+ const {
306
+ toolCalls,
307
+ toolRegistry,
308
+ permissionHandler,
309
+ context,
310
+ parseErrors,
311
+ maxConcurrent = DEFAULT_MAX_CONCURRENT
312
+ } = options;
313
+ const results = new Array(toolCalls.length);
314
+ const executionPlan = buildExecutionPlan(toolCalls, toolRegistry);
315
+ for (const group of executionPlan) {
316
+ if (group.kind === "parallel") {
317
+ await executeParallelGroup(group.entries, results, {
318
+ toolRegistry,
319
+ permissionHandler,
320
+ context,
321
+ parseErrors,
322
+ maxConcurrent
323
+ });
324
+ } else {
325
+ const entry = group.entries[0];
326
+ results[entry.index] = await executeSingleToolCall(
327
+ entry.toolCall,
328
+ toolRegistry,
329
+ permissionHandler,
330
+ context,
331
+ parseErrors
332
+ );
333
+ }
334
+ }
335
+ return results;
336
+ }
337
+ function buildExecutionPlan(toolCalls, registry) {
338
+ const groups = [];
339
+ let currentParallelBatch = [];
340
+ for (let i = 0; i < toolCalls.length; i++) {
341
+ const tc = toolCalls[i];
342
+ const toolDef = registry.get(tc.name);
343
+ const isReadOnly = toolDef?.isReadOnly === true;
344
+ if (isReadOnly) {
345
+ currentParallelBatch.push({ index: i, toolCall: tc });
346
+ } else {
347
+ if (currentParallelBatch.length > 0) {
348
+ groups.push({ kind: "parallel", entries: currentParallelBatch });
349
+ currentParallelBatch = [];
350
+ }
351
+ groups.push({ kind: "sequential", entries: [{ index: i, toolCall: tc }] });
352
+ }
353
+ }
354
+ if (currentParallelBatch.length > 0) {
355
+ groups.push({ kind: "parallel", entries: currentParallelBatch });
356
+ }
357
+ return groups;
358
+ }
359
+ async function executeParallelGroup(entries, results, opts) {
360
+ for (let i = 0; i < entries.length; i += opts.maxConcurrent) {
361
+ const batch = entries.slice(i, i + opts.maxConcurrent);
362
+ const promises = batch.map(async (entry) => {
363
+ results[entry.index] = await executeSingleToolCall(
364
+ entry.toolCall,
365
+ opts.toolRegistry,
366
+ opts.permissionHandler,
367
+ opts.context,
368
+ opts.parseErrors
369
+ );
370
+ });
371
+ await Promise.all(promises);
372
+ }
373
+ }
374
+ async function executeSingleToolCall(tc, toolRegistry, permissionHandler, context, parseErrors) {
375
+ const parseError = parseErrors?.get(tc.id);
376
+ if (parseError) {
377
+ return {
378
+ role: "tool",
379
+ toolCallId: tc.id,
380
+ content: parseError,
381
+ isError: true
382
+ };
383
+ }
384
+ try {
385
+ const toolDef = toolRegistry.get(tc.name);
386
+ const permissionResult = await permissionHandler({
387
+ tool: tc.name,
388
+ input: tc.input,
389
+ isReadOnly: toolDef?.isReadOnly
390
+ });
391
+ if (permissionResult.decision === "deny") {
392
+ return {
393
+ role: "tool",
394
+ toolCallId: tc.id,
395
+ content: `Permission denied for tool "${tc.name}"${permissionResult.reason ? `: ${permissionResult.reason}` : ""}`,
396
+ isError: true
397
+ };
398
+ }
399
+ const result = await toolRegistry.execute(tc.name, tc.input, context);
400
+ return {
401
+ role: "tool",
402
+ toolCallId: tc.id,
403
+ content: result.content,
404
+ isError: result.isError
405
+ };
406
+ } catch (error) {
407
+ return {
408
+ role: "tool",
409
+ toolCallId: tc.id,
410
+ content: `Permission error: ${error instanceof Error ? error.message : String(error)}`,
411
+ isError: true
412
+ };
413
+ }
414
+ }
415
+
137
416
  // src/permission.ts
138
417
  function createPermissionHandler(config) {
139
418
  return async (request) => {
@@ -185,10 +464,10 @@ var InMemorySession = class {
185
464
  };
186
465
 
187
466
  // src/tool-formatter.ts
188
- var import_zod = require("zod");
467
+ var import_zod2 = require("zod");
189
468
  function zodToInputSchema(schema) {
190
- if (typeof import_zod.z.toJSONSchema === "function") {
191
- const jsonSchema = import_zod.z.toJSONSchema(schema);
469
+ if (typeof import_zod2.z.toJSONSchema === "function") {
470
+ const jsonSchema = import_zod2.z.toJSONSchema(schema);
192
471
  const { $schema: _, ...rest } = jsonSchema;
193
472
  return rest;
194
473
  }
@@ -205,7 +484,7 @@ function toolToProviderFormat(tool) {
205
484
  return {
206
485
  name: tool.name,
207
486
  description: tool.description,
208
- inputSchema: zodToInputSchema(tool.inputSchema)
487
+ inputSchema: tool.rawInputSchema ?? zodToInputSchema(tool.inputSchema)
209
488
  };
210
489
  }
211
490
 
@@ -269,6 +548,7 @@ var ToolRegistry = class {
269
548
 
270
549
  // src/agent.ts
271
550
  var DEFAULT_MAX_TURNS = 50;
551
+ var DEFAULT_MAX_CONCURRENT_TOOLS = 5;
272
552
  var Agent = class {
273
553
  provider;
274
554
  model;
@@ -281,7 +561,12 @@ var Agent = class {
281
561
  contextManager;
282
562
  permissionHandler;
283
563
  workingDirectory;
564
+ maxConcurrentTools;
284
565
  abortController = null;
566
+ mcpClients = [];
567
+ mcpConfig;
568
+ mcpInitialized = false;
569
+ mcpInitPromise;
285
570
  constructor(config) {
286
571
  this.provider = config.provider;
287
572
  this.model = config.model;
@@ -289,9 +574,11 @@ var Agent = class {
289
574
  this.maxTokens = config.maxTokens;
290
575
  this.temperature = config.temperature;
291
576
  this.maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
577
+ this.maxConcurrentTools = config.maxConcurrentTools ?? DEFAULT_MAX_CONCURRENT_TOOLS;
292
578
  this.session = config.session ?? new InMemorySession();
293
579
  this.permissionHandler = config.permissionHandler ?? allowReadOnly;
294
580
  this.workingDirectory = config.workingDirectory ?? process.cwd();
581
+ this.mcpConfig = config.mcp;
295
582
  this.toolRegistry = new ToolRegistry();
296
583
  if (config.tools) {
297
584
  for (const tool of config.tools) {
@@ -317,6 +604,10 @@ var Agent = class {
317
604
  */
318
605
  async *run(input) {
319
606
  this.abortController = new AbortController();
607
+ if (this.mcpConfig && !this.mcpInitialized) {
608
+ this.mcpInitPromise ??= this.initializeMCP();
609
+ await this.mcpInitPromise;
610
+ }
320
611
  const messages = this.session.getMessages();
321
612
  if (typeof input === "string") {
322
613
  messages.push({ role: "user", content: input });
@@ -429,7 +720,17 @@ var Agent = class {
429
720
  msgs.push(assistantMessage);
430
721
  this.session.setMessages(msgs);
431
722
  if (accumulatedToolCalls.length > 0) {
432
- const toolResults = await this.executeToolCalls(accumulatedToolCalls, toolParseErrors);
723
+ const toolResults = await executeToolCalls({
724
+ toolCalls: accumulatedToolCalls,
725
+ toolRegistry: this.toolRegistry,
726
+ permissionHandler: this.permissionHandler,
727
+ context: {
728
+ workingDirectory: this.workingDirectory,
729
+ abortSignal: this.abortController?.signal ?? AbortSignal.timeout(12e4)
730
+ },
731
+ parseErrors: toolParseErrors,
732
+ maxConcurrent: this.maxConcurrentTools
733
+ });
433
734
  const currentMsgs = this.session.getMessages();
434
735
  for (const result of toolResults) {
435
736
  yield {
@@ -497,50 +798,72 @@ var Agent = class {
497
798
  setPermissionHandler(handler) {
498
799
  this.permissionHandler = handler;
499
800
  }
801
+ /** Get the list of active MCP clients. */
802
+ getMCPClients() {
803
+ return [...this.mcpClients];
804
+ }
805
+ /**
806
+ * Disconnect all MCP servers and clean up resources.
807
+ * Call this when the agent is no longer needed.
808
+ */
809
+ async disconnectMCP() {
810
+ const toolNames = [];
811
+ for (const client of this.mcpClients) {
812
+ for (const tool of client.tools) {
813
+ toolNames.push(tool.name);
814
+ }
815
+ }
816
+ for (const name of toolNames) {
817
+ this.toolRegistry.unregister(name);
818
+ }
819
+ const errors = [];
820
+ for (const client of this.mcpClients) {
821
+ try {
822
+ await client.disconnect();
823
+ } catch (error) {
824
+ errors.push(
825
+ error instanceof Error ? error : new Error(String(error))
826
+ );
827
+ }
828
+ }
829
+ this.mcpClients = [];
830
+ this.mcpInitialized = false;
831
+ this.mcpInitPromise = void 0;
832
+ if (errors.length > 0) {
833
+ throw new AggregateError(errors, "Some MCP servers failed to disconnect");
834
+ }
835
+ }
500
836
  // -------------------------------------------------------------------------
501
837
  // Private helpers
502
838
  // -------------------------------------------------------------------------
503
- async executeToolCalls(toolCalls, parseErrors) {
504
- const results = [];
505
- for (const tc of toolCalls) {
506
- const parseError = parseErrors?.get(tc.id);
507
- if (parseError) {
508
- results.push({
509
- role: "tool",
510
- toolCallId: tc.id,
511
- content: parseError,
512
- isError: true
513
- });
514
- continue;
515
- }
516
- const toolDef = this.toolRegistry.get(tc.name);
517
- const permissionResult = await this.permissionHandler({
518
- tool: tc.name,
519
- input: tc.input,
520
- isReadOnly: toolDef?.isReadOnly
521
- });
522
- if (permissionResult.decision === "deny") {
523
- results.push({
524
- role: "tool",
525
- toolCallId: tc.id,
526
- content: `Permission denied for tool "${tc.name}"${permissionResult.reason ? `: ${permissionResult.reason}` : ""}`,
527
- isError: true
528
- });
529
- continue;
839
+ /**
840
+ * Connect to configured MCP servers and register their tools.
841
+ * Servers that fail to connect are skipped with a warning (non-fatal).
842
+ */
843
+ async initializeMCP() {
844
+ if (!this.mcpConfig?.servers.length) {
845
+ this.mcpInitialized = true;
846
+ return;
847
+ }
848
+ const results = await Promise.allSettled(
849
+ this.mcpConfig.servers.map(async (serverConfig) => {
850
+ const client = new MCPClient(serverConfig);
851
+ await client.connect();
852
+ return client;
853
+ })
854
+ );
855
+ for (const result of results) {
856
+ if (result.status === "fulfilled") {
857
+ const client = result.value;
858
+ this.mcpClients.push(client);
859
+ for (const tool of client.tools) {
860
+ if (!this.toolRegistry.has(tool.name)) {
861
+ this.toolRegistry.register(tool);
862
+ }
863
+ }
530
864
  }
531
- const context = {
532
- workingDirectory: this.workingDirectory,
533
- abortSignal: this.abortController?.signal ?? AbortSignal.timeout(12e4)
534
- };
535
- const result = await this.toolRegistry.execute(tc.name, tc.input, context);
536
- results.push({
537
- role: "tool",
538
- toolCallId: tc.id,
539
- content: result.content,
540
- isError: result.isError
541
- });
542
865
  }
543
- return results;
866
+ this.mcpInitialized = true;
544
867
  }
545
868
  };
546
869
  function isContextTooLongError(error) {
@@ -1704,6 +2027,7 @@ function createAuth(options) {
1704
2027
  FileSession,
1705
2028
  FileSessionStore,
1706
2029
  InMemorySession,
2030
+ MCPClient,
1707
2031
  MemoryAuthStorage,
1708
2032
  MockProvider,
1709
2033
  NoopCompaction,
package/dist/index.mjs CHANGED
@@ -83,6 +83,284 @@ var ContextManager = class {
83
83
  }
84
84
  };
85
85
 
86
+ // src/mcp-client.ts
87
+ import { z } from "zod";
88
+ var VALID_SERVER_NAME = /^[a-zA-Z0-9]+([_-][a-zA-Z0-9]+)*$/;
89
+ var DEFAULT_CONNECT_TIMEOUT = 3e4;
90
+ var MCPClient = class {
91
+ config;
92
+ client = null;
93
+ transport = null;
94
+ _tools = [];
95
+ _connected = false;
96
+ constructor(config) {
97
+ if (!VALID_SERVER_NAME.test(config.name)) {
98
+ throw new Error(
99
+ `Invalid MCP server name "${config.name}": must match [a-zA-Z0-9_-] with no consecutive underscores (__).`
100
+ );
101
+ }
102
+ this.config = config;
103
+ }
104
+ get name() {
105
+ return this.config.name;
106
+ }
107
+ get connected() {
108
+ return this._connected;
109
+ }
110
+ get tools() {
111
+ return this._tools;
112
+ }
113
+ /**
114
+ * Connect to the MCP server and discover available tools.
115
+ * Throws if the SDK is not installed or the server fails to connect.
116
+ */
117
+ async connect() {
118
+ if (this._connected) return;
119
+ const sdk = await loadMCPSdk();
120
+ this.client = new sdk.Client(
121
+ { name: "claude-code-kit", version: "0.2.0" },
122
+ { capabilities: {} }
123
+ );
124
+ this.transport = isStdioConfig(this.config) ? new sdk.StdioClientTransport({
125
+ command: this.config.command,
126
+ args: this.config.args,
127
+ env: this.config.env,
128
+ cwd: this.config.cwd,
129
+ stderr: "pipe"
130
+ }) : new sdk.StreamableHTTPClientTransport(new URL(this.config.url), {
131
+ requestInit: this.config.headers ? { headers: this.config.headers } : void 0
132
+ });
133
+ const timeout = this.config.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
134
+ const connectPromise = this.client.connect(this.transport);
135
+ const timeoutPromise = new Promise((_, reject) => {
136
+ setTimeout(
137
+ () => reject(new Error(`MCP server "${this.config.name}" connection timed out after ${timeout}ms`)),
138
+ timeout
139
+ );
140
+ });
141
+ await Promise.race([connectPromise, timeoutPromise]);
142
+ this._connected = true;
143
+ await this.discoverTools();
144
+ }
145
+ /**
146
+ * Refresh the tool list from the server.
147
+ */
148
+ async discoverTools() {
149
+ if (!this.client) {
150
+ throw new Error(`MCP client "${this.config.name}" is not connected`);
151
+ }
152
+ const result = await this.client.listTools();
153
+ const serverName = this.config.name;
154
+ this._tools = result.tools.map(
155
+ (mcpTool) => convertMCPTool(mcpTool, serverName, this.client)
156
+ );
157
+ return this._tools;
158
+ }
159
+ /**
160
+ * Disconnect from the MCP server and clean up resources.
161
+ */
162
+ async disconnect() {
163
+ if (!this._connected) return;
164
+ try {
165
+ await this.transport?.close();
166
+ } catch {
167
+ }
168
+ this.client = null;
169
+ this.transport = null;
170
+ this._tools = [];
171
+ this._connected = false;
172
+ }
173
+ };
174
+ function convertMCPTool(mcpTool, serverName, client) {
175
+ const qualifiedName = `mcp__${serverName}__${mcpTool.name}`;
176
+ const inputSchema = z.record(z.string(), z.unknown());
177
+ const originalJsonSchema = mcpTool.inputSchema;
178
+ const isReadOnly = mcpTool.annotations?.readOnlyHint === true;
179
+ const isDestructive = mcpTool.annotations?.destructiveHint === true;
180
+ const tool = {
181
+ name: qualifiedName,
182
+ description: mcpTool.description ?? `MCP tool from ${serverName}`,
183
+ inputSchema,
184
+ isReadOnly,
185
+ isDestructive,
186
+ rawInputSchema: originalJsonSchema,
187
+ async execute(input, _context) {
188
+ try {
189
+ const result = await client.callTool({
190
+ name: mcpTool.name,
191
+ arguments: input
192
+ });
193
+ const content = extractTextContent(result);
194
+ const isError = "isError" in result ? result.isError === true : false;
195
+ return { content, isError };
196
+ } catch (error) {
197
+ const message = error instanceof Error ? error.message : String(error);
198
+ return {
199
+ content: `MCP tool "${mcpTool.name}" (${serverName}) failed: ${message}`,
200
+ isError: true
201
+ };
202
+ }
203
+ }
204
+ };
205
+ return tool;
206
+ }
207
+ function extractTextContent(result) {
208
+ if (!result.content || !Array.isArray(result.content)) {
209
+ return JSON.stringify(result);
210
+ }
211
+ const parts = [];
212
+ for (const part of result.content) {
213
+ if (typeof part === "object" && part !== null) {
214
+ if ("text" in part && typeof part.text === "string") {
215
+ parts.push(part.text);
216
+ } else if ("data" in part && typeof part.data === "string") {
217
+ const mimeType = "mimeType" in part && typeof part.mimeType === "string" ? part.mimeType : "unknown";
218
+ parts.push(`[binary content: ${mimeType}]`);
219
+ } else {
220
+ parts.push(JSON.stringify(part));
221
+ }
222
+ }
223
+ }
224
+ return parts.join("\n") || "(empty result)";
225
+ }
226
+ var _sdkCache;
227
+ async function loadMCPSdk() {
228
+ if (_sdkCache) return _sdkCache;
229
+ try {
230
+ const clientMod = await import("@modelcontextprotocol/sdk/client");
231
+ const stdioMod = await import("@modelcontextprotocol/sdk/client/stdio.js");
232
+ const httpMod = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
233
+ const sdk = {
234
+ Client: clientMod.Client,
235
+ StdioClientTransport: stdioMod.StdioClientTransport,
236
+ StreamableHTTPClientTransport: httpMod.StreamableHTTPClientTransport
237
+ };
238
+ _sdkCache = sdk;
239
+ return sdk;
240
+ } catch {
241
+ throw new Error(
242
+ "MCP support requires @modelcontextprotocol/sdk. Install it: pnpm add @modelcontextprotocol/sdk"
243
+ );
244
+ }
245
+ }
246
+ function isStdioConfig(config) {
247
+ return "command" in config;
248
+ }
249
+
250
+ // src/parallel-tools.ts
251
+ var DEFAULT_MAX_CONCURRENT = 5;
252
+ async function executeToolCalls(options) {
253
+ const {
254
+ toolCalls,
255
+ toolRegistry,
256
+ permissionHandler,
257
+ context,
258
+ parseErrors,
259
+ maxConcurrent = DEFAULT_MAX_CONCURRENT
260
+ } = options;
261
+ const results = new Array(toolCalls.length);
262
+ const executionPlan = buildExecutionPlan(toolCalls, toolRegistry);
263
+ for (const group of executionPlan) {
264
+ if (group.kind === "parallel") {
265
+ await executeParallelGroup(group.entries, results, {
266
+ toolRegistry,
267
+ permissionHandler,
268
+ context,
269
+ parseErrors,
270
+ maxConcurrent
271
+ });
272
+ } else {
273
+ const entry = group.entries[0];
274
+ results[entry.index] = await executeSingleToolCall(
275
+ entry.toolCall,
276
+ toolRegistry,
277
+ permissionHandler,
278
+ context,
279
+ parseErrors
280
+ );
281
+ }
282
+ }
283
+ return results;
284
+ }
285
+ function buildExecutionPlan(toolCalls, registry) {
286
+ const groups = [];
287
+ let currentParallelBatch = [];
288
+ for (let i = 0; i < toolCalls.length; i++) {
289
+ const tc = toolCalls[i];
290
+ const toolDef = registry.get(tc.name);
291
+ const isReadOnly = toolDef?.isReadOnly === true;
292
+ if (isReadOnly) {
293
+ currentParallelBatch.push({ index: i, toolCall: tc });
294
+ } else {
295
+ if (currentParallelBatch.length > 0) {
296
+ groups.push({ kind: "parallel", entries: currentParallelBatch });
297
+ currentParallelBatch = [];
298
+ }
299
+ groups.push({ kind: "sequential", entries: [{ index: i, toolCall: tc }] });
300
+ }
301
+ }
302
+ if (currentParallelBatch.length > 0) {
303
+ groups.push({ kind: "parallel", entries: currentParallelBatch });
304
+ }
305
+ return groups;
306
+ }
307
+ async function executeParallelGroup(entries, results, opts) {
308
+ for (let i = 0; i < entries.length; i += opts.maxConcurrent) {
309
+ const batch = entries.slice(i, i + opts.maxConcurrent);
310
+ const promises = batch.map(async (entry) => {
311
+ results[entry.index] = await executeSingleToolCall(
312
+ entry.toolCall,
313
+ opts.toolRegistry,
314
+ opts.permissionHandler,
315
+ opts.context,
316
+ opts.parseErrors
317
+ );
318
+ });
319
+ await Promise.all(promises);
320
+ }
321
+ }
322
+ async function executeSingleToolCall(tc, toolRegistry, permissionHandler, context, parseErrors) {
323
+ const parseError = parseErrors?.get(tc.id);
324
+ if (parseError) {
325
+ return {
326
+ role: "tool",
327
+ toolCallId: tc.id,
328
+ content: parseError,
329
+ isError: true
330
+ };
331
+ }
332
+ try {
333
+ const toolDef = toolRegistry.get(tc.name);
334
+ const permissionResult = await permissionHandler({
335
+ tool: tc.name,
336
+ input: tc.input,
337
+ isReadOnly: toolDef?.isReadOnly
338
+ });
339
+ if (permissionResult.decision === "deny") {
340
+ return {
341
+ role: "tool",
342
+ toolCallId: tc.id,
343
+ content: `Permission denied for tool "${tc.name}"${permissionResult.reason ? `: ${permissionResult.reason}` : ""}`,
344
+ isError: true
345
+ };
346
+ }
347
+ const result = await toolRegistry.execute(tc.name, tc.input, context);
348
+ return {
349
+ role: "tool",
350
+ toolCallId: tc.id,
351
+ content: result.content,
352
+ isError: result.isError
353
+ };
354
+ } catch (error) {
355
+ return {
356
+ role: "tool",
357
+ toolCallId: tc.id,
358
+ content: `Permission error: ${error instanceof Error ? error.message : String(error)}`,
359
+ isError: true
360
+ };
361
+ }
362
+ }
363
+
86
364
  // src/permission.ts
87
365
  function createPermissionHandler(config) {
88
366
  return async (request) => {
@@ -134,10 +412,10 @@ var InMemorySession = class {
134
412
  };
135
413
 
136
414
  // src/tool-formatter.ts
137
- import { z } from "zod";
415
+ import { z as z2 } from "zod";
138
416
  function zodToInputSchema(schema) {
139
- if (typeof z.toJSONSchema === "function") {
140
- const jsonSchema = z.toJSONSchema(schema);
417
+ if (typeof z2.toJSONSchema === "function") {
418
+ const jsonSchema = z2.toJSONSchema(schema);
141
419
  const { $schema: _, ...rest } = jsonSchema;
142
420
  return rest;
143
421
  }
@@ -154,7 +432,7 @@ function toolToProviderFormat(tool) {
154
432
  return {
155
433
  name: tool.name,
156
434
  description: tool.description,
157
- inputSchema: zodToInputSchema(tool.inputSchema)
435
+ inputSchema: tool.rawInputSchema ?? zodToInputSchema(tool.inputSchema)
158
436
  };
159
437
  }
160
438
 
@@ -218,6 +496,7 @@ var ToolRegistry = class {
218
496
 
219
497
  // src/agent.ts
220
498
  var DEFAULT_MAX_TURNS = 50;
499
+ var DEFAULT_MAX_CONCURRENT_TOOLS = 5;
221
500
  var Agent = class {
222
501
  provider;
223
502
  model;
@@ -230,7 +509,12 @@ var Agent = class {
230
509
  contextManager;
231
510
  permissionHandler;
232
511
  workingDirectory;
512
+ maxConcurrentTools;
233
513
  abortController = null;
514
+ mcpClients = [];
515
+ mcpConfig;
516
+ mcpInitialized = false;
517
+ mcpInitPromise;
234
518
  constructor(config) {
235
519
  this.provider = config.provider;
236
520
  this.model = config.model;
@@ -238,9 +522,11 @@ var Agent = class {
238
522
  this.maxTokens = config.maxTokens;
239
523
  this.temperature = config.temperature;
240
524
  this.maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
525
+ this.maxConcurrentTools = config.maxConcurrentTools ?? DEFAULT_MAX_CONCURRENT_TOOLS;
241
526
  this.session = config.session ?? new InMemorySession();
242
527
  this.permissionHandler = config.permissionHandler ?? allowReadOnly;
243
528
  this.workingDirectory = config.workingDirectory ?? process.cwd();
529
+ this.mcpConfig = config.mcp;
244
530
  this.toolRegistry = new ToolRegistry();
245
531
  if (config.tools) {
246
532
  for (const tool of config.tools) {
@@ -266,6 +552,10 @@ var Agent = class {
266
552
  */
267
553
  async *run(input) {
268
554
  this.abortController = new AbortController();
555
+ if (this.mcpConfig && !this.mcpInitialized) {
556
+ this.mcpInitPromise ??= this.initializeMCP();
557
+ await this.mcpInitPromise;
558
+ }
269
559
  const messages = this.session.getMessages();
270
560
  if (typeof input === "string") {
271
561
  messages.push({ role: "user", content: input });
@@ -378,7 +668,17 @@ var Agent = class {
378
668
  msgs.push(assistantMessage);
379
669
  this.session.setMessages(msgs);
380
670
  if (accumulatedToolCalls.length > 0) {
381
- const toolResults = await this.executeToolCalls(accumulatedToolCalls, toolParseErrors);
671
+ const toolResults = await executeToolCalls({
672
+ toolCalls: accumulatedToolCalls,
673
+ toolRegistry: this.toolRegistry,
674
+ permissionHandler: this.permissionHandler,
675
+ context: {
676
+ workingDirectory: this.workingDirectory,
677
+ abortSignal: this.abortController?.signal ?? AbortSignal.timeout(12e4)
678
+ },
679
+ parseErrors: toolParseErrors,
680
+ maxConcurrent: this.maxConcurrentTools
681
+ });
382
682
  const currentMsgs = this.session.getMessages();
383
683
  for (const result of toolResults) {
384
684
  yield {
@@ -446,50 +746,72 @@ var Agent = class {
446
746
  setPermissionHandler(handler) {
447
747
  this.permissionHandler = handler;
448
748
  }
749
+ /** Get the list of active MCP clients. */
750
+ getMCPClients() {
751
+ return [...this.mcpClients];
752
+ }
753
+ /**
754
+ * Disconnect all MCP servers and clean up resources.
755
+ * Call this when the agent is no longer needed.
756
+ */
757
+ async disconnectMCP() {
758
+ const toolNames = [];
759
+ for (const client of this.mcpClients) {
760
+ for (const tool of client.tools) {
761
+ toolNames.push(tool.name);
762
+ }
763
+ }
764
+ for (const name of toolNames) {
765
+ this.toolRegistry.unregister(name);
766
+ }
767
+ const errors = [];
768
+ for (const client of this.mcpClients) {
769
+ try {
770
+ await client.disconnect();
771
+ } catch (error) {
772
+ errors.push(
773
+ error instanceof Error ? error : new Error(String(error))
774
+ );
775
+ }
776
+ }
777
+ this.mcpClients = [];
778
+ this.mcpInitialized = false;
779
+ this.mcpInitPromise = void 0;
780
+ if (errors.length > 0) {
781
+ throw new AggregateError(errors, "Some MCP servers failed to disconnect");
782
+ }
783
+ }
449
784
  // -------------------------------------------------------------------------
450
785
  // Private helpers
451
786
  // -------------------------------------------------------------------------
452
- async executeToolCalls(toolCalls, parseErrors) {
453
- const results = [];
454
- for (const tc of toolCalls) {
455
- const parseError = parseErrors?.get(tc.id);
456
- if (parseError) {
457
- results.push({
458
- role: "tool",
459
- toolCallId: tc.id,
460
- content: parseError,
461
- isError: true
462
- });
463
- continue;
464
- }
465
- const toolDef = this.toolRegistry.get(tc.name);
466
- const permissionResult = await this.permissionHandler({
467
- tool: tc.name,
468
- input: tc.input,
469
- isReadOnly: toolDef?.isReadOnly
470
- });
471
- if (permissionResult.decision === "deny") {
472
- results.push({
473
- role: "tool",
474
- toolCallId: tc.id,
475
- content: `Permission denied for tool "${tc.name}"${permissionResult.reason ? `: ${permissionResult.reason}` : ""}`,
476
- isError: true
477
- });
478
- continue;
787
+ /**
788
+ * Connect to configured MCP servers and register their tools.
789
+ * Servers that fail to connect are skipped with a warning (non-fatal).
790
+ */
791
+ async initializeMCP() {
792
+ if (!this.mcpConfig?.servers.length) {
793
+ this.mcpInitialized = true;
794
+ return;
795
+ }
796
+ const results = await Promise.allSettled(
797
+ this.mcpConfig.servers.map(async (serverConfig) => {
798
+ const client = new MCPClient(serverConfig);
799
+ await client.connect();
800
+ return client;
801
+ })
802
+ );
803
+ for (const result of results) {
804
+ if (result.status === "fulfilled") {
805
+ const client = result.value;
806
+ this.mcpClients.push(client);
807
+ for (const tool of client.tools) {
808
+ if (!this.toolRegistry.has(tool.name)) {
809
+ this.toolRegistry.register(tool);
810
+ }
811
+ }
479
812
  }
480
- const context = {
481
- workingDirectory: this.workingDirectory,
482
- abortSignal: this.abortController?.signal ?? AbortSignal.timeout(12e4)
483
- };
484
- const result = await this.toolRegistry.execute(tc.name, tc.input, context);
485
- results.push({
486
- role: "tool",
487
- toolCallId: tc.id,
488
- content: result.content,
489
- isError: result.isError
490
- });
491
813
  }
492
- return results;
814
+ this.mcpInitialized = true;
493
815
  }
494
816
  };
495
817
  function isContextTooLongError(error) {
@@ -1652,6 +1974,7 @@ export {
1652
1974
  FileSession,
1653
1975
  FileSessionStore,
1654
1976
  InMemorySession,
1977
+ MCPClient,
1655
1978
  MemoryAuthStorage,
1656
1979
  MockProvider,
1657
1980
  NoopCompaction,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-code-kit/agent",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Headless agent framework for claude-code-kit — LLM query loop, tool execution, multi-provider",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
@@ -17,11 +17,17 @@
17
17
  "require": "./dist/index.js"
18
18
  }
19
19
  },
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "typecheck": "tsc --noEmit -p tsconfig.json",
23
+ "lint": "biome check src/"
24
+ },
20
25
  "dependencies": {
21
26
  "zod": ">=3.20.0"
22
27
  },
23
28
  "peerDependencies": {
24
29
  "@anthropic-ai/sdk": ">=0.30.0",
30
+ "@modelcontextprotocol/sdk": "^1.29.0",
25
31
  "openai": ">=4.0.0",
26
32
  "zod-to-json-schema": ">=3.20.0"
27
33
  },
@@ -29,6 +35,9 @@
29
35
  "@anthropic-ai/sdk": {
30
36
  "optional": true
31
37
  },
38
+ "@modelcontextprotocol/sdk": {
39
+ "optional": true
40
+ },
32
41
  "openai": {
33
42
  "optional": true
34
43
  },
@@ -38,6 +47,7 @@
38
47
  },
39
48
  "devDependencies": {
40
49
  "@anthropic-ai/sdk": "*",
50
+ "@modelcontextprotocol/sdk": "^1.29.0",
41
51
  "openai": "*",
42
52
  "tsup": "*",
43
53
  "typescript": "*"
@@ -62,10 +72,5 @@
62
72
  },
63
73
  "publishConfig": {
64
74
  "access": "public"
65
- },
66
- "scripts": {
67
- "build": "tsup",
68
- "typecheck": "tsc --noEmit -p tsconfig.json",
69
- "lint": "biome check src/"
70
75
  }
71
- }
76
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Minnzen
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.