mcp-use 1.34.2 → 1.34.3
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/.tsbuildinfo +1 -1
- package/dist/{chunk-QNMWBM3V.js → chunk-4GUL52TF.js} +1 -1
- package/dist/{chunk-3L7F47SI.js → chunk-BJIP3NFE.js} +4 -4
- package/dist/{chunk-Z3NHDOLS.js → chunk-CQPVGLM4.js} +1 -1
- package/dist/{chunk-P2JIVJY6.js → chunk-CXHJETCQ.js} +4 -4
- package/dist/{chunk-VJAQ2A7N.js → chunk-GTLSCTCO.js} +1 -1
- package/dist/{chunk-EJOL74UE.js → chunk-J67NWN5H.js} +1 -1
- package/dist/{chunk-6DJM56C3.js → chunk-KWYZMUVB.js} +2 -2
- package/dist/chunk-KX7P6L43.js +171 -0
- package/dist/{chunk-VT4KQHJI.js → chunk-MCYFETK3.js} +2 -2
- package/dist/{chunk-DENFIIV2.js → chunk-NJPLVA2I.js} +1 -1
- package/dist/chunk-NQYSWKWJ.js +2609 -0
- package/dist/{chunk-OYFQOSSW.js → chunk-NYIMA6MZ.js} +1 -1
- package/dist/chunk-PJB2MFKZ.js +954 -0
- package/dist/chunk-PSD6ATRG.js +562 -0
- package/dist/{chunk-T3QVQIYU.js → chunk-QLKY4PK3.js} +5 -5
- package/dist/chunk-SUVUANZ3.js +594 -0
- package/dist/chunk-XHP35F4S.js +204 -0
- package/dist/chunk-ZLHL3BY6.js +2690 -0
- package/dist/{client-24ODJASB.js → client-VKWZPQCL.js} +4 -4
- package/dist/index.cjs +1 -1
- package/dist/index.js +6 -6
- package/dist/src/agents/index.cjs +1 -1
- package/dist/src/agents/index.js +4 -4
- package/dist/src/browser-agent.cjs +1 -1
- package/dist/src/browser-agent.js +2 -2
- package/dist/src/browser.cjs +1 -1
- package/dist/src/browser.js +4 -4
- package/dist/src/client.cjs +1 -1
- package/dist/src/client.js +4 -4
- package/dist/src/react/index.cjs +1 -1
- package/dist/src/react/index.js +4 -4
- package/dist/src/server/index.cjs +1 -1
- package/dist/src/server/index.js +7 -7
- package/dist/src/version.d.ts +1 -1
- package/dist/{stdio-CG6YESIU.js → stdio-37X7ETM7.js} +3 -3
- package/dist/{stdio-KZLWEEYB.js → stdio-CSGVA5IJ.js} +2 -2
- package/dist/stdio-FUBSRH66.js +13 -0
- package/dist/{tool-execution-helpers-GVP22F32.js → tool-execution-helpers-4LIAFMYM.js} +2 -2
- package/dist/tool-execution-helpers-HPVG5WVB.js +27 -0
- package/package.json +3 -3
|
@@ -0,0 +1,2609 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConnectionManager
|
|
3
|
+
} from "./chunk-GKPKUKD6.js";
|
|
4
|
+
import {
|
|
5
|
+
CodeModeConnector
|
|
6
|
+
} from "./chunk-J67NWN5H.js";
|
|
7
|
+
import {
|
|
8
|
+
BaseConnector
|
|
9
|
+
} from "./chunk-PSD6ATRG.js";
|
|
10
|
+
import {
|
|
11
|
+
Tel,
|
|
12
|
+
getPackageVersion
|
|
13
|
+
} from "./chunk-BJIP3NFE.js";
|
|
14
|
+
import {
|
|
15
|
+
JSONSchemaToZod
|
|
16
|
+
} from "./chunk-LG5NSHEL.js";
|
|
17
|
+
import {
|
|
18
|
+
logger
|
|
19
|
+
} from "./chunk-QWQYAQCK.js";
|
|
20
|
+
import {
|
|
21
|
+
__name,
|
|
22
|
+
__require
|
|
23
|
+
} from "./chunk-3GQAWCBQ.js";
|
|
24
|
+
|
|
25
|
+
// src/client.ts
|
|
26
|
+
import fs from "fs";
|
|
27
|
+
import path from "path";
|
|
28
|
+
|
|
29
|
+
// src/session.ts
|
|
30
|
+
var MCPSession = class {
|
|
31
|
+
static {
|
|
32
|
+
__name(this, "MCPSession");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The underlying connector managing the transport layer.
|
|
36
|
+
* This is the Stdio, HTTP, or WebSocket connector handling actual communication.
|
|
37
|
+
*/
|
|
38
|
+
connector;
|
|
39
|
+
/**
|
|
40
|
+
* Whether to automatically connect when initializing.
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
autoConnect;
|
|
44
|
+
/**
|
|
45
|
+
* Creates a new MCP session.
|
|
46
|
+
*
|
|
47
|
+
* @param connector - The connector to use for communication (Stdio, HTTP, WebSocket)
|
|
48
|
+
* @param autoConnect - Whether to automatically connect during initialization (default: true)
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const connector = new HttpConnector({ url: 'http://localhost:3000/mcp' });
|
|
53
|
+
* const session = new MCPSession(connector);
|
|
54
|
+
* await session.initialize(); // Auto-connects and initializes
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* // Manual connection control
|
|
60
|
+
* const session = new MCPSession(connector, false);
|
|
61
|
+
* await session.connect();
|
|
62
|
+
* await session.initialize();
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
constructor(connector, autoConnect = true) {
|
|
66
|
+
this.connector = connector;
|
|
67
|
+
this.autoConnect = autoConnect;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Establishes the connection to the MCP server.
|
|
71
|
+
*
|
|
72
|
+
* This method starts the underlying transport (spawns process for Stdio,
|
|
73
|
+
* opens WebSocket, etc.) but does not perform the MCP initialization
|
|
74
|
+
* handshake. Call {@link initialize} after connecting.
|
|
75
|
+
*
|
|
76
|
+
* @returns Promise that resolves when connected
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* await session.connect();
|
|
81
|
+
* await session.initialize();
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @see {@link initialize} for performing the MCP handshake
|
|
85
|
+
* @see {@link disconnect} for closing the connection
|
|
86
|
+
*/
|
|
87
|
+
async connect() {
|
|
88
|
+
await this.connector.connect();
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Closes the connection to the MCP server.
|
|
92
|
+
*
|
|
93
|
+
* This method gracefully shuts down the transport and cleans up resources.
|
|
94
|
+
* After disconnecting, the session cannot be used until reconnected.
|
|
95
|
+
*
|
|
96
|
+
* @returns Promise that resolves when disconnected
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* await session.disconnect();
|
|
101
|
+
* console.log('Session closed');
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @see {@link connect} for establishing connections
|
|
105
|
+
*/
|
|
106
|
+
async disconnect() {
|
|
107
|
+
await this.connector.disconnect();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Initializes the MCP session with the server.
|
|
111
|
+
*
|
|
112
|
+
* This method performs the MCP initialization handshake, exchanging
|
|
113
|
+
* capabilities and metadata with the server. If `autoConnect` is true
|
|
114
|
+
* and the session is not yet connected, it will connect first.
|
|
115
|
+
*
|
|
116
|
+
* After initialization, you can list and call tools, read resources, etc.
|
|
117
|
+
*
|
|
118
|
+
* @returns Promise that resolves when initialized
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const session = await client.createSession('my-server', false);
|
|
123
|
+
* await session.connect();
|
|
124
|
+
* await session.initialize();
|
|
125
|
+
* // Now ready to use
|
|
126
|
+
* const tools = await session.listTools();
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* @see {@link connect} for establishing the connection first
|
|
130
|
+
*/
|
|
131
|
+
async initialize() {
|
|
132
|
+
if (!this.isConnected && this.autoConnect) {
|
|
133
|
+
await this.connect();
|
|
134
|
+
}
|
|
135
|
+
await this.connector.initialize();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Checks if the session is currently connected to the server.
|
|
139
|
+
*
|
|
140
|
+
* @returns True if connected, false otherwise
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* if (session.isConnected) {
|
|
145
|
+
* const tools = await session.listTools();
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
get isConnected() {
|
|
150
|
+
return this.connector && this.connector.isClientConnected;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Register an event handler for session events
|
|
154
|
+
*
|
|
155
|
+
* @param event - The event type to listen for
|
|
156
|
+
* @param handler - The handler function to call when the event occurs
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* session.on("notification", async (notification) => {
|
|
161
|
+
* console.log(`Received: ${notification.method}`, notification.params);
|
|
162
|
+
*
|
|
163
|
+
* if (notification.method === "notifications/tools/list_changed") {
|
|
164
|
+
* // Refresh tools list
|
|
165
|
+
* }
|
|
166
|
+
* });
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
on(event, handler) {
|
|
170
|
+
if (event === "notification") {
|
|
171
|
+
this.connector.onNotification(handler);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Set roots and notify the server.
|
|
176
|
+
* Roots represent directories or files that the client has access to.
|
|
177
|
+
*
|
|
178
|
+
* @param roots - Array of Root objects with `uri` (must start with "file://") and optional `name`
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* await session.setRoots([
|
|
183
|
+
* { uri: "file:///home/user/project", name: "My Project" },
|
|
184
|
+
* { uri: "file:///home/user/data" }
|
|
185
|
+
* ]);
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
async setRoots(roots) {
|
|
189
|
+
return this.connector.setRoots(roots);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Gets the current roots advertised to the server.
|
|
193
|
+
*
|
|
194
|
+
* Roots represent directories or files that the client has provided access to.
|
|
195
|
+
* The server may use this information to scope its operations.
|
|
196
|
+
*
|
|
197
|
+
* @returns Array of Root objects
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* const roots = session.getRoots();
|
|
202
|
+
* console.log(`Current roots: ${roots.map(r => r.uri).join(', ')}`);
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* @see {@link setRoots} for updating roots
|
|
206
|
+
*/
|
|
207
|
+
getRoots() {
|
|
208
|
+
return this.connector.getRoots();
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get the cached list of tools from the server.
|
|
212
|
+
*
|
|
213
|
+
* @returns Array of available tools
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* const tools = session.tools;
|
|
218
|
+
* console.log(`Available tools: ${tools.map(t => t.name).join(", ")}`);
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
get tools() {
|
|
222
|
+
return this.connector.tools;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* List all available tools from the MCP server.
|
|
226
|
+
* This method fetches fresh tools from the server, unlike the `tools` getter which returns cached tools.
|
|
227
|
+
*
|
|
228
|
+
* @param options - Optional request options
|
|
229
|
+
* @returns Array of available tools
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* const tools = await session.listTools();
|
|
234
|
+
* console.log(`Available tools: ${tools.map(t => t.name).join(", ")}`);
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
async listTools(options) {
|
|
238
|
+
return this.connector.listTools(options);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get the server capabilities advertised during initialization.
|
|
242
|
+
*
|
|
243
|
+
* @returns Server capabilities object
|
|
244
|
+
*/
|
|
245
|
+
get serverCapabilities() {
|
|
246
|
+
return this.connector.serverCapabilities;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get the server information (name and version).
|
|
250
|
+
*
|
|
251
|
+
* @returns Server info object or null if not available
|
|
252
|
+
*/
|
|
253
|
+
get serverInfo() {
|
|
254
|
+
return this.connector.serverInfo;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Call a tool on the server.
|
|
258
|
+
*
|
|
259
|
+
* @param name - Name of the tool to call
|
|
260
|
+
* @param args - Arguments to pass to the tool (defaults to empty object)
|
|
261
|
+
* @param options - Optional request options (timeout, progress handlers, etc.)
|
|
262
|
+
* @returns Result from the tool execution
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* const result = await session.callTool("add", { a: 5, b: 3 });
|
|
267
|
+
* console.log(`Result: ${result.content[0].text}`);
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
async callTool(name, args = {}, options) {
|
|
271
|
+
return this.connector.callTool(name, args, options);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* List resources from the server with optional pagination.
|
|
275
|
+
*
|
|
276
|
+
* @param cursor - Optional cursor for pagination
|
|
277
|
+
* @param options - Request options
|
|
278
|
+
* @returns Resource list with optional nextCursor for pagination
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```typescript
|
|
282
|
+
* const result = await session.listResources();
|
|
283
|
+
* console.log(`Found ${result.resources.length} resources`);
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
async listResources(cursor, options) {
|
|
287
|
+
return this.connector.listResources(cursor, options);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* List all resources from the server, automatically handling pagination.
|
|
291
|
+
*
|
|
292
|
+
* @param options - Request options
|
|
293
|
+
* @returns Complete list of all resources
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* const result = await session.listAllResources();
|
|
298
|
+
* console.log(`Total resources: ${result.resources.length}`);
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
async listAllResources(options) {
|
|
302
|
+
return this.connector.listAllResources(options);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* List resource templates from the server.
|
|
306
|
+
*
|
|
307
|
+
* @param options - Request options
|
|
308
|
+
* @returns List of available resource templates
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* ```typescript
|
|
312
|
+
* const result = await session.listResourceTemplates();
|
|
313
|
+
* console.log(`Available templates: ${result.resourceTemplates.length}`);
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
async listResourceTemplates(options) {
|
|
317
|
+
return this.connector.listResourceTemplates(options);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Request completion suggestions for a prompt or resource template argument.
|
|
321
|
+
*
|
|
322
|
+
* @param params - Completion request parameters
|
|
323
|
+
* @param options - Request options
|
|
324
|
+
* @returns Completion suggestions from the server
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```typescript
|
|
328
|
+
* // Complete a prompt argument
|
|
329
|
+
* const result = await session.complete({
|
|
330
|
+
* ref: { type: "ref/prompt", name: "my-prompt" },
|
|
331
|
+
* argument: { name: "language", value: "py" }
|
|
332
|
+
* });
|
|
333
|
+
* console.log(result.completion.values); // ["python"]
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
async complete(params, options) {
|
|
337
|
+
return this.connector.complete(params, options);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Read a resource by URI.
|
|
341
|
+
*
|
|
342
|
+
* @param uri - URI of the resource to read
|
|
343
|
+
* @param options - Request options
|
|
344
|
+
* @returns Resource content
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```typescript
|
|
348
|
+
* const resource = await session.readResource("file:///path/to/file.txt");
|
|
349
|
+
* console.log(resource.contents);
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
async readResource(uri, options) {
|
|
353
|
+
return this.connector.readResource(uri, options);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Subscribe to resource updates.
|
|
357
|
+
*
|
|
358
|
+
* @param uri - URI of the resource to subscribe to
|
|
359
|
+
* @param options - Request options
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* ```typescript
|
|
363
|
+
* await session.subscribeToResource("file:///path/to/file.txt");
|
|
364
|
+
* // Now you'll receive notifications when this resource changes
|
|
365
|
+
* ```
|
|
366
|
+
*/
|
|
367
|
+
async subscribeToResource(uri, options) {
|
|
368
|
+
return this.connector.subscribeToResource(uri, options);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Unsubscribe from resource updates.
|
|
372
|
+
*
|
|
373
|
+
* @param uri - URI of the resource to unsubscribe from
|
|
374
|
+
* @param options - Request options
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```typescript
|
|
378
|
+
* await session.unsubscribeFromResource("file:///path/to/file.txt");
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
async unsubscribeFromResource(uri, options) {
|
|
382
|
+
return this.connector.unsubscribeFromResource(uri, options);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* List available prompts from the server.
|
|
386
|
+
*
|
|
387
|
+
* @returns List of available prompts
|
|
388
|
+
*
|
|
389
|
+
* @example
|
|
390
|
+
* ```typescript
|
|
391
|
+
* const result = await session.listPrompts();
|
|
392
|
+
* console.log(`Available prompts: ${result.prompts.length}`);
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
async listPrompts() {
|
|
396
|
+
return this.connector.listPrompts();
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get a specific prompt with arguments.
|
|
400
|
+
*
|
|
401
|
+
* @param name - Name of the prompt to get
|
|
402
|
+
* @param args - Arguments for the prompt
|
|
403
|
+
* @returns Prompt result
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```typescript
|
|
407
|
+
* const prompt = await session.getPrompt("greeting", { name: "Alice" });
|
|
408
|
+
* console.log(prompt.messages);
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
async getPrompt(name, args) {
|
|
412
|
+
return this.connector.getPrompt(name, args);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Send a raw request through the client.
|
|
416
|
+
*
|
|
417
|
+
* @param method - MCP method name
|
|
418
|
+
* @param params - Request parameters
|
|
419
|
+
* @param options - Request options
|
|
420
|
+
* @returns Response from the server
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* ```typescript
|
|
424
|
+
* const result = await session.request("custom/method", { key: "value" });
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
async request(method, params = null, options) {
|
|
428
|
+
return this.connector.request(method, params, options);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// src/client/base.ts
|
|
433
|
+
var BaseMCPClient = class {
|
|
434
|
+
static {
|
|
435
|
+
__name(this, "BaseMCPClient");
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Internal configuration object containing MCP server definitions.
|
|
439
|
+
* @protected
|
|
440
|
+
*/
|
|
441
|
+
config = {};
|
|
442
|
+
/**
|
|
443
|
+
* Map of server names to their active sessions.
|
|
444
|
+
* @protected
|
|
445
|
+
*/
|
|
446
|
+
sessions = {};
|
|
447
|
+
/**
|
|
448
|
+
* List of server names that have active sessions.
|
|
449
|
+
* This array is kept in sync with the sessions map and can be used
|
|
450
|
+
* to iterate over active connections.
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* ```typescript
|
|
454
|
+
* console.log(`Active servers: ${client.activeSessions.join(', ')}`);
|
|
455
|
+
* ```
|
|
456
|
+
*/
|
|
457
|
+
activeSessions = [];
|
|
458
|
+
/**
|
|
459
|
+
* Creates a new BaseMCPClient instance.
|
|
460
|
+
*
|
|
461
|
+
* @param config - Optional configuration object with MCP server definitions
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```typescript
|
|
465
|
+
* const client = new MCPClient({
|
|
466
|
+
* mcpServers: {
|
|
467
|
+
* 'example': {
|
|
468
|
+
* command: 'node',
|
|
469
|
+
* args: ['server.js']
|
|
470
|
+
* }
|
|
471
|
+
* }
|
|
472
|
+
* });
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
constructor(config) {
|
|
476
|
+
if (config) {
|
|
477
|
+
this.config = config;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Creates a client instance from a configuration dictionary.
|
|
482
|
+
*
|
|
483
|
+
* This static factory method must be implemented by concrete subclasses
|
|
484
|
+
* to provide proper type information and platform-specific initialization.
|
|
485
|
+
*
|
|
486
|
+
* @param _cfg - Configuration dictionary
|
|
487
|
+
* @returns Client instance
|
|
488
|
+
* @throws {Error} If called on the base class instead of a concrete implementation
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* ```typescript
|
|
492
|
+
* const client = MCPClient.fromDict({
|
|
493
|
+
* mcpServers: {
|
|
494
|
+
* 'my-server': { command: 'node', args: ['server.js'] }
|
|
495
|
+
* }
|
|
496
|
+
* });
|
|
497
|
+
* ```
|
|
498
|
+
*/
|
|
499
|
+
static fromDict(_cfg) {
|
|
500
|
+
throw new Error("fromDict must be implemented by concrete class");
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Adds a new MCP server configuration to the client.
|
|
504
|
+
*
|
|
505
|
+
* This method adds or updates a server configuration dynamically without
|
|
506
|
+
* needing to restart the client. The server can then be used to create
|
|
507
|
+
* new sessions.
|
|
508
|
+
*
|
|
509
|
+
* @param name - Unique name for the server
|
|
510
|
+
* @param serverConfig - Server configuration object (connector type, command, args, etc.)
|
|
511
|
+
*
|
|
512
|
+
* @example
|
|
513
|
+
* ```typescript
|
|
514
|
+
* client.addServer('new-server', {
|
|
515
|
+
* command: 'python',
|
|
516
|
+
* args: ['server.py']
|
|
517
|
+
* });
|
|
518
|
+
*
|
|
519
|
+
* // Now you can create a session
|
|
520
|
+
* const session = await client.createSession('new-server');
|
|
521
|
+
* ```
|
|
522
|
+
*
|
|
523
|
+
* @see {@link removeServer} for removing servers
|
|
524
|
+
* @see {@link getServerConfig} for retrieving configurations
|
|
525
|
+
*/
|
|
526
|
+
addServer(name, serverConfig) {
|
|
527
|
+
this.config.mcpServers = this.config.mcpServers || {};
|
|
528
|
+
this.config.mcpServers[name] = serverConfig;
|
|
529
|
+
Tel.getInstance().trackClientAddServer(name, serverConfig);
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Removes an MCP server configuration from the client.
|
|
533
|
+
*
|
|
534
|
+
* This method removes a server configuration and cleans up any active
|
|
535
|
+
* sessions associated with that server. If there's an active session,
|
|
536
|
+
* it will be removed from the active sessions list.
|
|
537
|
+
*
|
|
538
|
+
* @param name - Name of the server to remove
|
|
539
|
+
*
|
|
540
|
+
* @example
|
|
541
|
+
* ```typescript
|
|
542
|
+
* // Remove a server configuration
|
|
543
|
+
* client.removeServer('old-server');
|
|
544
|
+
*
|
|
545
|
+
* // The server name will no longer appear in getServerNames()
|
|
546
|
+
* console.log(client.getServerNames()); // 'old-server' is gone
|
|
547
|
+
* ```
|
|
548
|
+
*
|
|
549
|
+
* @see {@link addServer} for adding servers
|
|
550
|
+
* @see {@link closeSession} for properly closing sessions before removal
|
|
551
|
+
*/
|
|
552
|
+
removeServer(name) {
|
|
553
|
+
if (this.config.mcpServers?.[name]) {
|
|
554
|
+
delete this.config.mcpServers[name];
|
|
555
|
+
this.activeSessions = this.activeSessions.filter((n) => n !== name);
|
|
556
|
+
Tel.getInstance().trackClientRemoveServer(name);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Gets the names of all configured MCP servers.
|
|
561
|
+
*
|
|
562
|
+
* @returns Array of server names defined in the configuration
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* ```typescript
|
|
566
|
+
* const serverNames = client.getServerNames();
|
|
567
|
+
* console.log(`Configured servers: ${serverNames.join(', ')}`);
|
|
568
|
+
*
|
|
569
|
+
* // Create sessions for all servers
|
|
570
|
+
* for (const name of serverNames) {
|
|
571
|
+
* await client.createSession(name);
|
|
572
|
+
* }
|
|
573
|
+
* ```
|
|
574
|
+
*
|
|
575
|
+
* @see {@link activeSessions} for servers with active sessions
|
|
576
|
+
*/
|
|
577
|
+
getServerNames() {
|
|
578
|
+
return Object.keys(this.config.mcpServers ?? {});
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Gets the configuration for a specific MCP server.
|
|
582
|
+
*
|
|
583
|
+
* @param name - Name of the server
|
|
584
|
+
* @returns Server configuration object, or undefined if not found
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* ```typescript
|
|
588
|
+
* const config = client.getServerConfig('my-server');
|
|
589
|
+
* if (config) {
|
|
590
|
+
* console.log(`Command: ${config.command}`);
|
|
591
|
+
* console.log(`Args: ${config.args.join(' ')}`);
|
|
592
|
+
* }
|
|
593
|
+
* ```
|
|
594
|
+
*
|
|
595
|
+
* @see {@link getConfig} for retrieving the entire configuration
|
|
596
|
+
*/
|
|
597
|
+
getServerConfig(name) {
|
|
598
|
+
return this.config.mcpServers?.[name];
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Gets the complete client configuration.
|
|
602
|
+
*
|
|
603
|
+
* @returns Complete configuration object including all server definitions
|
|
604
|
+
*
|
|
605
|
+
* @example
|
|
606
|
+
* ```typescript
|
|
607
|
+
* const config = client.getConfig();
|
|
608
|
+
* console.log(`Total servers: ${Object.keys(config.mcpServers).length}`);
|
|
609
|
+
* ```
|
|
610
|
+
*
|
|
611
|
+
* @see {@link getServerConfig} for retrieving individual server configurations
|
|
612
|
+
*/
|
|
613
|
+
getConfig() {
|
|
614
|
+
return this.config ?? {};
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Creates a new session for connecting to an MCP server.
|
|
618
|
+
*
|
|
619
|
+
* This method initializes a connection to the specified server using the
|
|
620
|
+
* configuration provided during client construction. Sessions manage the
|
|
621
|
+
* lifecycle of connections and provide methods for calling tools, listing
|
|
622
|
+
* resources, and more.
|
|
623
|
+
*
|
|
624
|
+
* If a session already exists for the server, it will be replaced with a new one.
|
|
625
|
+
*
|
|
626
|
+
* @param serverName - The name of the server as defined in the client configuration
|
|
627
|
+
* @param autoInitialize - Whether to automatically initialize the session (default: true)
|
|
628
|
+
* @returns A promise that resolves to the created MCPSession instance
|
|
629
|
+
* @throws {Error} If the server is not found in the configuration
|
|
630
|
+
*
|
|
631
|
+
* @example
|
|
632
|
+
* ```typescript
|
|
633
|
+
* // Create and initialize a session
|
|
634
|
+
* const session = await client.createSession('my-server');
|
|
635
|
+
* const tools = await session.listTools();
|
|
636
|
+
*
|
|
637
|
+
* // Create without auto-initialization
|
|
638
|
+
* const session = await client.createSession('my-server', false);
|
|
639
|
+
* await session.connect();
|
|
640
|
+
* await session.initialize();
|
|
641
|
+
* ```
|
|
642
|
+
*
|
|
643
|
+
* @see {@link MCPSession} for session management methods
|
|
644
|
+
* @see {@link closeSession} for closing sessions
|
|
645
|
+
* @see {@link getSession} for retrieving existing sessions
|
|
646
|
+
*/
|
|
647
|
+
async createSession(serverName, autoInitialize = true) {
|
|
648
|
+
const servers = this.config.mcpServers ?? {};
|
|
649
|
+
if (Object.keys(servers).length === 0) {
|
|
650
|
+
logger.warn("No MCP servers defined in config");
|
|
651
|
+
}
|
|
652
|
+
if (!servers[serverName]) {
|
|
653
|
+
throw new Error(`Server '${serverName}' not found in config`);
|
|
654
|
+
}
|
|
655
|
+
const connector = await Promise.resolve(
|
|
656
|
+
this.createConnectorFromConfig(servers[serverName])
|
|
657
|
+
);
|
|
658
|
+
const session = new MCPSession(connector);
|
|
659
|
+
if (autoInitialize) {
|
|
660
|
+
await session.initialize();
|
|
661
|
+
}
|
|
662
|
+
this.sessions[serverName] = session;
|
|
663
|
+
if (!this.activeSessions.includes(serverName)) {
|
|
664
|
+
this.activeSessions.push(serverName);
|
|
665
|
+
}
|
|
666
|
+
return session;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Creates sessions for all configured MCP servers.
|
|
670
|
+
*
|
|
671
|
+
* This is a convenience method that iterates through all servers in the
|
|
672
|
+
* configuration and creates a session for each one. Sessions are created
|
|
673
|
+
* sequentially to avoid overwhelming the system.
|
|
674
|
+
*
|
|
675
|
+
* @param autoInitialize - Whether to automatically initialize each session (default: true)
|
|
676
|
+
* @returns A promise that resolves to a map of server names to sessions
|
|
677
|
+
*
|
|
678
|
+
* @example
|
|
679
|
+
* ```typescript
|
|
680
|
+
* // Create sessions for all configured servers
|
|
681
|
+
* const sessions = await client.createAllSessions();
|
|
682
|
+
* console.log(`Created ${Object.keys(sessions).length} sessions`);
|
|
683
|
+
*
|
|
684
|
+
* // List tools from all servers
|
|
685
|
+
* for (const [name, session] of Object.entries(sessions)) {
|
|
686
|
+
* const tools = await session.listTools();
|
|
687
|
+
* console.log(`${name}: ${tools.length} tools`);
|
|
688
|
+
* }
|
|
689
|
+
* ```
|
|
690
|
+
*
|
|
691
|
+
* @see {@link createSession} for creating individual sessions
|
|
692
|
+
* @see {@link closeAllSessions} for closing all sessions
|
|
693
|
+
*/
|
|
694
|
+
async createAllSessions(autoInitialize = true) {
|
|
695
|
+
const servers = this.config.mcpServers ?? {};
|
|
696
|
+
if (Object.keys(servers).length === 0) {
|
|
697
|
+
logger.warn("No MCP servers defined in config");
|
|
698
|
+
}
|
|
699
|
+
for (const name of Object.keys(servers)) {
|
|
700
|
+
await this.createSession(name, autoInitialize);
|
|
701
|
+
}
|
|
702
|
+
return this.sessions;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Retrieves an existing session by server name.
|
|
706
|
+
*
|
|
707
|
+
* This method returns null if no session exists, making it safe for
|
|
708
|
+
* checking session existence without throwing errors.
|
|
709
|
+
*
|
|
710
|
+
* @param serverName - Name of the server
|
|
711
|
+
* @returns The session instance or null if not found
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```typescript
|
|
715
|
+
* const session = client.getSession('my-server');
|
|
716
|
+
* if (session) {
|
|
717
|
+
* const tools = await session.listTools();
|
|
718
|
+
* } else {
|
|
719
|
+
* console.log('Session not found, creating...');
|
|
720
|
+
* await client.createSession('my-server');
|
|
721
|
+
* }
|
|
722
|
+
* ```
|
|
723
|
+
*
|
|
724
|
+
* @see {@link requireSession} for getting a session that throws if not found
|
|
725
|
+
* @see {@link createSession} for creating sessions
|
|
726
|
+
*/
|
|
727
|
+
getSession(serverName) {
|
|
728
|
+
const session = this.sessions[serverName];
|
|
729
|
+
if (!session) {
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
return session;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Retrieves an existing session by server name, throwing if not found.
|
|
736
|
+
*
|
|
737
|
+
* This method is useful when you need to ensure a session exists before
|
|
738
|
+
* proceeding. It throws a descriptive error if the session is not found.
|
|
739
|
+
*
|
|
740
|
+
* @param serverName - Name of the server
|
|
741
|
+
* @returns The session instance
|
|
742
|
+
* @throws {Error} If the session is not found
|
|
743
|
+
*
|
|
744
|
+
* @example
|
|
745
|
+
* ```typescript
|
|
746
|
+
* try {
|
|
747
|
+
* const session = client.requireSession('my-server');
|
|
748
|
+
* const tools = await session.listTools();
|
|
749
|
+
* } catch (error) {
|
|
750
|
+
* console.error('Session not found:', error.message);
|
|
751
|
+
* }
|
|
752
|
+
* ```
|
|
753
|
+
*
|
|
754
|
+
* @see {@link getSession} for a null-returning alternative
|
|
755
|
+
* @see {@link createSession} for creating sessions
|
|
756
|
+
*/
|
|
757
|
+
requireSession(serverName) {
|
|
758
|
+
const session = this.sessions[serverName];
|
|
759
|
+
if (!session) {
|
|
760
|
+
throw new Error(
|
|
761
|
+
`Session '${serverName}' not found. Available sessions: ${this.activeSessions.join(", ") || "none"}`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
return session;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Gets all active sessions as a map of server names to sessions.
|
|
768
|
+
*
|
|
769
|
+
* @returns Map of server names to their active sessions
|
|
770
|
+
*
|
|
771
|
+
* @example
|
|
772
|
+
* ```typescript
|
|
773
|
+
* const sessions = client.getAllActiveSessions();
|
|
774
|
+
*
|
|
775
|
+
* // Iterate over all active sessions
|
|
776
|
+
* for (const [name, session] of Object.entries(sessions)) {
|
|
777
|
+
* console.log(`Server: ${name}`);
|
|
778
|
+
* const tools = await session.listTools();
|
|
779
|
+
* console.log(` Tools: ${tools.length}`);
|
|
780
|
+
* }
|
|
781
|
+
* ```
|
|
782
|
+
*
|
|
783
|
+
* @see {@link activeSessions} for just the list of server names
|
|
784
|
+
* @see {@link getSession} for retrieving individual sessions
|
|
785
|
+
*/
|
|
786
|
+
getAllActiveSessions() {
|
|
787
|
+
return Object.fromEntries(
|
|
788
|
+
this.activeSessions.map((n) => [n, this.sessions[n]])
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Closes a session and cleans up its resources.
|
|
793
|
+
*
|
|
794
|
+
* This method gracefully disconnects from the server and removes the
|
|
795
|
+
* session from the active sessions list. It's safe to call even if
|
|
796
|
+
* the session doesn't exist.
|
|
797
|
+
*
|
|
798
|
+
* @param serverName - Name of the server whose session should be closed
|
|
799
|
+
*
|
|
800
|
+
* @example
|
|
801
|
+
* ```typescript
|
|
802
|
+
* // Close a specific session
|
|
803
|
+
* await client.closeSession('my-server');
|
|
804
|
+
*
|
|
805
|
+
* // Verify it's closed
|
|
806
|
+
* console.log(client.activeSessions.includes('my-server')); // false
|
|
807
|
+
* ```
|
|
808
|
+
*
|
|
809
|
+
* @see {@link closeAllSessions} for closing all sessions at once
|
|
810
|
+
* @see {@link createSession} for creating new sessions
|
|
811
|
+
*/
|
|
812
|
+
async closeSession(serverName) {
|
|
813
|
+
const session = this.sessions[serverName];
|
|
814
|
+
if (!session) {
|
|
815
|
+
logger.warn(
|
|
816
|
+
`No session exists for server ${serverName}, nothing to close`
|
|
817
|
+
);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
try {
|
|
821
|
+
logger.debug(`Closing session for server ${serverName}`);
|
|
822
|
+
await session.disconnect();
|
|
823
|
+
} catch (e) {
|
|
824
|
+
logger.error(`Error closing session for server '${serverName}': ${e}`);
|
|
825
|
+
} finally {
|
|
826
|
+
if (this.sessions[serverName] === session) {
|
|
827
|
+
delete this.sessions[serverName];
|
|
828
|
+
this.activeSessions = this.activeSessions.filter(
|
|
829
|
+
(n) => n !== serverName
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Closes all active sessions and cleans up their resources.
|
|
836
|
+
*
|
|
837
|
+
* This method iterates through all sessions and attempts to close each one
|
|
838
|
+
* gracefully. If any session fails to close, the error is logged but the
|
|
839
|
+
* method continues to close remaining sessions.
|
|
840
|
+
*
|
|
841
|
+
* This is particularly useful for cleanup on application shutdown.
|
|
842
|
+
*
|
|
843
|
+
* @example
|
|
844
|
+
* ```typescript
|
|
845
|
+
* // Clean shutdown
|
|
846
|
+
* try {
|
|
847
|
+
* await client.closeAllSessions();
|
|
848
|
+
* console.log('All sessions closed successfully');
|
|
849
|
+
* } catch (error) {
|
|
850
|
+
* console.error('Error during cleanup:', error);
|
|
851
|
+
* }
|
|
852
|
+
* ```
|
|
853
|
+
*
|
|
854
|
+
* @example
|
|
855
|
+
* ```typescript
|
|
856
|
+
* // Use in application shutdown handler
|
|
857
|
+
* process.on('SIGINT', async () => {
|
|
858
|
+
* console.log('Shutting down...');
|
|
859
|
+
* await client.closeAllSessions();
|
|
860
|
+
* process.exit(0);
|
|
861
|
+
* });
|
|
862
|
+
* ```
|
|
863
|
+
*
|
|
864
|
+
* @see {@link closeSession} for closing individual sessions
|
|
865
|
+
* @see {@link createAllSessions} for creating sessions
|
|
866
|
+
*/
|
|
867
|
+
async closeAllSessions() {
|
|
868
|
+
const serverNames = Object.keys(this.sessions);
|
|
869
|
+
const errors = [];
|
|
870
|
+
for (const serverName of serverNames) {
|
|
871
|
+
try {
|
|
872
|
+
logger.debug(`Closing session for server ${serverName}`);
|
|
873
|
+
await this.closeSession(serverName);
|
|
874
|
+
} catch (e) {
|
|
875
|
+
const errorMsg = `Failed to close session for server '${serverName}': ${e}`;
|
|
876
|
+
logger.error(errorMsg);
|
|
877
|
+
errors.push(errorMsg);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if (errors.length) {
|
|
881
|
+
logger.error(
|
|
882
|
+
`Encountered ${errors.length} errors while closing sessions`
|
|
883
|
+
);
|
|
884
|
+
} else {
|
|
885
|
+
logger.debug("All sessions closed successfully");
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
// src/client/executors/base.ts
|
|
891
|
+
var BaseCodeExecutor = class {
|
|
892
|
+
static {
|
|
893
|
+
__name(this, "BaseCodeExecutor");
|
|
894
|
+
}
|
|
895
|
+
client;
|
|
896
|
+
_connecting = false;
|
|
897
|
+
constructor(client) {
|
|
898
|
+
this.client = client;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Ensure all configured MCP servers are connected before execution.
|
|
902
|
+
* Prevents race conditions with a connection lock.
|
|
903
|
+
*/
|
|
904
|
+
async ensureServersConnected() {
|
|
905
|
+
const configuredServers = this.client.getServerNames();
|
|
906
|
+
const activeSessions = Object.keys(this.client.getAllActiveSessions());
|
|
907
|
+
const missingServers = configuredServers.filter(
|
|
908
|
+
(s) => !activeSessions.includes(s)
|
|
909
|
+
);
|
|
910
|
+
if (missingServers.length > 0 && !this._connecting) {
|
|
911
|
+
this._connecting = true;
|
|
912
|
+
try {
|
|
913
|
+
logger.debug(
|
|
914
|
+
`Connecting to configured servers for code execution: ${missingServers.join(", ")}`
|
|
915
|
+
);
|
|
916
|
+
await this.client.createAllSessions();
|
|
917
|
+
} finally {
|
|
918
|
+
this._connecting = false;
|
|
919
|
+
}
|
|
920
|
+
} else if (missingServers.length > 0 && this._connecting) {
|
|
921
|
+
logger.debug("Waiting for ongoing server connection...");
|
|
922
|
+
const startWait = Date.now();
|
|
923
|
+
while (this._connecting && Date.now() - startWait < 5e3) {
|
|
924
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Get tool namespace information from all active MCP sessions.
|
|
930
|
+
* Filters out the internal code_mode server.
|
|
931
|
+
*/
|
|
932
|
+
getToolNamespaces() {
|
|
933
|
+
const namespaces = [];
|
|
934
|
+
const activeSessions = this.client.getAllActiveSessions();
|
|
935
|
+
for (const [serverName, session] of Object.entries(activeSessions)) {
|
|
936
|
+
if (serverName === "code_mode") continue;
|
|
937
|
+
try {
|
|
938
|
+
const connector = session.connector;
|
|
939
|
+
let tools;
|
|
940
|
+
try {
|
|
941
|
+
tools = connector.tools;
|
|
942
|
+
} catch (e) {
|
|
943
|
+
logger.warn(`Tools not available for server ${serverName}: ${e}`);
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
if (!tools || tools.length === 0) continue;
|
|
947
|
+
namespaces.push({ serverName, tools, session });
|
|
948
|
+
} catch (e) {
|
|
949
|
+
logger.warn(`Failed to load tools for server ${serverName}: ${e}`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return namespaces;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Create a search function for discovering available MCP tools.
|
|
956
|
+
* Used by code execution environments to find tools at runtime.
|
|
957
|
+
*/
|
|
958
|
+
createSearchToolsFunction() {
|
|
959
|
+
return async (query = "", detailLevel = "full") => {
|
|
960
|
+
const allTools = [];
|
|
961
|
+
const allNamespaces = /* @__PURE__ */ new Set();
|
|
962
|
+
const queryLower = query.toLowerCase();
|
|
963
|
+
const activeSessions = this.client.getAllActiveSessions();
|
|
964
|
+
for (const [serverName, session] of Object.entries(activeSessions)) {
|
|
965
|
+
if (serverName === "code_mode") continue;
|
|
966
|
+
try {
|
|
967
|
+
const tools = session.connector.tools;
|
|
968
|
+
if (tools && tools.length > 0) {
|
|
969
|
+
allNamespaces.add(serverName);
|
|
970
|
+
}
|
|
971
|
+
for (const tool of tools) {
|
|
972
|
+
if (detailLevel === "names") {
|
|
973
|
+
allTools.push({ name: tool.name, server: serverName });
|
|
974
|
+
} else if (detailLevel === "descriptions") {
|
|
975
|
+
allTools.push({
|
|
976
|
+
name: tool.name,
|
|
977
|
+
server: serverName,
|
|
978
|
+
description: tool.description
|
|
979
|
+
});
|
|
980
|
+
} else {
|
|
981
|
+
allTools.push({
|
|
982
|
+
name: tool.name,
|
|
983
|
+
server: serverName,
|
|
984
|
+
description: tool.description,
|
|
985
|
+
input_schema: tool.inputSchema
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
} catch (e) {
|
|
990
|
+
logger.warn(`Failed to search tools in server ${serverName}: ${e}`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
let filteredTools = allTools;
|
|
994
|
+
if (query) {
|
|
995
|
+
filteredTools = allTools.filter((tool) => {
|
|
996
|
+
const nameMatch = tool.name.toLowerCase().includes(queryLower);
|
|
997
|
+
const descMatch = tool.description?.toLowerCase().includes(queryLower);
|
|
998
|
+
const serverMatch = tool.server.toLowerCase().includes(queryLower);
|
|
999
|
+
return nameMatch || descMatch || serverMatch;
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
return {
|
|
1003
|
+
meta: {
|
|
1004
|
+
total_tools: allTools.length,
|
|
1005
|
+
namespaces: Array.from(allNamespaces).sort(),
|
|
1006
|
+
result_count: filteredTools.length
|
|
1007
|
+
},
|
|
1008
|
+
results: filteredTools
|
|
1009
|
+
};
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
// src/client/executors/e2b.ts
|
|
1015
|
+
var E2BCodeExecutor = class extends BaseCodeExecutor {
|
|
1016
|
+
static {
|
|
1017
|
+
__name(this, "E2BCodeExecutor");
|
|
1018
|
+
}
|
|
1019
|
+
e2bApiKey;
|
|
1020
|
+
codeExecSandbox = null;
|
|
1021
|
+
SandboxClass = null;
|
|
1022
|
+
timeoutMs;
|
|
1023
|
+
constructor(client, options) {
|
|
1024
|
+
super(client);
|
|
1025
|
+
this.e2bApiKey = options.apiKey;
|
|
1026
|
+
this.timeoutMs = options.timeoutMs ?? 3e5;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Lazy load E2B Sandbox class.
|
|
1030
|
+
* This allows the library to work without E2B installed.
|
|
1031
|
+
*/
|
|
1032
|
+
async ensureSandboxClass() {
|
|
1033
|
+
if (this.SandboxClass) return;
|
|
1034
|
+
try {
|
|
1035
|
+
const e2b = await import("@e2b/code-interpreter");
|
|
1036
|
+
this.SandboxClass = e2b.Sandbox;
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
throw new Error(
|
|
1039
|
+
"@e2b/code-interpreter is not installed. The E2B code executor requires this optional dependency. Install it with: yarn add @e2b/code-interpreter"
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Get or create a dedicated sandbox for code execution.
|
|
1045
|
+
*/
|
|
1046
|
+
async getOrCreateCodeExecSandbox() {
|
|
1047
|
+
if (this.codeExecSandbox) return this.codeExecSandbox;
|
|
1048
|
+
await this.ensureSandboxClass();
|
|
1049
|
+
logger.debug("Starting E2B sandbox for code execution...");
|
|
1050
|
+
this.codeExecSandbox = await this.SandboxClass.create("base", {
|
|
1051
|
+
apiKey: this.e2bApiKey,
|
|
1052
|
+
timeoutMs: this.timeoutMs
|
|
1053
|
+
});
|
|
1054
|
+
return this.codeExecSandbox;
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Generate the shim code that exposes tools to the sandbox environment.
|
|
1058
|
+
* Creates a bridge that intercepts tool calls and sends them back to host.
|
|
1059
|
+
*/
|
|
1060
|
+
generateShim(tools) {
|
|
1061
|
+
let shim = `
|
|
1062
|
+
// MCP Bridge Shim
|
|
1063
|
+
global.__callMcpTool = async (server, tool, args) => {
|
|
1064
|
+
const id = Math.random().toString(36).substring(7);
|
|
1065
|
+
console.log(JSON.stringify({
|
|
1066
|
+
type: '__MCP_TOOL_CALL__',
|
|
1067
|
+
id,
|
|
1068
|
+
server,
|
|
1069
|
+
tool,
|
|
1070
|
+
args
|
|
1071
|
+
}));
|
|
1072
|
+
|
|
1073
|
+
const resultPath = \`/tmp/mcp_result_\${id}.json\`;
|
|
1074
|
+
const fs = require('fs');
|
|
1075
|
+
|
|
1076
|
+
// Poll for result file
|
|
1077
|
+
let attempts = 0;
|
|
1078
|
+
while (attempts < 300) { // 30 seconds timeout
|
|
1079
|
+
if (fs.existsSync(resultPath)) {
|
|
1080
|
+
const content = fs.readFileSync(resultPath, 'utf8');
|
|
1081
|
+
const result = JSON.parse(content);
|
|
1082
|
+
fs.unlinkSync(resultPath); // Clean up
|
|
1083
|
+
|
|
1084
|
+
if (result.error) {
|
|
1085
|
+
throw new Error(result.error);
|
|
1086
|
+
}
|
|
1087
|
+
return result.data;
|
|
1088
|
+
}
|
|
1089
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1090
|
+
attempts++;
|
|
1091
|
+
}
|
|
1092
|
+
throw new Error('Tool execution timed out');
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
// Global search_tools helper
|
|
1096
|
+
global.search_tools = async (query, detailLevel = 'full') => {
|
|
1097
|
+
const allTools = ${JSON.stringify(
|
|
1098
|
+
Object.entries(tools).flatMap(
|
|
1099
|
+
([server, serverTools]) => serverTools.map((tool) => ({
|
|
1100
|
+
name: tool.name,
|
|
1101
|
+
description: tool.description,
|
|
1102
|
+
server,
|
|
1103
|
+
input_schema: tool.inputSchema
|
|
1104
|
+
}))
|
|
1105
|
+
)
|
|
1106
|
+
)};
|
|
1107
|
+
|
|
1108
|
+
const filtered = allTools.filter(tool => {
|
|
1109
|
+
if (!query) return true;
|
|
1110
|
+
const q = query.toLowerCase();
|
|
1111
|
+
return tool.name.toLowerCase().includes(q) ||
|
|
1112
|
+
(tool.description && tool.description.toLowerCase().includes(q));
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
if (detailLevel === 'names') {
|
|
1116
|
+
return filtered.map(t => ({ name: t.name, server: t.server }));
|
|
1117
|
+
} else if (detailLevel === 'descriptions') {
|
|
1118
|
+
return filtered.map(t => ({ name: t.name, server: t.server, description: t.description }));
|
|
1119
|
+
}
|
|
1120
|
+
return filtered;
|
|
1121
|
+
};
|
|
1122
|
+
`;
|
|
1123
|
+
for (const [serverName, serverTools] of Object.entries(tools)) {
|
|
1124
|
+
if (!serverTools || serverTools.length === 0) continue;
|
|
1125
|
+
const safeServerName = serverName.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
1126
|
+
shim += `
|
|
1127
|
+
global['${serverName}'] = {`;
|
|
1128
|
+
for (const tool of serverTools) {
|
|
1129
|
+
shim += `
|
|
1130
|
+
'${tool.name}': async (args) => await global.__callMcpTool('${serverName}', '${tool.name}', args),`;
|
|
1131
|
+
}
|
|
1132
|
+
shim += `
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
// Also expose as safe name if different
|
|
1136
|
+
if ('${safeServerName}' !== '${serverName}') {
|
|
1137
|
+
global['${safeServerName}'] = global['${serverName}'];
|
|
1138
|
+
}
|
|
1139
|
+
`;
|
|
1140
|
+
}
|
|
1141
|
+
return shim;
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Build the tool catalog for the shim.
|
|
1145
|
+
* Returns a map of server names to their available tools.
|
|
1146
|
+
*/
|
|
1147
|
+
buildToolCatalog() {
|
|
1148
|
+
const catalog = {};
|
|
1149
|
+
const namespaces = this.getToolNamespaces();
|
|
1150
|
+
for (const { serverName, tools } of namespaces) {
|
|
1151
|
+
catalog[serverName] = tools;
|
|
1152
|
+
}
|
|
1153
|
+
return catalog;
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Execute JavaScript/TypeScript code in an E2B sandbox with MCP tool access.
|
|
1157
|
+
* Tool calls are proxied back to the host via the bridge pattern.
|
|
1158
|
+
*
|
|
1159
|
+
* @param code - Code to execute
|
|
1160
|
+
* @param timeout - Execution timeout in milliseconds (default: 30000)
|
|
1161
|
+
*/
|
|
1162
|
+
async execute(code, timeout = 3e4) {
|
|
1163
|
+
const startTime = Date.now();
|
|
1164
|
+
let result = null;
|
|
1165
|
+
let error = null;
|
|
1166
|
+
let logs = [];
|
|
1167
|
+
try {
|
|
1168
|
+
await this.ensureServersConnected();
|
|
1169
|
+
const sandbox = await this.getOrCreateCodeExecSandbox();
|
|
1170
|
+
const toolCatalog = this.buildToolCatalog();
|
|
1171
|
+
const shim = this.generateShim(toolCatalog);
|
|
1172
|
+
const wrappedCode = `
|
|
1173
|
+
${shim}
|
|
1174
|
+
|
|
1175
|
+
(async () => {
|
|
1176
|
+
try {
|
|
1177
|
+
const func = async () => {
|
|
1178
|
+
${code}
|
|
1179
|
+
};
|
|
1180
|
+
const result = await func();
|
|
1181
|
+
console.log('__MCP_RESULT_START__');
|
|
1182
|
+
console.log(JSON.stringify(result));
|
|
1183
|
+
console.log('__MCP_RESULT_END__');
|
|
1184
|
+
} catch (e) {
|
|
1185
|
+
console.error(e);
|
|
1186
|
+
process.exit(1);
|
|
1187
|
+
}
|
|
1188
|
+
})();
|
|
1189
|
+
`;
|
|
1190
|
+
const filename = `exec_${Date.now()}.js`;
|
|
1191
|
+
await sandbox.files.write(filename, wrappedCode);
|
|
1192
|
+
const execution = await sandbox.commands.run(`node ${filename}`, {
|
|
1193
|
+
timeoutMs: timeout,
|
|
1194
|
+
onStdout: /* @__PURE__ */ __name(async (data) => {
|
|
1195
|
+
try {
|
|
1196
|
+
const lines = data.split("\n");
|
|
1197
|
+
for (const line of lines) {
|
|
1198
|
+
if (line.trim().startsWith('{"type":"__MCP_TOOL_CALL__"')) {
|
|
1199
|
+
const call = JSON.parse(line);
|
|
1200
|
+
if (call.type === "__MCP_TOOL_CALL__") {
|
|
1201
|
+
try {
|
|
1202
|
+
logger.debug(
|
|
1203
|
+
`[E2B Bridge] Calling tool ${call.server}.${call.tool}`
|
|
1204
|
+
);
|
|
1205
|
+
const activeSessions = this.client.getAllActiveSessions();
|
|
1206
|
+
const session = activeSessions[call.server];
|
|
1207
|
+
if (!session) {
|
|
1208
|
+
throw new Error(`Server ${call.server} not found`);
|
|
1209
|
+
}
|
|
1210
|
+
const toolResult = await session.connector.callTool(
|
|
1211
|
+
call.tool,
|
|
1212
|
+
call.args
|
|
1213
|
+
);
|
|
1214
|
+
let extractedResult = toolResult;
|
|
1215
|
+
if (toolResult.content && toolResult.content.length > 0) {
|
|
1216
|
+
const item = toolResult.content[0];
|
|
1217
|
+
if (item.type === "text") {
|
|
1218
|
+
try {
|
|
1219
|
+
extractedResult = JSON.parse(item.text);
|
|
1220
|
+
} catch {
|
|
1221
|
+
extractedResult = item.text;
|
|
1222
|
+
}
|
|
1223
|
+
} else {
|
|
1224
|
+
extractedResult = item;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
const resultPath = `/tmp/mcp_result_${call.id}.json`;
|
|
1228
|
+
await sandbox.files.write(
|
|
1229
|
+
resultPath,
|
|
1230
|
+
JSON.stringify({ data: extractedResult })
|
|
1231
|
+
);
|
|
1232
|
+
} catch (err) {
|
|
1233
|
+
logger.error(
|
|
1234
|
+
`[E2B Bridge] Tool execution failed: ${err.message}`
|
|
1235
|
+
);
|
|
1236
|
+
const resultPath = `/tmp/mcp_result_${call.id}.json`;
|
|
1237
|
+
await sandbox.files.write(
|
|
1238
|
+
resultPath,
|
|
1239
|
+
JSON.stringify({
|
|
1240
|
+
error: err.message || String(err)
|
|
1241
|
+
})
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
} catch (e) {
|
|
1248
|
+
}
|
|
1249
|
+
}, "onStdout")
|
|
1250
|
+
});
|
|
1251
|
+
logs = [execution.stdout, execution.stderr].filter(Boolean);
|
|
1252
|
+
if (execution.exitCode !== 0) {
|
|
1253
|
+
error = execution.stderr || "Execution failed";
|
|
1254
|
+
} else {
|
|
1255
|
+
const stdout = execution.stdout;
|
|
1256
|
+
const startMarker = "__MCP_RESULT_START__";
|
|
1257
|
+
const endMarker = "__MCP_RESULT_END__";
|
|
1258
|
+
const startIndex = stdout.indexOf(startMarker);
|
|
1259
|
+
const endIndex = stdout.indexOf(endMarker);
|
|
1260
|
+
if (startIndex !== -1 && endIndex !== -1) {
|
|
1261
|
+
const jsonStr = stdout.substring(startIndex + startMarker.length, endIndex).trim();
|
|
1262
|
+
try {
|
|
1263
|
+
result = JSON.parse(jsonStr);
|
|
1264
|
+
} catch (e) {
|
|
1265
|
+
result = jsonStr;
|
|
1266
|
+
}
|
|
1267
|
+
logs = logs.map((log) => {
|
|
1268
|
+
let cleaned = log.replace(
|
|
1269
|
+
new RegExp(startMarker + "[\\s\\S]*?" + endMarker),
|
|
1270
|
+
"[Result captured]"
|
|
1271
|
+
);
|
|
1272
|
+
cleaned = cleaned.split("\n").filter((l) => !l.includes("__MCP_TOOL_CALL__")).join("\n");
|
|
1273
|
+
return cleaned;
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
} catch (e) {
|
|
1278
|
+
error = e.message || String(e);
|
|
1279
|
+
if (error && (error.includes("timeout") || error.includes("timed out"))) {
|
|
1280
|
+
error = "Script execution timed out";
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
return {
|
|
1284
|
+
result,
|
|
1285
|
+
logs,
|
|
1286
|
+
error,
|
|
1287
|
+
execution_time: (Date.now() - startTime) / 1e3
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Clean up the E2B sandbox.
|
|
1292
|
+
* Should be called when the executor is no longer needed.
|
|
1293
|
+
*/
|
|
1294
|
+
async cleanup() {
|
|
1295
|
+
if (this.codeExecSandbox) {
|
|
1296
|
+
try {
|
|
1297
|
+
await this.codeExecSandbox.kill();
|
|
1298
|
+
this.codeExecSandbox = null;
|
|
1299
|
+
logger.debug("E2B code execution sandbox stopped");
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
logger.error("Failed to stop E2B code execution sandbox:", error);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
// src/client/executors/vm.ts
|
|
1308
|
+
var vm = null;
|
|
1309
|
+
var vmCheckAttempted = false;
|
|
1310
|
+
function getVMModuleName() {
|
|
1311
|
+
return ["node", "vm"].join(":");
|
|
1312
|
+
}
|
|
1313
|
+
__name(getVMModuleName, "getVMModuleName");
|
|
1314
|
+
function tryLoadVM() {
|
|
1315
|
+
if (vmCheckAttempted) {
|
|
1316
|
+
return vm !== null;
|
|
1317
|
+
}
|
|
1318
|
+
vmCheckAttempted = true;
|
|
1319
|
+
try {
|
|
1320
|
+
const nodeRequire = typeof __require !== "undefined" ? __require : null;
|
|
1321
|
+
if (nodeRequire) {
|
|
1322
|
+
vm = nodeRequire(getVMModuleName());
|
|
1323
|
+
return true;
|
|
1324
|
+
}
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
logger.debug("node:vm module not available via require");
|
|
1327
|
+
}
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
__name(tryLoadVM, "tryLoadVM");
|
|
1331
|
+
async function tryLoadVMAsync() {
|
|
1332
|
+
if (vm !== null) {
|
|
1333
|
+
return true;
|
|
1334
|
+
}
|
|
1335
|
+
if (!vmCheckAttempted) {
|
|
1336
|
+
if (tryLoadVM()) {
|
|
1337
|
+
return true;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
try {
|
|
1341
|
+
vm = await import(
|
|
1342
|
+
/* @vite-ignore */
|
|
1343
|
+
getVMModuleName()
|
|
1344
|
+
);
|
|
1345
|
+
return true;
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
logger.debug(
|
|
1348
|
+
"node:vm module not available in this environment (e.g., Deno)"
|
|
1349
|
+
);
|
|
1350
|
+
return false;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
__name(tryLoadVMAsync, "tryLoadVMAsync");
|
|
1354
|
+
function isVMAvailable() {
|
|
1355
|
+
tryLoadVM();
|
|
1356
|
+
return vm !== null;
|
|
1357
|
+
}
|
|
1358
|
+
__name(isVMAvailable, "isVMAvailable");
|
|
1359
|
+
var VMCodeExecutor = class extends BaseCodeExecutor {
|
|
1360
|
+
static {
|
|
1361
|
+
__name(this, "VMCodeExecutor");
|
|
1362
|
+
}
|
|
1363
|
+
defaultTimeout;
|
|
1364
|
+
memoryLimitMb;
|
|
1365
|
+
constructor(client, options) {
|
|
1366
|
+
super(client);
|
|
1367
|
+
this.defaultTimeout = options?.timeoutMs ?? 3e4;
|
|
1368
|
+
this.memoryLimitMb = options?.memoryLimitMb;
|
|
1369
|
+
tryLoadVM();
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Ensure VM module is loaded before execution
|
|
1373
|
+
*/
|
|
1374
|
+
async ensureVMLoaded() {
|
|
1375
|
+
if (vm !== null) {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const loaded = await tryLoadVMAsync();
|
|
1379
|
+
if (!loaded) {
|
|
1380
|
+
throw new Error(
|
|
1381
|
+
"node:vm module is not available in this environment. Please use E2B executor instead or run in a Node.js environment."
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Execute JavaScript/TypeScript code with access to MCP tools.
|
|
1387
|
+
*
|
|
1388
|
+
* @param code - Code to execute
|
|
1389
|
+
* @param timeout - Execution timeout in milliseconds (default: configured timeout or 30000)
|
|
1390
|
+
*/
|
|
1391
|
+
async execute(code, timeout) {
|
|
1392
|
+
const effectiveTimeout = timeout ?? this.defaultTimeout;
|
|
1393
|
+
await this.ensureVMLoaded();
|
|
1394
|
+
await this.ensureServersConnected();
|
|
1395
|
+
const logs = [];
|
|
1396
|
+
const startTime = Date.now();
|
|
1397
|
+
let result = null;
|
|
1398
|
+
let error = null;
|
|
1399
|
+
try {
|
|
1400
|
+
const context = await this._buildContext(logs);
|
|
1401
|
+
const wrappedCode = `
|
|
1402
|
+
(async () => {
|
|
1403
|
+
try {
|
|
1404
|
+
${code}
|
|
1405
|
+
} catch (e) {
|
|
1406
|
+
throw e;
|
|
1407
|
+
}
|
|
1408
|
+
})()
|
|
1409
|
+
`;
|
|
1410
|
+
const script = new vm.Script(wrappedCode, {
|
|
1411
|
+
filename: "agent_code.js"
|
|
1412
|
+
});
|
|
1413
|
+
const promise = script.runInNewContext(context, {
|
|
1414
|
+
timeout: effectiveTimeout,
|
|
1415
|
+
displayErrors: true
|
|
1416
|
+
});
|
|
1417
|
+
result = await promise;
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
error = e.message || String(e);
|
|
1420
|
+
if (e.code === "ERR_SCRIPT_EXECUTION_TIMEOUT" || e.message === "Script execution timed out." || typeof error === "string" && (error.includes("timed out") || error.includes("timeout"))) {
|
|
1421
|
+
error = "Script execution timed out";
|
|
1422
|
+
}
|
|
1423
|
+
if (e.stack) {
|
|
1424
|
+
logger.debug(`Code execution error stack: ${e.stack}`);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
const executionTime = (Date.now() - startTime) / 1e3;
|
|
1428
|
+
return {
|
|
1429
|
+
result,
|
|
1430
|
+
logs,
|
|
1431
|
+
error,
|
|
1432
|
+
execution_time: executionTime
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Build the VM execution context with MCP tools and standard globals.
|
|
1437
|
+
*
|
|
1438
|
+
* @param logs - Array to capture console output
|
|
1439
|
+
*/
|
|
1440
|
+
async _buildContext(logs) {
|
|
1441
|
+
const logHandler = /* @__PURE__ */ __name((...args) => {
|
|
1442
|
+
logs.push(
|
|
1443
|
+
args.map(
|
|
1444
|
+
(arg) => typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg)
|
|
1445
|
+
).join(" ")
|
|
1446
|
+
);
|
|
1447
|
+
}, "logHandler");
|
|
1448
|
+
const sandbox = {
|
|
1449
|
+
console: {
|
|
1450
|
+
log: logHandler,
|
|
1451
|
+
error: /* @__PURE__ */ __name((...args) => {
|
|
1452
|
+
logHandler("[ERROR]", ...args);
|
|
1453
|
+
}, "error"),
|
|
1454
|
+
warn: /* @__PURE__ */ __name((...args) => {
|
|
1455
|
+
logHandler("[WARN]", ...args);
|
|
1456
|
+
}, "warn"),
|
|
1457
|
+
info: logHandler,
|
|
1458
|
+
debug: logHandler
|
|
1459
|
+
},
|
|
1460
|
+
// Standard globals
|
|
1461
|
+
Object,
|
|
1462
|
+
Array,
|
|
1463
|
+
String,
|
|
1464
|
+
Number,
|
|
1465
|
+
Boolean,
|
|
1466
|
+
Date,
|
|
1467
|
+
Math,
|
|
1468
|
+
JSON,
|
|
1469
|
+
RegExp,
|
|
1470
|
+
Map,
|
|
1471
|
+
Set,
|
|
1472
|
+
Promise,
|
|
1473
|
+
parseInt,
|
|
1474
|
+
parseFloat,
|
|
1475
|
+
isNaN,
|
|
1476
|
+
isFinite,
|
|
1477
|
+
encodeURI,
|
|
1478
|
+
decodeURI,
|
|
1479
|
+
encodeURIComponent,
|
|
1480
|
+
decodeURIComponent,
|
|
1481
|
+
setTimeout,
|
|
1482
|
+
clearTimeout,
|
|
1483
|
+
// Helper for tools
|
|
1484
|
+
search_tools: this.createSearchToolsFunction(),
|
|
1485
|
+
__tool_namespaces: []
|
|
1486
|
+
};
|
|
1487
|
+
const toolNamespaces = {};
|
|
1488
|
+
const namespaceInfos = this.getToolNamespaces();
|
|
1489
|
+
for (const { serverName, tools, session } of namespaceInfos) {
|
|
1490
|
+
const serverNamespace = {};
|
|
1491
|
+
for (const tool of tools) {
|
|
1492
|
+
const toolName = tool.name;
|
|
1493
|
+
serverNamespace[toolName] = async (args) => {
|
|
1494
|
+
const result = await session.connector.callTool(toolName, args || {});
|
|
1495
|
+
if (result.content && result.content.length > 0) {
|
|
1496
|
+
const item = result.content[0];
|
|
1497
|
+
if (item.type === "text") {
|
|
1498
|
+
try {
|
|
1499
|
+
return JSON.parse(item.text);
|
|
1500
|
+
} catch {
|
|
1501
|
+
return item.text;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return item;
|
|
1505
|
+
}
|
|
1506
|
+
return result;
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
sandbox[serverName] = serverNamespace;
|
|
1510
|
+
toolNamespaces[serverName] = true;
|
|
1511
|
+
}
|
|
1512
|
+
sandbox.__tool_namespaces = Object.keys(toolNamespaces);
|
|
1513
|
+
return vm.createContext(sandbox);
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Clean up resources.
|
|
1517
|
+
* VM executor doesn't need cleanup, but method kept for interface consistency.
|
|
1518
|
+
*/
|
|
1519
|
+
async cleanup() {
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
// src/connectors/http.ts
|
|
1524
|
+
import {
|
|
1525
|
+
Client
|
|
1526
|
+
} from "@modelcontextprotocol/sdk/client/index.js";
|
|
1527
|
+
import {
|
|
1528
|
+
StreamableHTTPClientTransport,
|
|
1529
|
+
StreamableHTTPError
|
|
1530
|
+
} from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1531
|
+
|
|
1532
|
+
// src/task_managers/sse.ts
|
|
1533
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
1534
|
+
var SseConnectionManager = class extends ConnectionManager {
|
|
1535
|
+
static {
|
|
1536
|
+
__name(this, "SseConnectionManager");
|
|
1537
|
+
}
|
|
1538
|
+
url;
|
|
1539
|
+
opts;
|
|
1540
|
+
_transport = null;
|
|
1541
|
+
reinitializing = false;
|
|
1542
|
+
/**
|
|
1543
|
+
* Create an SSE connection manager.
|
|
1544
|
+
*
|
|
1545
|
+
* @param url The SSE endpoint URL.
|
|
1546
|
+
* @param opts Optional transport options (auth, headers, etc.).
|
|
1547
|
+
*/
|
|
1548
|
+
constructor(url, opts) {
|
|
1549
|
+
super();
|
|
1550
|
+
this.url = typeof url === "string" ? new URL(url) : url;
|
|
1551
|
+
this.opts = opts;
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Spawn a new `SSEClientTransport` and wrap it with 404 handling.
|
|
1555
|
+
* Per MCP spec, clients MUST re-initialize when receiving 404 for stale sessions.
|
|
1556
|
+
*/
|
|
1557
|
+
async establishConnection() {
|
|
1558
|
+
const transport = new SSEClientTransport(this.url, this.opts);
|
|
1559
|
+
const originalSend = transport.send.bind(transport);
|
|
1560
|
+
transport.send = async (message) => {
|
|
1561
|
+
const sendMessage = /* @__PURE__ */ __name(async (msg) => {
|
|
1562
|
+
if (Array.isArray(msg)) {
|
|
1563
|
+
for (const singleMsg of msg) {
|
|
1564
|
+
await originalSend(singleMsg);
|
|
1565
|
+
}
|
|
1566
|
+
} else {
|
|
1567
|
+
await originalSend(msg);
|
|
1568
|
+
}
|
|
1569
|
+
}, "sendMessage");
|
|
1570
|
+
try {
|
|
1571
|
+
await sendMessage(message);
|
|
1572
|
+
} catch (error) {
|
|
1573
|
+
if (error?.code === 404 && transport.sessionId && !this.reinitializing) {
|
|
1574
|
+
logger.warn(
|
|
1575
|
+
`[SSE] Session not found (404), re-initializing per MCP spec...`
|
|
1576
|
+
);
|
|
1577
|
+
this.reinitializing = true;
|
|
1578
|
+
try {
|
|
1579
|
+
transport.sessionId = void 0;
|
|
1580
|
+
await this.reinitialize(transport);
|
|
1581
|
+
logger.debug(
|
|
1582
|
+
`[SSE] Re-initialization successful, retrying request`
|
|
1583
|
+
);
|
|
1584
|
+
await sendMessage(message);
|
|
1585
|
+
} finally {
|
|
1586
|
+
this.reinitializing = false;
|
|
1587
|
+
}
|
|
1588
|
+
} else {
|
|
1589
|
+
throw error;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
this._transport = transport;
|
|
1594
|
+
logger.debug(`${this.constructor.name} connected successfully`);
|
|
1595
|
+
return transport;
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Re-initialize the transport with a new session
|
|
1599
|
+
* This is called when the server returns 404 for a stale session
|
|
1600
|
+
*/
|
|
1601
|
+
async reinitialize(transport) {
|
|
1602
|
+
logger.debug(`[SSE] Re-initialization triggered`);
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Close the underlying transport and clean up resources.
|
|
1606
|
+
*/
|
|
1607
|
+
async closeConnection(_connection) {
|
|
1608
|
+
if (this._transport) {
|
|
1609
|
+
try {
|
|
1610
|
+
await this._transport.close();
|
|
1611
|
+
} catch (e) {
|
|
1612
|
+
logger.warn(`Error closing SSE transport: ${e}`);
|
|
1613
|
+
} finally {
|
|
1614
|
+
this._transport = null;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
|
|
1620
|
+
// src/connectors/http.ts
|
|
1621
|
+
var HttpConnector = class extends BaseConnector {
|
|
1622
|
+
static {
|
|
1623
|
+
__name(this, "HttpConnector");
|
|
1624
|
+
}
|
|
1625
|
+
baseUrl;
|
|
1626
|
+
headers;
|
|
1627
|
+
timeout;
|
|
1628
|
+
sseReadTimeout;
|
|
1629
|
+
customFetch;
|
|
1630
|
+
clientInfo;
|
|
1631
|
+
preferSse;
|
|
1632
|
+
disableSseFallback;
|
|
1633
|
+
gatewayUrl;
|
|
1634
|
+
serverId;
|
|
1635
|
+
reconnectionOptions;
|
|
1636
|
+
transportType = null;
|
|
1637
|
+
streamableTransport = null;
|
|
1638
|
+
constructor(baseUrl, opts = {}) {
|
|
1639
|
+
super(opts);
|
|
1640
|
+
const originalUrl = baseUrl.replace(/\/$/, "");
|
|
1641
|
+
this.gatewayUrl = opts.gatewayUrl;
|
|
1642
|
+
this.serverId = opts.serverId;
|
|
1643
|
+
if (this.gatewayUrl) {
|
|
1644
|
+
this.baseUrl = this.gatewayUrl.replace(/\/$/, "");
|
|
1645
|
+
this.headers = { ...opts.headers ?? {} };
|
|
1646
|
+
this.headers["X-Target-URL"] = originalUrl;
|
|
1647
|
+
if (this.serverId) {
|
|
1648
|
+
this.headers["X-Server-Id"] = this.serverId;
|
|
1649
|
+
}
|
|
1650
|
+
} else {
|
|
1651
|
+
this.baseUrl = originalUrl;
|
|
1652
|
+
this.headers = { ...opts.headers ?? {} };
|
|
1653
|
+
}
|
|
1654
|
+
if (opts.authToken) {
|
|
1655
|
+
this.headers.Authorization = `Bearer ${opts.authToken}`;
|
|
1656
|
+
}
|
|
1657
|
+
this.timeout = opts.timeout ?? 1e4;
|
|
1658
|
+
this.sseReadTimeout = opts.sseReadTimeout ?? 3e5;
|
|
1659
|
+
this.customFetch = opts.fetch;
|
|
1660
|
+
this.clientInfo = opts.clientInfo ?? {
|
|
1661
|
+
name: "http-connector",
|
|
1662
|
+
version: "1.0.0"
|
|
1663
|
+
};
|
|
1664
|
+
this.preferSse = opts.preferSse ?? false;
|
|
1665
|
+
this.disableSseFallback = opts.disableSseFallback ?? false;
|
|
1666
|
+
this.reconnectionOptions = opts.reconnectionOptions;
|
|
1667
|
+
}
|
|
1668
|
+
buildClientOptions() {
|
|
1669
|
+
return {
|
|
1670
|
+
...this.opts.clientOptions || {},
|
|
1671
|
+
capabilities: {
|
|
1672
|
+
...this.opts.clientOptions?.capabilities || {},
|
|
1673
|
+
roots: { listChanged: true },
|
|
1674
|
+
...this.opts.onSampling ? { sampling: {} } : {},
|
|
1675
|
+
...this.opts.onElicitation ?? this.opts.elicitationCallback ? { elicitation: { form: {}, url: {} } } : {}
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
unwrapStreamableError(err) {
|
|
1680
|
+
if (err instanceof StreamableHTTPError) {
|
|
1681
|
+
return err;
|
|
1682
|
+
}
|
|
1683
|
+
if (err instanceof Error && err.cause instanceof StreamableHTTPError) {
|
|
1684
|
+
return err.cause;
|
|
1685
|
+
}
|
|
1686
|
+
return null;
|
|
1687
|
+
}
|
|
1688
|
+
classifyStreamableHttpFailure(err) {
|
|
1689
|
+
let fallbackReason = "Unknown error";
|
|
1690
|
+
let is401Error = false;
|
|
1691
|
+
let httpStatusCode;
|
|
1692
|
+
const streamableErr = this.unwrapStreamableError(err);
|
|
1693
|
+
if (streamableErr) {
|
|
1694
|
+
is401Error = streamableErr.code === 401;
|
|
1695
|
+
httpStatusCode = streamableErr.code;
|
|
1696
|
+
if (streamableErr.code === 400 && streamableErr.message.includes("Missing session ID")) {
|
|
1697
|
+
fallbackReason = "Server requires session ID (FastMCP compatibility) - using SSE transport";
|
|
1698
|
+
logger.warn(`\u26A0\uFE0F ${fallbackReason}`);
|
|
1699
|
+
} else if (streamableErr.code === 404 || streamableErr.code === 405) {
|
|
1700
|
+
fallbackReason = `Server returned ${streamableErr.code} - server likely doesn't support streamable HTTP`;
|
|
1701
|
+
logger.debug(fallbackReason);
|
|
1702
|
+
} else {
|
|
1703
|
+
fallbackReason = `Server returned ${streamableErr.code}: ${streamableErr.message}`;
|
|
1704
|
+
logger.debug(fallbackReason);
|
|
1705
|
+
}
|
|
1706
|
+
return { fallbackReason, is401Error, httpStatusCode };
|
|
1707
|
+
}
|
|
1708
|
+
if (err instanceof Error) {
|
|
1709
|
+
const errorStr = err.toString();
|
|
1710
|
+
const errorMsg = err.message || "";
|
|
1711
|
+
is401Error = errorStr.includes("401") || errorMsg.includes("Unauthorized");
|
|
1712
|
+
if (errorStr.includes("Missing session ID") || errorStr.includes("Bad Request: Missing session ID") || errorMsg.includes("FastMCP session ID error")) {
|
|
1713
|
+
fallbackReason = "Server requires session ID (FastMCP compatibility) - using SSE transport";
|
|
1714
|
+
logger.warn(`\u26A0\uFE0F ${fallbackReason}`);
|
|
1715
|
+
} else if (errorStr.includes("405 Method Not Allowed") || errorStr.includes("404 Not Found")) {
|
|
1716
|
+
fallbackReason = "Server doesn't support streamable HTTP (405/404)";
|
|
1717
|
+
logger.debug(fallbackReason);
|
|
1718
|
+
} else {
|
|
1719
|
+
fallbackReason = `Streamable HTTP failed: ${err.message}`;
|
|
1720
|
+
logger.debug(fallbackReason);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
return { fallbackReason, is401Error, httpStatusCode };
|
|
1724
|
+
}
|
|
1725
|
+
/** Establish connection to the MCP implementation via HTTP (streamable or SSE). */
|
|
1726
|
+
async connect() {
|
|
1727
|
+
if (this.connected) {
|
|
1728
|
+
logger.debug("Already connected to MCP implementation");
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
const baseUrl = this.baseUrl;
|
|
1732
|
+
if (this.preferSse) {
|
|
1733
|
+
logger.debug(`Connecting to MCP implementation via HTTP/SSE: ${baseUrl}`);
|
|
1734
|
+
await this.connectWithSse(baseUrl);
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
logger.debug(`Connecting to MCP implementation via HTTP: ${baseUrl}`);
|
|
1738
|
+
try {
|
|
1739
|
+
logger.debug("\u{1F504} Attempting streamable HTTP transport...");
|
|
1740
|
+
await this.connectWithStreamableHttp(baseUrl);
|
|
1741
|
+
logger.debug("\u2705 Successfully connected via streamable HTTP");
|
|
1742
|
+
} catch (err) {
|
|
1743
|
+
logger.debug("Streamable HTTP connect failed", err);
|
|
1744
|
+
const { fallbackReason, is401Error, httpStatusCode } = this.classifyStreamableHttpFailure(err);
|
|
1745
|
+
if (is401Error) {
|
|
1746
|
+
logger.info("Authentication required - skipping SSE fallback");
|
|
1747
|
+
await this.cleanupResources();
|
|
1748
|
+
const authError = new Error("Authentication required");
|
|
1749
|
+
authError.code = 401;
|
|
1750
|
+
throw authError;
|
|
1751
|
+
}
|
|
1752
|
+
if (this.disableSseFallback) {
|
|
1753
|
+
logger.info("SSE fallback disabled - failing connection");
|
|
1754
|
+
await this.cleanupResources();
|
|
1755
|
+
throw new Error(`Streamable HTTP connection failed: ${fallbackReason}`);
|
|
1756
|
+
}
|
|
1757
|
+
logger.debug("\u{1F504} Falling back to SSE transport...");
|
|
1758
|
+
try {
|
|
1759
|
+
await this.connectWithSse(baseUrl);
|
|
1760
|
+
} catch (sseErr) {
|
|
1761
|
+
logger.error("Failed to connect with both transports:");
|
|
1762
|
+
logger.error(` Streamable HTTP: ${fallbackReason}`);
|
|
1763
|
+
logger.error(` SSE: ${sseErr}`);
|
|
1764
|
+
await this.cleanupResources();
|
|
1765
|
+
const sseIs401 = sseErr?.message?.includes("401") || sseErr?.message?.includes("Unauthorized");
|
|
1766
|
+
if (sseIs401) {
|
|
1767
|
+
const authError = new Error("Authentication required");
|
|
1768
|
+
authError.code = 401;
|
|
1769
|
+
throw authError;
|
|
1770
|
+
}
|
|
1771
|
+
const finalError = new Error(
|
|
1772
|
+
`Could not connect to server with any available transport. Streamable HTTP: ${fallbackReason}`
|
|
1773
|
+
);
|
|
1774
|
+
if (httpStatusCode !== void 0) {
|
|
1775
|
+
Object.defineProperty(finalError, "code", {
|
|
1776
|
+
value: httpStatusCode,
|
|
1777
|
+
writable: false,
|
|
1778
|
+
enumerable: true,
|
|
1779
|
+
configurable: true
|
|
1780
|
+
});
|
|
1781
|
+
logger.debug(
|
|
1782
|
+
`Preserving HTTP status code ${httpStatusCode} in error for proxy fallback detection`
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
throw finalError;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
async connectWithStreamableHttp(baseUrl) {
|
|
1790
|
+
try {
|
|
1791
|
+
logger.debug("[HttpConnector] Connecting with Streamable HTTP", {
|
|
1792
|
+
baseUrl,
|
|
1793
|
+
originalUrl: this.baseUrl,
|
|
1794
|
+
gatewayUrl: this.gatewayUrl || "none",
|
|
1795
|
+
authProviderUrl: this.opts.authProvider?.serverUrl || "none",
|
|
1796
|
+
headers: this.headers
|
|
1797
|
+
});
|
|
1798
|
+
const streamableTransport = new StreamableHTTPClientTransport(
|
|
1799
|
+
new URL(baseUrl),
|
|
1800
|
+
{
|
|
1801
|
+
authProvider: this.opts.authProvider,
|
|
1802
|
+
// ← Pass OAuth provider to SDK
|
|
1803
|
+
fetch: this.customFetch,
|
|
1804
|
+
requestInit: {
|
|
1805
|
+
headers: this.headers
|
|
1806
|
+
},
|
|
1807
|
+
reconnectionOptions: {
|
|
1808
|
+
maxReconnectionDelay: 3e4,
|
|
1809
|
+
initialReconnectionDelay: 1e3,
|
|
1810
|
+
reconnectionDelayGrowFactor: 1.5,
|
|
1811
|
+
maxRetries: 2,
|
|
1812
|
+
...this.reconnectionOptions
|
|
1813
|
+
}
|
|
1814
|
+
// Don't pass sessionId - let the SDK generate it automatically during connect()
|
|
1815
|
+
}
|
|
1816
|
+
);
|
|
1817
|
+
let transport = streamableTransport;
|
|
1818
|
+
if (this.opts.wrapTransport) {
|
|
1819
|
+
const serverId = this.baseUrl;
|
|
1820
|
+
transport = this.opts.wrapTransport(
|
|
1821
|
+
transport,
|
|
1822
|
+
serverId
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
const clientOptions = this.buildClientOptions();
|
|
1826
|
+
logger.debug(
|
|
1827
|
+
`Creating Client with capabilities:`,
|
|
1828
|
+
JSON.stringify(clientOptions.capabilities, null, 2)
|
|
1829
|
+
);
|
|
1830
|
+
this.client = new Client(this.clientInfo, clientOptions);
|
|
1831
|
+
this.setupRootsHandler();
|
|
1832
|
+
logger.debug("Roots handler registered before connect");
|
|
1833
|
+
try {
|
|
1834
|
+
await this.client.connect(transport, {
|
|
1835
|
+
timeout: this.timeout
|
|
1836
|
+
});
|
|
1837
|
+
const sessionId = streamableTransport.sessionId;
|
|
1838
|
+
if (sessionId) {
|
|
1839
|
+
logger.debug(`Session ID obtained: ${sessionId}`);
|
|
1840
|
+
} else {
|
|
1841
|
+
logger.warn(
|
|
1842
|
+
"Session ID not available after connect - this may cause issues with SSE stream"
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
} catch (connectErr) {
|
|
1846
|
+
if (connectErr instanceof Error) {
|
|
1847
|
+
const errMsg = connectErr.message || connectErr.toString();
|
|
1848
|
+
if (errMsg.includes("Missing session ID") || errMsg.includes("Bad Request: Missing session ID") || errMsg.includes("Mcp-Session-Id header is required")) {
|
|
1849
|
+
const wrappedError = new Error(
|
|
1850
|
+
`Session ID error: ${errMsg}. The SDK should automatically extract session ID from initialize response.`
|
|
1851
|
+
);
|
|
1852
|
+
wrappedError.cause = connectErr;
|
|
1853
|
+
throw wrappedError;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
throw connectErr;
|
|
1857
|
+
}
|
|
1858
|
+
this.streamableTransport = streamableTransport;
|
|
1859
|
+
this.connectionManager = {
|
|
1860
|
+
stop: /* @__PURE__ */ __name(async () => {
|
|
1861
|
+
if (this.streamableTransport) {
|
|
1862
|
+
try {
|
|
1863
|
+
await this.streamableTransport.close();
|
|
1864
|
+
} catch (e) {
|
|
1865
|
+
logger.warn(`Error closing Streamable HTTP transport: ${e}`);
|
|
1866
|
+
} finally {
|
|
1867
|
+
this.streamableTransport = null;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}, "stop")
|
|
1871
|
+
};
|
|
1872
|
+
this.connected = true;
|
|
1873
|
+
this.transportType = "streamable-http";
|
|
1874
|
+
this.setupNotificationHandler();
|
|
1875
|
+
this.setupSamplingHandler();
|
|
1876
|
+
this.setupElicitationHandler();
|
|
1877
|
+
logger.debug(
|
|
1878
|
+
`Successfully connected to MCP implementation via streamable HTTP: ${baseUrl}`
|
|
1879
|
+
);
|
|
1880
|
+
this.trackConnectorInit({
|
|
1881
|
+
serverUrl: this.baseUrl,
|
|
1882
|
+
publicIdentifier: `${this.baseUrl} (streamable-http)`
|
|
1883
|
+
});
|
|
1884
|
+
} catch (err) {
|
|
1885
|
+
await this.cleanupResources();
|
|
1886
|
+
throw err;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
async connectWithSse(baseUrl) {
|
|
1890
|
+
try {
|
|
1891
|
+
this.connectionManager = new SseConnectionManager(baseUrl, {
|
|
1892
|
+
authProvider: this.opts.authProvider,
|
|
1893
|
+
// ← Pass OAuth provider to SDK (same as streamable HTTP)
|
|
1894
|
+
requestInit: {
|
|
1895
|
+
headers: this.headers
|
|
1896
|
+
}
|
|
1897
|
+
});
|
|
1898
|
+
let transport = await this.connectionManager.start();
|
|
1899
|
+
if (this.opts.wrapTransport) {
|
|
1900
|
+
const serverId = this.baseUrl;
|
|
1901
|
+
transport = this.opts.wrapTransport(transport, serverId);
|
|
1902
|
+
}
|
|
1903
|
+
const clientOptions = this.buildClientOptions();
|
|
1904
|
+
logger.debug(
|
|
1905
|
+
`Creating Client with capabilities (SSE):`,
|
|
1906
|
+
JSON.stringify(clientOptions.capabilities, null, 2)
|
|
1907
|
+
);
|
|
1908
|
+
this.client = new Client(this.clientInfo, clientOptions);
|
|
1909
|
+
this.setupRootsHandler();
|
|
1910
|
+
logger.debug("Roots handler registered before connect (SSE)");
|
|
1911
|
+
await this.client.connect(transport);
|
|
1912
|
+
this.connected = true;
|
|
1913
|
+
this.transportType = "sse";
|
|
1914
|
+
this.setupNotificationHandler();
|
|
1915
|
+
this.setupSamplingHandler();
|
|
1916
|
+
this.setupElicitationHandler();
|
|
1917
|
+
logger.debug(
|
|
1918
|
+
`Successfully connected to MCP implementation via HTTP/SSE: ${baseUrl}`
|
|
1919
|
+
);
|
|
1920
|
+
this.trackConnectorInit({
|
|
1921
|
+
serverUrl: this.baseUrl,
|
|
1922
|
+
publicIdentifier: `${this.baseUrl} (sse)`
|
|
1923
|
+
});
|
|
1924
|
+
} catch (err) {
|
|
1925
|
+
await this.cleanupResources();
|
|
1926
|
+
throw err;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
get publicIdentifier() {
|
|
1930
|
+
return {
|
|
1931
|
+
type: "http",
|
|
1932
|
+
url: this.baseUrl,
|
|
1933
|
+
transport: this.transportType || "unknown"
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Get the transport type being used (streamable-http or sse)
|
|
1938
|
+
*/
|
|
1939
|
+
getTransportType() {
|
|
1940
|
+
return this.transportType;
|
|
1941
|
+
}
|
|
1942
|
+
// Send the streamable-HTTP DELETE *before* super.cleanupResources() invokes
|
|
1943
|
+
// client.close(). The SDK's transport.close() aborts the shared abort
|
|
1944
|
+
// controller, and terminateSession()'s DELETE fetch reuses that signal —
|
|
1945
|
+
// running it after close() rejects immediately with AbortError.
|
|
1946
|
+
async cleanupResources() {
|
|
1947
|
+
if (this.streamableTransport) {
|
|
1948
|
+
try {
|
|
1949
|
+
await this.streamableTransport.terminateSession();
|
|
1950
|
+
} catch (e) {
|
|
1951
|
+
logger.debug(`Error terminating Streamable HTTP session: ${e}`);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
await super.cleanupResources();
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1957
|
+
|
|
1958
|
+
// src/config.ts
|
|
1959
|
+
function resolveCallbacks(perServer, globalDefaults) {
|
|
1960
|
+
const pickSampling = perServer?.onSampling ?? perServer?.samplingCallback ?? globalDefaults?.onSampling ?? globalDefaults?.samplingCallback;
|
|
1961
|
+
const pickElicitation = perServer?.onElicitation ?? perServer?.elicitationCallback ?? globalDefaults?.onElicitation ?? globalDefaults?.elicitationCallback;
|
|
1962
|
+
const pickNotification = perServer?.onNotification ?? globalDefaults?.onNotification;
|
|
1963
|
+
return {
|
|
1964
|
+
onSampling: pickSampling,
|
|
1965
|
+
onElicitation: pickElicitation,
|
|
1966
|
+
onNotification: pickNotification
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
__name(resolveCallbacks, "resolveCallbacks");
|
|
1970
|
+
function getDefaultClientInfo() {
|
|
1971
|
+
return {
|
|
1972
|
+
name: "mcp-use",
|
|
1973
|
+
title: "mcp-use",
|
|
1974
|
+
version: getPackageVersion(),
|
|
1975
|
+
description: "mcp-use is a complete TypeScript framework for building and using MCP",
|
|
1976
|
+
icons: [
|
|
1977
|
+
{
|
|
1978
|
+
src: "https://mcp-use.com/logo.png"
|
|
1979
|
+
}
|
|
1980
|
+
],
|
|
1981
|
+
websiteUrl: "https://mcp-use.com"
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
__name(getDefaultClientInfo, "getDefaultClientInfo");
|
|
1985
|
+
function normalizeClientInfo(input) {
|
|
1986
|
+
const fallback = getDefaultClientInfo();
|
|
1987
|
+
if (!input || typeof input !== "object") return fallback;
|
|
1988
|
+
const ci = input;
|
|
1989
|
+
if (!ci.name || !ci.version) return fallback;
|
|
1990
|
+
return { ...fallback, ...ci };
|
|
1991
|
+
}
|
|
1992
|
+
__name(normalizeClientInfo, "normalizeClientInfo");
|
|
1993
|
+
function createConnectorFromConfig(serverConfig, connectorOptions) {
|
|
1994
|
+
const clientInfo = normalizeClientInfo(serverConfig.clientInfo);
|
|
1995
|
+
if ("command" in serverConfig && "args" in serverConfig) {
|
|
1996
|
+
throw new Error(
|
|
1997
|
+
"Stdio connector is not supported in this environment. Stdio connections require Node.js and are only available in the Node.js MCPClient."
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
if ("url" in serverConfig) {
|
|
2001
|
+
const transport = serverConfig.transport || "http";
|
|
2002
|
+
return new HttpConnector(serverConfig.url, {
|
|
2003
|
+
headers: serverConfig.headers,
|
|
2004
|
+
fetch: serverConfig.fetch,
|
|
2005
|
+
authToken: serverConfig.auth_token || serverConfig.authToken,
|
|
2006
|
+
authProvider: serverConfig.authProvider,
|
|
2007
|
+
// Only force SSE if explicitly requested
|
|
2008
|
+
preferSse: serverConfig.preferSse || transport === "sse",
|
|
2009
|
+
// Disable SSE fallback if explicitly disabled in config
|
|
2010
|
+
disableSseFallback: serverConfig.disableSseFallback,
|
|
2011
|
+
clientInfo,
|
|
2012
|
+
...connectorOptions
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
throw new Error("Cannot determine connector type from config");
|
|
2016
|
+
}
|
|
2017
|
+
__name(createConnectorFromConfig, "createConnectorFromConfig");
|
|
2018
|
+
|
|
2019
|
+
// src/config-file.ts
|
|
2020
|
+
function loadConfigFile(filepath) {
|
|
2021
|
+
const { readFileSync } = __require("fs");
|
|
2022
|
+
const raw = readFileSync(filepath, "utf-8");
|
|
2023
|
+
return JSON.parse(raw);
|
|
2024
|
+
}
|
|
2025
|
+
__name(loadConfigFile, "loadConfigFile");
|
|
2026
|
+
|
|
2027
|
+
// src/client/elicitation-helpers.ts
|
|
2028
|
+
function hasRequestedSchema(params) {
|
|
2029
|
+
return "requestedSchema" in params && params.requestedSchema != null;
|
|
2030
|
+
}
|
|
2031
|
+
__name(hasRequestedSchema, "hasRequestedSchema");
|
|
2032
|
+
function getDefaults(params) {
|
|
2033
|
+
const content = {};
|
|
2034
|
+
if (!hasRequestedSchema(params)) return content;
|
|
2035
|
+
const schema = params.requestedSchema;
|
|
2036
|
+
const properties = schema.properties ?? {};
|
|
2037
|
+
for (const [fieldName, fieldSchema] of Object.entries(properties)) {
|
|
2038
|
+
const field = fieldSchema;
|
|
2039
|
+
if ("default" in field) {
|
|
2040
|
+
const v = field.default;
|
|
2041
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || Array.isArray(v) && v.every((x) => typeof x === "string")) {
|
|
2042
|
+
content[fieldName] = v;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return content;
|
|
2047
|
+
}
|
|
2048
|
+
__name(getDefaults, "getDefaults");
|
|
2049
|
+
function applyDefaults(params, partial) {
|
|
2050
|
+
const defaults = getDefaults(params);
|
|
2051
|
+
if (partial == null) return defaults;
|
|
2052
|
+
return { ...defaults, ...partial };
|
|
2053
|
+
}
|
|
2054
|
+
__name(applyDefaults, "applyDefaults");
|
|
2055
|
+
function acceptWithDefaults(params) {
|
|
2056
|
+
return { action: "accept", content: getDefaults(params) };
|
|
2057
|
+
}
|
|
2058
|
+
__name(acceptWithDefaults, "acceptWithDefaults");
|
|
2059
|
+
function accept(content) {
|
|
2060
|
+
return { action: "accept", content };
|
|
2061
|
+
}
|
|
2062
|
+
__name(accept, "accept");
|
|
2063
|
+
function decline(_reason) {
|
|
2064
|
+
return { action: "decline" };
|
|
2065
|
+
}
|
|
2066
|
+
__name(decline, "decline");
|
|
2067
|
+
function cancel() {
|
|
2068
|
+
return { action: "cancel" };
|
|
2069
|
+
}
|
|
2070
|
+
__name(cancel, "cancel");
|
|
2071
|
+
function reject(reason) {
|
|
2072
|
+
return decline(reason);
|
|
2073
|
+
}
|
|
2074
|
+
__name(reject, "reject");
|
|
2075
|
+
function validate(params, content) {
|
|
2076
|
+
if (!hasRequestedSchema(params)) {
|
|
2077
|
+
return { valid: true };
|
|
2078
|
+
}
|
|
2079
|
+
try {
|
|
2080
|
+
const zodSchema = JSONSchemaToZod.convert(
|
|
2081
|
+
params.requestedSchema
|
|
2082
|
+
);
|
|
2083
|
+
const result = zodSchema.safeParse(content);
|
|
2084
|
+
if (result.success) {
|
|
2085
|
+
return { valid: true };
|
|
2086
|
+
}
|
|
2087
|
+
const messages = result.error.issues.map(
|
|
2088
|
+
(i) => i.path.length > 0 ? `${i.path.join(".")}: ${i.message}` : i.message
|
|
2089
|
+
);
|
|
2090
|
+
return { valid: false, errors: messages };
|
|
2091
|
+
} catch {
|
|
2092
|
+
return { valid: false, errors: ["Unsupported or invalid schema"] };
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
__name(validate, "validate");
|
|
2096
|
+
|
|
2097
|
+
// src/client.ts
|
|
2098
|
+
function trackNodeClientInit(config, codeMode, callbacks) {
|
|
2099
|
+
const servers = Object.keys(config.mcpServers ?? {});
|
|
2100
|
+
const hasSamplingCallback = !!(callbacks.onSampling ?? callbacks.samplingCallback);
|
|
2101
|
+
const hasElicitationCallback = !!(callbacks.onElicitation ?? callbacks.elicitationCallback);
|
|
2102
|
+
Tel.getInstance().trackMCPClientInit({
|
|
2103
|
+
codeMode,
|
|
2104
|
+
sandbox: false,
|
|
2105
|
+
allCallbacks: hasSamplingCallback && hasElicitationCallback,
|
|
2106
|
+
verify: false,
|
|
2107
|
+
servers,
|
|
2108
|
+
numServers: servers.length,
|
|
2109
|
+
isBrowser: false
|
|
2110
|
+
}).catch((e) => logger.debug(`Failed to track MCPClient init: ${e}`));
|
|
2111
|
+
}
|
|
2112
|
+
__name(trackNodeClientInit, "trackNodeClientInit");
|
|
2113
|
+
var MCPClient = class _MCPClient extends BaseMCPClient {
|
|
2114
|
+
static {
|
|
2115
|
+
__name(this, "MCPClient");
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Gets the mcp-use package version.
|
|
2119
|
+
*
|
|
2120
|
+
* This static method returns the version string of the installed mcp-use package,
|
|
2121
|
+
* which is useful for debugging and compatibility checks.
|
|
2122
|
+
*
|
|
2123
|
+
* @returns The package version string (e.g., "1.13.2")
|
|
2124
|
+
*
|
|
2125
|
+
* @example
|
|
2126
|
+
* ```typescript
|
|
2127
|
+
* console.log(`mcp-use version: ${MCPClient.getPackageVersion()}`);
|
|
2128
|
+
* ```
|
|
2129
|
+
*/
|
|
2130
|
+
static getPackageVersion() {
|
|
2131
|
+
return getPackageVersion();
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* Indicates whether code execution mode is enabled.
|
|
2135
|
+
*
|
|
2136
|
+
* When true, the client provides special tools for executing code dynamically
|
|
2137
|
+
* through the {@link executeCode} and {@link searchTools} methods.
|
|
2138
|
+
*
|
|
2139
|
+
* @example
|
|
2140
|
+
* ```typescript
|
|
2141
|
+
* if (client.codeMode) {
|
|
2142
|
+
* const result = await client.executeCode('return 2 + 2');
|
|
2143
|
+
* console.log(result.output); // "4"
|
|
2144
|
+
* }
|
|
2145
|
+
* ```
|
|
2146
|
+
*/
|
|
2147
|
+
codeMode = false;
|
|
2148
|
+
_codeExecutor = null;
|
|
2149
|
+
_customCodeExecutor = null;
|
|
2150
|
+
_codeExecutorConfig = "vm";
|
|
2151
|
+
_executorOptions;
|
|
2152
|
+
_globalCallbacks;
|
|
2153
|
+
/**
|
|
2154
|
+
* Creates a new MCPClient instance.
|
|
2155
|
+
*
|
|
2156
|
+
* The client can be initialized with either a configuration object, a path to
|
|
2157
|
+
* a configuration file, or no configuration at all (servers can be added later
|
|
2158
|
+
* using {@link addServer}).
|
|
2159
|
+
*
|
|
2160
|
+
* @param config - Configuration object or path to JSON config file. If omitted,
|
|
2161
|
+
* starts with empty configuration
|
|
2162
|
+
* @param options - Optional client behavior configuration
|
|
2163
|
+
* @param options.codeMode - Enable code execution mode (boolean or advanced config)
|
|
2164
|
+
* @param options.onSampling - Callback for handling sampling requests from servers
|
|
2165
|
+
* @param options.elicitationCallback - Callback for handling elicitation requests
|
|
2166
|
+
*
|
|
2167
|
+
* @example
|
|
2168
|
+
* ```typescript
|
|
2169
|
+
* // From config file
|
|
2170
|
+
* const client = new MCPClient('./mcp-config.json');
|
|
2171
|
+
* ```
|
|
2172
|
+
*
|
|
2173
|
+
* @example
|
|
2174
|
+
* ```typescript
|
|
2175
|
+
* // From inline config
|
|
2176
|
+
* const client = new MCPClient({
|
|
2177
|
+
* mcpServers: {
|
|
2178
|
+
* 'my-server': {
|
|
2179
|
+
* command: 'node',
|
|
2180
|
+
* args: ['server.js']
|
|
2181
|
+
* }
|
|
2182
|
+
* }
|
|
2183
|
+
* });
|
|
2184
|
+
* ```
|
|
2185
|
+
*
|
|
2186
|
+
* @example
|
|
2187
|
+
* ```typescript
|
|
2188
|
+
* // With code mode enabled
|
|
2189
|
+
* const client = new MCPClient('./config.json', {
|
|
2190
|
+
* codeMode: true
|
|
2191
|
+
* });
|
|
2192
|
+
* ```
|
|
2193
|
+
*
|
|
2194
|
+
* @example
|
|
2195
|
+
* ```typescript
|
|
2196
|
+
* // With sampling callback
|
|
2197
|
+
* const client = new MCPClient('./config.json', {
|
|
2198
|
+
* onSampling: async (params) => {
|
|
2199
|
+
* // Call your LLM here
|
|
2200
|
+
* return anthropic.messages.create(params);
|
|
2201
|
+
* }
|
|
2202
|
+
* });
|
|
2203
|
+
* ```
|
|
2204
|
+
*
|
|
2205
|
+
* @see {@link fromDict} for creating from config object (alternative syntax)
|
|
2206
|
+
* @see {@link fromConfigFile} for creating from file (alternative syntax)
|
|
2207
|
+
*/
|
|
2208
|
+
constructor(config, options) {
|
|
2209
|
+
if (config) {
|
|
2210
|
+
if (typeof config === "string") {
|
|
2211
|
+
super(loadConfigFile(config));
|
|
2212
|
+
} else {
|
|
2213
|
+
super(config);
|
|
2214
|
+
}
|
|
2215
|
+
} else {
|
|
2216
|
+
super();
|
|
2217
|
+
}
|
|
2218
|
+
let codeModeEnabled = false;
|
|
2219
|
+
let executorConfig = "vm";
|
|
2220
|
+
let executorOptions;
|
|
2221
|
+
if (options?.codeMode) {
|
|
2222
|
+
if (typeof options.codeMode === "boolean") {
|
|
2223
|
+
codeModeEnabled = options.codeMode;
|
|
2224
|
+
} else {
|
|
2225
|
+
codeModeEnabled = options.codeMode.enabled;
|
|
2226
|
+
executorConfig = options.codeMode.executor ?? "vm";
|
|
2227
|
+
executorOptions = options.codeMode.executorOptions;
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
this.codeMode = codeModeEnabled;
|
|
2231
|
+
this._codeExecutorConfig = executorConfig;
|
|
2232
|
+
this._executorOptions = executorOptions;
|
|
2233
|
+
const configRoot = this.config;
|
|
2234
|
+
this._globalCallbacks = {
|
|
2235
|
+
onSampling: options?.onSampling ?? options?.samplingCallback ?? configRoot?.onSampling ?? configRoot?.samplingCallback,
|
|
2236
|
+
samplingCallback: options?.samplingCallback ?? configRoot?.samplingCallback,
|
|
2237
|
+
onElicitation: options?.onElicitation ?? options?.elicitationCallback ?? configRoot?.onElicitation ?? configRoot?.elicitationCallback,
|
|
2238
|
+
elicitationCallback: options?.elicitationCallback ?? configRoot?.elicitationCallback,
|
|
2239
|
+
onNotification: options?.onNotification ?? configRoot?.onNotification
|
|
2240
|
+
};
|
|
2241
|
+
if (options?.samplingCallback && !options?.onSampling) {
|
|
2242
|
+
console.warn(
|
|
2243
|
+
'[MCPClient] The "samplingCallback" option is deprecated. Use "onSampling" instead.'
|
|
2244
|
+
);
|
|
2245
|
+
}
|
|
2246
|
+
if (options?.elicitationCallback && !options?.onElicitation) {
|
|
2247
|
+
console.warn(
|
|
2248
|
+
'[MCPClient] The "elicitationCallback" option is deprecated. Use "onElicitation" instead.'
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
if (this.codeMode) {
|
|
2252
|
+
this._setupCodeModeConnector();
|
|
2253
|
+
}
|
|
2254
|
+
trackNodeClientInit(this.config, this.codeMode, this._globalCallbacks);
|
|
2255
|
+
}
|
|
2256
|
+
/**
|
|
2257
|
+
* Creates a client instance from a configuration dictionary.
|
|
2258
|
+
*
|
|
2259
|
+
* This static factory method provides an alternative syntax for creating
|
|
2260
|
+
* a client from an inline configuration object.
|
|
2261
|
+
*
|
|
2262
|
+
* @param cfg - Configuration dictionary with server definitions
|
|
2263
|
+
* @param options - Optional client behavior configuration
|
|
2264
|
+
* @returns New MCPClient instance
|
|
2265
|
+
*
|
|
2266
|
+
* @example
|
|
2267
|
+
* ```typescript
|
|
2268
|
+
* const client = MCPClient.fromDict({
|
|
2269
|
+
* mcpServers: {
|
|
2270
|
+
* 'filesystem': {
|
|
2271
|
+
* command: 'npx',
|
|
2272
|
+
* args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp']
|
|
2273
|
+
* }
|
|
2274
|
+
* }
|
|
2275
|
+
* });
|
|
2276
|
+
* ```
|
|
2277
|
+
*
|
|
2278
|
+
* @see {@link constructor} for direct instantiation
|
|
2279
|
+
* @see {@link fromConfigFile} for loading from file
|
|
2280
|
+
*/
|
|
2281
|
+
static fromDict(cfg, options) {
|
|
2282
|
+
return new _MCPClient(cfg, options);
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Creates a client instance from a configuration file.
|
|
2286
|
+
*
|
|
2287
|
+
* This static factory method loads MCP server configurations from a JSON
|
|
2288
|
+
* file and creates a new client instance.
|
|
2289
|
+
*
|
|
2290
|
+
* @param path - Path to the JSON configuration file
|
|
2291
|
+
* @param options - Optional client behavior configuration
|
|
2292
|
+
* @returns New MCPClient instance
|
|
2293
|
+
* @throws {Error} If the file cannot be read or parsed
|
|
2294
|
+
*
|
|
2295
|
+
* @example
|
|
2296
|
+
* ```typescript
|
|
2297
|
+
* const client = MCPClient.fromConfigFile('./mcp-config.json');
|
|
2298
|
+
* await client.createAllSessions();
|
|
2299
|
+
* ```
|
|
2300
|
+
*
|
|
2301
|
+
* @example
|
|
2302
|
+
* ```typescript
|
|
2303
|
+
* // With code mode
|
|
2304
|
+
* const client = MCPClient.fromConfigFile('./config.json', {
|
|
2305
|
+
* codeMode: true
|
|
2306
|
+
* });
|
|
2307
|
+
* ```
|
|
2308
|
+
*
|
|
2309
|
+
* @see {@link constructor} for direct instantiation
|
|
2310
|
+
* @see {@link fromDict} for inline configuration
|
|
2311
|
+
*/
|
|
2312
|
+
static fromConfigFile(path2, options) {
|
|
2313
|
+
return new _MCPClient(loadConfigFile(path2), options);
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Saves the current configuration to a file.
|
|
2317
|
+
*
|
|
2318
|
+
* This Node.js-specific method writes the client's current configuration
|
|
2319
|
+
* (including all server definitions) to a JSON file. The directory will be
|
|
2320
|
+
* created if it doesn't exist.
|
|
2321
|
+
*
|
|
2322
|
+
* @param filepath - Path where the configuration file should be saved
|
|
2323
|
+
*
|
|
2324
|
+
* @example
|
|
2325
|
+
* ```typescript
|
|
2326
|
+
* const client = new MCPClient();
|
|
2327
|
+
* client.addServer('my-server', {
|
|
2328
|
+
* command: 'node',
|
|
2329
|
+
* args: ['server.js']
|
|
2330
|
+
* });
|
|
2331
|
+
*
|
|
2332
|
+
* // Save configuration for later use
|
|
2333
|
+
* client.saveConfig('./mcp-config.json');
|
|
2334
|
+
* ```
|
|
2335
|
+
*
|
|
2336
|
+
* @see {@link fromConfigFile} for loading configurations
|
|
2337
|
+
*/
|
|
2338
|
+
saveConfig(filepath) {
|
|
2339
|
+
const dir = path.dirname(filepath);
|
|
2340
|
+
if (!fs.existsSync(dir)) {
|
|
2341
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2342
|
+
}
|
|
2343
|
+
fs.writeFileSync(filepath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
2344
|
+
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Create a connector from server configuration (Node.js version)
|
|
2347
|
+
* Supports all connector types including StdioConnector (lazy-loaded to avoid pulling Node-only code into browser bundles).
|
|
2348
|
+
*/
|
|
2349
|
+
async createConnectorFromConfig(serverConfig) {
|
|
2350
|
+
const resolved = resolveCallbacks(
|
|
2351
|
+
serverConfig,
|
|
2352
|
+
this._globalCallbacks
|
|
2353
|
+
);
|
|
2354
|
+
const merged = {
|
|
2355
|
+
...serverConfig,
|
|
2356
|
+
clientInfo: serverConfig.clientInfo ?? this.config.clientInfo
|
|
2357
|
+
};
|
|
2358
|
+
if ("command" in merged && "args" in merged) {
|
|
2359
|
+
const { StdioConnector } = await import("./stdio-FUBSRH66.js");
|
|
2360
|
+
const stdioConfig = merged;
|
|
2361
|
+
return new StdioConnector({
|
|
2362
|
+
command: stdioConfig.command,
|
|
2363
|
+
args: stdioConfig.args,
|
|
2364
|
+
env: stdioConfig.env,
|
|
2365
|
+
cwd: stdioConfig.cwd,
|
|
2366
|
+
clientInfo: normalizeClientInfo(merged.clientInfo),
|
|
2367
|
+
onSampling: resolved.onSampling,
|
|
2368
|
+
onElicitation: resolved.onElicitation,
|
|
2369
|
+
onNotification: resolved.onNotification
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2372
|
+
return createConnectorFromConfig(merged, {
|
|
2373
|
+
onSampling: resolved.onSampling,
|
|
2374
|
+
onElicitation: resolved.onElicitation,
|
|
2375
|
+
onNotification: resolved.onNotification
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
_setupCodeModeConnector() {
|
|
2379
|
+
logger.debug("Code mode connector initialized as internal meta server");
|
|
2380
|
+
const connector = new CodeModeConnector(this);
|
|
2381
|
+
const session = new MCPSession(connector);
|
|
2382
|
+
this.sessions["code_mode"] = session;
|
|
2383
|
+
this.activeSessions.push("code_mode");
|
|
2384
|
+
}
|
|
2385
|
+
_ensureCodeExecutor() {
|
|
2386
|
+
if (!this._codeExecutor) {
|
|
2387
|
+
const config = this._codeExecutorConfig;
|
|
2388
|
+
if (config instanceof BaseCodeExecutor) {
|
|
2389
|
+
this._codeExecutor = config;
|
|
2390
|
+
} else if (typeof config === "function") {
|
|
2391
|
+
this._customCodeExecutor = config;
|
|
2392
|
+
throw new Error(
|
|
2393
|
+
"Custom executor function should be handled in executeCode"
|
|
2394
|
+
);
|
|
2395
|
+
} else if (config === "e2b") {
|
|
2396
|
+
const opts = this._executorOptions;
|
|
2397
|
+
if (!opts?.apiKey) {
|
|
2398
|
+
logger.warn("E2B executor requires apiKey. Falling back to VM.");
|
|
2399
|
+
try {
|
|
2400
|
+
this._codeExecutor = new VMCodeExecutor(
|
|
2401
|
+
this,
|
|
2402
|
+
this._executorOptions
|
|
2403
|
+
);
|
|
2404
|
+
} catch (error) {
|
|
2405
|
+
throw new Error(
|
|
2406
|
+
"VM executor is not available in this environment and E2B API key is not provided. Please provide an E2B API key or run in a Node.js environment."
|
|
2407
|
+
);
|
|
2408
|
+
}
|
|
2409
|
+
} else {
|
|
2410
|
+
this._codeExecutor = new E2BCodeExecutor(this, opts);
|
|
2411
|
+
}
|
|
2412
|
+
} else {
|
|
2413
|
+
try {
|
|
2414
|
+
this._codeExecutor = new VMCodeExecutor(
|
|
2415
|
+
this,
|
|
2416
|
+
this._executorOptions
|
|
2417
|
+
);
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
const e2bOpts = this._executorOptions;
|
|
2420
|
+
const e2bApiKey = e2bOpts?.apiKey || process.env.E2B_API_KEY;
|
|
2421
|
+
if (e2bApiKey) {
|
|
2422
|
+
logger.info(
|
|
2423
|
+
"VM executor not available in this environment. Falling back to E2B."
|
|
2424
|
+
);
|
|
2425
|
+
this._codeExecutor = new E2BCodeExecutor(this, {
|
|
2426
|
+
...e2bOpts,
|
|
2427
|
+
apiKey: e2bApiKey
|
|
2428
|
+
});
|
|
2429
|
+
} else {
|
|
2430
|
+
throw new Error(
|
|
2431
|
+
"VM executor is not available in this environment. Please provide an E2B API key via executorOptions or E2B_API_KEY environment variable, or run in a Node.js environment."
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
return this._codeExecutor;
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Executes JavaScript/TypeScript code in a sandboxed environment.
|
|
2441
|
+
*
|
|
2442
|
+
* This method is only available when code mode is enabled. It executes the
|
|
2443
|
+
* provided code in an isolated environment (VM or E2B sandbox) and returns
|
|
2444
|
+
* the results including stdout, stderr, and return value.
|
|
2445
|
+
*
|
|
2446
|
+
* @param code - JavaScript/TypeScript code to execute
|
|
2447
|
+
* @param timeout - Optional execution timeout in milliseconds
|
|
2448
|
+
* @returns Execution result with output, errors, and return value
|
|
2449
|
+
* @throws {Error} If code mode is not enabled
|
|
2450
|
+
*
|
|
2451
|
+
* @example
|
|
2452
|
+
* ```typescript
|
|
2453
|
+
* const client = new MCPClient('./config.json', { codeMode: true });
|
|
2454
|
+
*
|
|
2455
|
+
* const result = await client.executeCode(`
|
|
2456
|
+
* console.log('Hello, world!');
|
|
2457
|
+
* return 2 + 2;
|
|
2458
|
+
* `);
|
|
2459
|
+
*
|
|
2460
|
+
* console.log(result.stdout); // "Hello, world!\n"
|
|
2461
|
+
* console.log(result.returnValue); // 4
|
|
2462
|
+
* ```
|
|
2463
|
+
*
|
|
2464
|
+
* @example
|
|
2465
|
+
* ```typescript
|
|
2466
|
+
* // With timeout
|
|
2467
|
+
* try {
|
|
2468
|
+
* const result = await client.executeCode('while(true) {}', 1000);
|
|
2469
|
+
* } catch (error) {
|
|
2470
|
+
* console.log('Execution timed out');
|
|
2471
|
+
* }
|
|
2472
|
+
* ```
|
|
2473
|
+
*
|
|
2474
|
+
* @see {@link searchTools} for discovering available tools in code mode
|
|
2475
|
+
*/
|
|
2476
|
+
async executeCode(code, timeout) {
|
|
2477
|
+
if (!this.codeMode) {
|
|
2478
|
+
throw new Error("Code execution mode is not enabled");
|
|
2479
|
+
}
|
|
2480
|
+
if (this._customCodeExecutor) {
|
|
2481
|
+
return this._customCodeExecutor(code, timeout);
|
|
2482
|
+
}
|
|
2483
|
+
return this._ensureCodeExecutor().execute(code, timeout);
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2486
|
+
* Searches for available tools across all MCP servers.
|
|
2487
|
+
*
|
|
2488
|
+
* This method is only available when code mode is enabled. It searches
|
|
2489
|
+
* through tools from all active servers and returns matching tools based
|
|
2490
|
+
* on the query and detail level.
|
|
2491
|
+
*
|
|
2492
|
+
* @param query - Optional search query to filter tools (defaults to empty string for all tools)
|
|
2493
|
+
* @param detailLevel - Level of detail to return: "names", "descriptions", or "full"
|
|
2494
|
+
* @returns Tool search results with matching tools
|
|
2495
|
+
* @throws {Error} If code mode is not enabled
|
|
2496
|
+
*
|
|
2497
|
+
* @example
|
|
2498
|
+
* ```typescript
|
|
2499
|
+
* const client = new MCPClient('./config.json', { codeMode: true });
|
|
2500
|
+
* await client.createAllSessions();
|
|
2501
|
+
*
|
|
2502
|
+
* // Search for all tools
|
|
2503
|
+
* const allTools = await client.searchTools();
|
|
2504
|
+
* console.log(`Found ${allTools.tools.length} tools`);
|
|
2505
|
+
*
|
|
2506
|
+
* // Search for specific tools
|
|
2507
|
+
* const fileTools = await client.searchTools('file', 'descriptions');
|
|
2508
|
+
* ```
|
|
2509
|
+
*
|
|
2510
|
+
* @see {@link executeCode} for executing code in code mode
|
|
2511
|
+
*/
|
|
2512
|
+
async searchTools(query = "", detailLevel = "full") {
|
|
2513
|
+
if (!this.codeMode) {
|
|
2514
|
+
throw new Error("Code execution mode is not enabled");
|
|
2515
|
+
}
|
|
2516
|
+
return this._ensureCodeExecutor().createSearchToolsFunction()(
|
|
2517
|
+
query,
|
|
2518
|
+
detailLevel
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2521
|
+
/**
|
|
2522
|
+
* Gets the names of all configured MCP servers (excluding internal servers).
|
|
2523
|
+
*
|
|
2524
|
+
* This method overrides the base implementation to filter out internal
|
|
2525
|
+
* meta-servers like the code mode server, which is an implementation detail
|
|
2526
|
+
* not intended for direct user interaction.
|
|
2527
|
+
*
|
|
2528
|
+
* @returns Array of user-configured server names
|
|
2529
|
+
*
|
|
2530
|
+
* @example
|
|
2531
|
+
* ```typescript
|
|
2532
|
+
* const names = client.getServerNames();
|
|
2533
|
+
* console.log(`User servers: ${names.join(', ')}`);
|
|
2534
|
+
* // Note: 'code_mode' is excluded even if code mode is enabled
|
|
2535
|
+
* ```
|
|
2536
|
+
*
|
|
2537
|
+
* @see {@link activeSessions} for servers with active sessions
|
|
2538
|
+
*/
|
|
2539
|
+
getServerNames() {
|
|
2540
|
+
const isCodeModeEnabled = this.codeMode;
|
|
2541
|
+
return super.getServerNames().filter((name) => {
|
|
2542
|
+
return !isCodeModeEnabled || name !== "code_mode";
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
/**
|
|
2546
|
+
* Closes the client and cleans up all resources.
|
|
2547
|
+
*
|
|
2548
|
+
* This method performs a complete cleanup by:
|
|
2549
|
+
* 1. Shutting down code executors (VM or E2B sandboxes)
|
|
2550
|
+
* 2. Closing all active MCP sessions
|
|
2551
|
+
* 3. Releasing any other held resources
|
|
2552
|
+
*
|
|
2553
|
+
* Always call this method when you're done with the client to ensure
|
|
2554
|
+
* proper resource cleanup, especially when using E2B sandboxes which
|
|
2555
|
+
* have associated costs.
|
|
2556
|
+
*
|
|
2557
|
+
* @example
|
|
2558
|
+
* ```typescript
|
|
2559
|
+
* const client = new MCPClient('./config.json', { codeMode: true });
|
|
2560
|
+
* await client.createAllSessions();
|
|
2561
|
+
*
|
|
2562
|
+
* // Do work...
|
|
2563
|
+
*
|
|
2564
|
+
* // Clean up
|
|
2565
|
+
* await client.close();
|
|
2566
|
+
* ```
|
|
2567
|
+
*
|
|
2568
|
+
* @example
|
|
2569
|
+
* ```typescript
|
|
2570
|
+
* // Use in shutdown handler
|
|
2571
|
+
* process.on('SIGINT', async () => {
|
|
2572
|
+
* console.log('Shutting down...');
|
|
2573
|
+
* await client.close();
|
|
2574
|
+
* process.exit(0);
|
|
2575
|
+
* });
|
|
2576
|
+
* ```
|
|
2577
|
+
*
|
|
2578
|
+
* @see {@link closeAllSessions} for closing just the sessions
|
|
2579
|
+
*/
|
|
2580
|
+
async close() {
|
|
2581
|
+
if (this._codeExecutor) {
|
|
2582
|
+
await this._codeExecutor.cleanup();
|
|
2583
|
+
this._codeExecutor = null;
|
|
2584
|
+
}
|
|
2585
|
+
await this.closeAllSessions();
|
|
2586
|
+
}
|
|
2587
|
+
};
|
|
2588
|
+
|
|
2589
|
+
export {
|
|
2590
|
+
MCPSession,
|
|
2591
|
+
BaseMCPClient,
|
|
2592
|
+
BaseCodeExecutor,
|
|
2593
|
+
E2BCodeExecutor,
|
|
2594
|
+
isVMAvailable,
|
|
2595
|
+
VMCodeExecutor,
|
|
2596
|
+
HttpConnector,
|
|
2597
|
+
resolveCallbacks,
|
|
2598
|
+
normalizeClientInfo,
|
|
2599
|
+
loadConfigFile,
|
|
2600
|
+
getDefaults,
|
|
2601
|
+
applyDefaults,
|
|
2602
|
+
acceptWithDefaults,
|
|
2603
|
+
accept,
|
|
2604
|
+
decline,
|
|
2605
|
+
cancel,
|
|
2606
|
+
reject,
|
|
2607
|
+
validate,
|
|
2608
|
+
MCPClient
|
|
2609
|
+
};
|