@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 +71 -2
- package/dist/index.d.ts +71 -2
- package/dist/index.js +368 -44
- package/dist/index.mjs +367 -44
- package/package.json +12 -7
- package/LICENSE +0 -21
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
|
-
|
|
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
|
-
|
|
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
|
|
467
|
+
var import_zod2 = require("zod");
|
|
189
468
|
function zodToInputSchema(schema) {
|
|
190
|
-
if (typeof
|
|
191
|
-
const jsonSchema =
|
|
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
|
|
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
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
|
|
140
|
-
const jsonSchema =
|
|
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
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|