@mcpjam/inspector 0.9.3 → 0.9.5

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.
@@ -1,213 +1,58 @@
1
1
  // index.ts
2
2
  import { serve } from "@hono/node-server";
3
- import { Hono as Hono8 } from "hono";
3
+ import { Hono as Hono10 } from "hono";
4
4
  import { cors } from "hono/cors";
5
5
  import { logger } from "hono/logger";
6
6
  import { serveStatic } from "@hono/node-server/serve-static";
7
7
  import { readFileSync } from "fs";
8
- import { join } from "path";
8
+ import { join as join2 } from "path";
9
9
 
10
10
  // routes/mcp/index.ts
11
- import { Hono as Hono7 } from "hono";
11
+ import { Hono as Hono9 } from "hono";
12
12
 
13
13
  // routes/mcp/connect.ts
14
14
  import { Hono } from "hono";
15
-
16
- // utils/mcp-utils.ts
17
- import { MCPClient } from "@mastra/mcp";
18
- function validateServerConfig(serverConfig) {
19
- if (!serverConfig) {
20
- return {
21
- success: false,
22
- error: {
23
- message: "Server configuration is required",
24
- status: 400
25
- }
26
- };
27
- }
28
- const config = { ...serverConfig };
29
- if (config.url) {
30
- try {
31
- if (typeof config.url === "string") {
32
- const parsed = new URL(config.url);
33
- parsed.search = "";
34
- parsed.hash = "";
35
- config.url = parsed;
36
- } else if (typeof config.url === "object" && !config.url.href) {
37
- return {
38
- success: false,
39
- error: {
40
- message: "Invalid URL configuration",
41
- status: 400
42
- }
43
- };
44
- }
45
- if (config.oauth?.access_token) {
46
- const authHeaders = {
47
- Authorization: `Bearer ${config.oauth.access_token}`,
48
- ...config.requestInit?.headers || {}
49
- };
50
- config.requestInit = {
51
- ...config.requestInit,
52
- headers: authHeaders
53
- };
54
- config.eventSourceInit = {
55
- fetch(input, init) {
56
- const headers = new Headers(init?.headers || {});
57
- headers.set(
58
- "Authorization",
59
- `Bearer ${config.oauth.access_token}`
60
- );
61
- if (config.requestInit?.headers) {
62
- const requestHeaders = new Headers(config.requestInit.headers);
63
- requestHeaders.forEach((value, key) => {
64
- if (key.toLowerCase() !== "authorization") {
65
- headers.set(key, value);
66
- }
67
- });
68
- }
69
- return fetch(input, {
70
- ...init,
71
- headers
72
- });
73
- }
74
- };
75
- } else if (config.requestInit?.headers) {
76
- config.eventSourceInit = {
77
- fetch(input, init) {
78
- const headers = new Headers(init?.headers || {});
79
- const requestHeaders = new Headers(config.requestInit.headers);
80
- requestHeaders.forEach((value, key) => {
81
- headers.set(key, value);
82
- });
83
- return fetch(input, {
84
- ...init,
85
- headers
86
- });
87
- }
88
- };
89
- }
90
- } catch (error) {
91
- return {
92
- success: false,
93
- error: {
94
- message: `Invalid URL format: ${error}`,
95
- status: 400
96
- }
97
- };
98
- }
99
- }
100
- return {
101
- success: true,
102
- config
103
- };
104
- }
105
- function createMCPClient(config, id) {
106
- return new MCPClient({
107
- id,
108
- servers: {
109
- server: config
110
- }
111
- });
112
- }
113
- var validateMultipleServerConfigs = (serverConfigs) => {
114
- if (!serverConfigs || Object.keys(serverConfigs).length === 0) {
115
- return {
116
- success: false,
117
- error: {
118
- message: "At least one server configuration is required",
119
- status: 400
120
- }
121
- };
122
- }
123
- const validConfigs = {};
124
- const errors = {};
125
- let hasErrors = false;
126
- for (const [serverName, serverConfig] of Object.entries(serverConfigs)) {
127
- const validationResult = validateServerConfig(serverConfig);
128
- if (validationResult.success && validationResult.config) {
129
- validConfigs[serverName] = validationResult.config;
130
- } else {
131
- hasErrors = true;
132
- let errorMessage = "Configuration validation failed";
133
- if (validationResult.error) {
134
- errorMessage = validationResult.error.message;
135
- }
136
- errors[serverName] = errorMessage;
137
- }
138
- }
139
- if (!hasErrors) {
140
- return {
141
- success: true,
142
- validConfigs
143
- };
144
- }
145
- if (Object.keys(validConfigs).length > 0) {
146
- return {
147
- success: false,
148
- validConfigs,
149
- errors
150
- };
151
- }
152
- return {
153
- success: false,
154
- errors,
155
- error: {
156
- message: "All server configurations failed validation",
157
- status: 400
158
- }
159
- };
160
- };
161
- function createMCPClientWithMultipleConnections(serverConfigs) {
162
- const normalizedConfigs = {};
163
- for (const [serverName, config] of Object.entries(serverConfigs)) {
164
- const normalizedName = normalizeServerConfigName(serverName);
165
- normalizedConfigs[normalizedName] = config;
166
- }
167
- return new MCPClient({
168
- id: `chat-${Date.now()}`,
169
- servers: normalizedConfigs
170
- });
171
- }
172
- function normalizeServerConfigName(serverName) {
173
- return serverName.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
174
- }
175
-
176
- // routes/mcp/connect.ts
177
15
  var connect = new Hono();
178
16
  connect.post("/", async (c) => {
179
17
  try {
180
- const { serverConfig } = await c.req.json();
181
- const validation = validateServerConfig(serverConfig);
182
- if (!validation.success) {
183
- const error = validation.error;
18
+ const { serverConfig, serverId } = await c.req.json();
19
+ if (!serverConfig) {
184
20
  return c.json(
185
21
  {
186
22
  success: false,
187
- error: error.message
23
+ error: "serverConfig is required"
188
24
  },
189
- error.status
25
+ 400
190
26
  );
191
27
  }
192
- let client;
193
- try {
194
- client = createMCPClient(validation.config, `test-${Date.now()}`);
195
- } catch (error) {
28
+ if (!serverId) {
196
29
  return c.json(
197
30
  {
198
31
  success: false,
199
- error: `Failed to create a MCP client. Please double check your server configuration: ${JSON.stringify(serverConfig)}`,
200
- details: error instanceof Error ? error.message : "Unknown error"
32
+ error: "serverId is required"
201
33
  },
202
- 500
34
+ 400
203
35
  );
204
36
  }
37
+ const mcpClientManager = c.mcpJamClientManager;
205
38
  try {
206
- await client.getTools();
207
- await client.disconnect();
208
- return c.json({
209
- success: true
210
- });
39
+ await mcpClientManager.connectToServer(serverId, serverConfig);
40
+ const status = mcpClientManager.getConnectionStatus(serverId);
41
+ if (status === "connected") {
42
+ return c.json({
43
+ success: true,
44
+ status: "connected"
45
+ });
46
+ } else {
47
+ return c.json(
48
+ {
49
+ success: false,
50
+ error: "Connection failed",
51
+ status
52
+ },
53
+ 500
54
+ );
55
+ }
211
56
  } catch (error) {
212
57
  return c.json(
213
58
  {
@@ -231,16 +76,119 @@ connect.post("/", async (c) => {
231
76
  });
232
77
  var connect_default = connect;
233
78
 
234
- // routes/mcp/tools.ts
79
+ // routes/mcp/servers.ts
235
80
  import { Hono as Hono2 } from "hono";
81
+ var servers = new Hono2();
82
+ servers.get("/", async (c) => {
83
+ try {
84
+ const mcpJamClientManager2 = c.mcpJamClientManager;
85
+ const connectedServers = mcpJamClientManager2.getConnectedServers();
86
+ const serverList = Object.entries(connectedServers).map(
87
+ ([serverId, serverInfo]) => ({
88
+ id: serverId,
89
+ name: serverId,
90
+ status: serverInfo.status,
91
+ config: serverInfo.config
92
+ })
93
+ );
94
+ return c.json({
95
+ success: true,
96
+ servers: serverList
97
+ });
98
+ } catch (error) {
99
+ console.error("Error listing servers:", error);
100
+ return c.json(
101
+ {
102
+ success: false,
103
+ error: error instanceof Error ? error.message : "Unknown error"
104
+ },
105
+ 500
106
+ );
107
+ }
108
+ });
109
+ servers.get("/status/:serverId", async (c) => {
110
+ try {
111
+ const serverId = c.req.param("serverId");
112
+ const mcpJamClientManager2 = c.mcpJamClientManager;
113
+ const status = mcpJamClientManager2.getConnectionStatus(serverId);
114
+ return c.json({
115
+ success: true,
116
+ serverId,
117
+ status
118
+ });
119
+ } catch (error) {
120
+ console.error("Error getting server status:", error);
121
+ return c.json(
122
+ {
123
+ success: false,
124
+ error: error instanceof Error ? error.message : "Unknown error"
125
+ },
126
+ 500
127
+ );
128
+ }
129
+ });
130
+ servers.delete("/:serverId", async (c) => {
131
+ try {
132
+ const serverId = c.req.param("serverId");
133
+ const mcpJamClientManager2 = c.mcpJamClientManager;
134
+ await mcpJamClientManager2.disconnectFromServer(serverId);
135
+ return c.json({
136
+ success: true,
137
+ message: `Disconnected from server: ${serverId}`
138
+ });
139
+ } catch (error) {
140
+ console.error("Error disconnecting server:", error);
141
+ return c.json(
142
+ {
143
+ success: false,
144
+ error: error instanceof Error ? error.message : "Unknown error"
145
+ },
146
+ 500
147
+ );
148
+ }
149
+ });
150
+ servers.post("/reconnect", async (c) => {
151
+ try {
152
+ const { serverId, serverConfig } = await c.req.json();
153
+ if (!serverId || !serverConfig) {
154
+ return c.json(
155
+ {
156
+ success: false,
157
+ error: "serverId and serverConfig are required"
158
+ },
159
+ 400
160
+ );
161
+ }
162
+ const mcpJamClientManager2 = c.mcpJamClientManager;
163
+ await mcpJamClientManager2.disconnectFromServer(serverId);
164
+ await mcpJamClientManager2.connectToServer(serverId, serverConfig);
165
+ const status = mcpJamClientManager2.getConnectionStatus(serverId);
166
+ return c.json({
167
+ success: true,
168
+ serverId,
169
+ status,
170
+ message: `Reconnected to server: ${serverId}`
171
+ });
172
+ } catch (error) {
173
+ console.error("Error reconnecting server:", error);
174
+ return c.json(
175
+ {
176
+ success: false,
177
+ error: error instanceof Error ? error.message : "Unknown error"
178
+ },
179
+ 500
180
+ );
181
+ }
182
+ });
183
+ var servers_default = servers;
184
+
185
+ // routes/mcp/tools.ts
186
+ import { Hono as Hono3 } from "hono";
236
187
  import { zodToJsonSchema } from "zod-to-json-schema";
237
- import { TextEncoder } from "util";
238
- var tools = new Hono2();
188
+ import { TextEncoder as TextEncoder2 } from "util";
189
+ var tools = new Hono3();
239
190
  var pendingElicitations = /* @__PURE__ */ new Map();
240
191
  tools.post("/", async (c) => {
241
- let client = null;
242
- let encoder = null;
243
- let streamController = null;
244
192
  let action;
245
193
  let toolName;
246
194
  try {
@@ -267,8 +215,18 @@ tools.post("/", async (c) => {
267
215
  400
268
216
  );
269
217
  }
270
- const pending = pendingElicitations.get(requestId);
271
- if (!pending) {
218
+ const mcpJamClientManager2 = c.mcpJamClientManager;
219
+ const success = mcpJamClientManager2.respondToElicitation(
220
+ requestId,
221
+ response
222
+ );
223
+ if (!success) {
224
+ const pending = pendingElicitations.get(requestId);
225
+ if (pending) {
226
+ pending.resolve(response);
227
+ pendingElicitations.delete(requestId);
228
+ return c.json({ success: true });
229
+ }
272
230
  return c.json(
273
231
  {
274
232
  success: false,
@@ -277,177 +235,171 @@ tools.post("/", async (c) => {
277
235
  404
278
236
  );
279
237
  }
280
- pending.resolve(response);
281
- pendingElicitations.delete(requestId);
282
238
  return c.json({ success: true });
283
239
  }
284
- const validation = validateServerConfig(serverConfig);
285
- if (!validation.success) {
286
- return c.json(
287
- { success: false, error: validation.error.message },
288
- validation.error.status
289
- );
290
- }
291
- encoder = new TextEncoder();
240
+ const encoder = new TextEncoder2();
292
241
  const readableStream = new ReadableStream({
293
242
  async start(controller) {
294
- streamController = controller;
295
243
  try {
296
- const clientId = `tools-${action}-${Date.now()}`;
297
- client = createMCPClient(validation.config, clientId);
298
- if (action === "list") {
244
+ if (!serverConfig) {
299
245
  controller.enqueue(
300
246
  encoder.encode(
301
- `data: ${JSON.stringify({
302
- type: "tools_loading",
303
- message: "Fetching tools from server..."
304
- })}
247
+ `data: ${JSON.stringify({ type: "tool_error", error: "serverConfig is required" })}
305
248
 
306
249
  `
307
250
  )
308
251
  );
309
- const tools2 = await client.getTools();
310
- const toolsWithJsonSchema = Object.fromEntries(
311
- Object.entries(tools2).map(([toolName2, tool]) => {
312
- return [
313
- toolName2,
314
- {
315
- ...tool,
316
- inputSchema: zodToJsonSchema(
317
- tool.inputSchema
318
- )
319
- }
320
- ];
321
- })
322
- );
323
- controller.enqueue(
252
+ controller.enqueue(encoder.encode(`data: [DONE]
253
+
254
+ `));
255
+ controller.close();
256
+ return;
257
+ }
258
+ const mcpJamClientManager2 = c.mcpJamClientManager;
259
+ const serverId = serverConfig.name || serverConfig.id || "server";
260
+ await mcpJamClientManager2.connectToServer(serverId, serverConfig);
261
+ mcpJamClientManager2.setElicitationCallback(async (request) => {
262
+ const { requestId: requestId2, message, schema } = request;
263
+ controller.enqueue(
324
264
  encoder.encode(
325
265
  `data: ${JSON.stringify({
326
- type: "tools_list",
327
- tools: toolsWithJsonSchema
266
+ type: "elicitation_request",
267
+ requestId: requestId2,
268
+ message,
269
+ schema,
270
+ toolName: toolName || "unknown",
271
+ timestamp: /* @__PURE__ */ new Date()
328
272
  })}
329
273
 
330
274
  `
331
275
  )
332
276
  );
333
- } else if (action === "execute") {
334
- if (!toolName) {
277
+ return new Promise((resolve, reject) => {
278
+ pendingElicitations.set(requestId2, {
279
+ resolve: (response2) => {
280
+ resolve(response2);
281
+ pendingElicitations.delete(requestId2);
282
+ },
283
+ reject: (error) => {
284
+ reject(error);
285
+ pendingElicitations.delete(requestId2);
286
+ }
287
+ });
288
+ setTimeout(() => {
289
+ if (pendingElicitations.has(requestId2)) {
290
+ pendingElicitations.delete(requestId2);
291
+ reject(new Error("Elicitation timeout"));
292
+ }
293
+ }, 3e5);
294
+ });
295
+ });
296
+ if (action === "list") {
297
+ try {
298
+ const flattenedTools = await mcpJamClientManager2.getToolsetsForServer(serverId);
299
+ const toolsWithJsonSchema = {};
300
+ for (const [name, tool] of Object.entries(flattenedTools)) {
301
+ let inputSchema = tool.inputSchema;
302
+ try {
303
+ inputSchema = zodToJsonSchema(inputSchema);
304
+ } catch {
305
+ }
306
+ toolsWithJsonSchema[name] = {
307
+ name,
308
+ description: tool.description,
309
+ inputSchema,
310
+ outputSchema: tool.outputSchema
311
+ };
312
+ }
335
313
  controller.enqueue(
336
314
  encoder.encode(
337
- `data: ${JSON.stringify({
338
- type: "tool_error",
339
- error: "Tool name is required for execution"
340
- })}
315
+ `data: ${JSON.stringify({ type: "tools_list", tools: toolsWithJsonSchema })}
341
316
 
342
317
  `
343
318
  )
344
319
  );
320
+ controller.enqueue(encoder.encode(`data: [DONE]
321
+
322
+ `));
323
+ controller.close();
345
324
  return;
346
- }
347
- controller.enqueue(
348
- encoder.encode(
349
- `data: ${JSON.stringify({
350
- type: "tool_executing",
351
- toolName,
352
- parameters: parameters || {},
353
- message: "Executing tool..."
354
- })}
325
+ } catch (err) {
326
+ controller.enqueue(
327
+ encoder.encode(
328
+ `data: ${JSON.stringify({ type: "tool_error", error: err instanceof Error ? err.message : String(err) })}
355
329
 
356
330
  `
357
- )
358
- );
359
- const tools2 = await client.getTools();
360
- const tool = tools2[toolName];
361
- if (!tool) {
331
+ )
332
+ );
333
+ controller.enqueue(encoder.encode(`data: [DONE]
334
+
335
+ `));
336
+ controller.close();
337
+ return;
338
+ }
339
+ }
340
+ if (action === "execute") {
341
+ if (!toolName) {
362
342
  controller.enqueue(
363
343
  encoder.encode(
364
- `data: ${JSON.stringify({
365
- type: "tool_error",
366
- error: `Tool '${toolName}' not found`
367
- })}
344
+ `data: ${JSON.stringify({ type: "tool_error", error: "Tool name is required for execution" })}
368
345
 
369
346
  `
370
347
  )
371
348
  );
349
+ controller.enqueue(encoder.encode(`data: [DONE]
350
+
351
+ `));
352
+ controller.close();
372
353
  return;
373
354
  }
374
- const toolArgs = parameters && typeof parameters === "object" ? parameters : {};
375
- const elicitationHandler = async (elicitationRequest) => {
376
- const requestId2 = `elicit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
377
- if (streamController && encoder) {
378
- streamController.enqueue(
379
- encoder.encode(
380
- `data: ${JSON.stringify({
381
- type: "elicitation_request",
382
- requestId: requestId2,
383
- message: elicitationRequest.message,
384
- schema: elicitationRequest.requestedSchema,
385
- timestamp: /* @__PURE__ */ new Date()
386
- })}
355
+ controller.enqueue(
356
+ encoder.encode(
357
+ `data: ${JSON.stringify({ type: "tool_executing", toolName, parameters: parameters || {}, message: "Executing tool..." })}
387
358
 
388
359
  `
389
- )
390
- );
391
- }
392
- return new Promise((resolve, reject) => {
393
- pendingElicitations.set(requestId2, { resolve, reject });
394
- setTimeout(() => {
395
- if (pendingElicitations.has(requestId2)) {
396
- pendingElicitations.delete(requestId2);
397
- reject(new Error("Elicitation timeout"));
398
- }
399
- }, 3e5);
400
- });
401
- };
402
- if (client.elicitation && client.elicitation.onRequest) {
403
- const serverName = "server";
404
- client.elicitation.onRequest(serverName, elicitationHandler);
405
- }
406
- const result = await tool.execute({
407
- context: toolArgs
408
- });
360
+ )
361
+ );
362
+ const exec = await mcpJamClientManager2.executeToolDirect(
363
+ toolName,
364
+ parameters || {}
365
+ );
409
366
  controller.enqueue(
410
367
  encoder.encode(
411
- `data: ${JSON.stringify({
412
- type: "tool_result",
413
- toolName,
414
- result
415
- })}
368
+ `data: ${JSON.stringify({ type: "tool_result", toolName, result: exec.result })}
416
369
 
417
370
  `
418
371
  )
419
372
  );
420
373
  controller.enqueue(
421
374
  encoder.encode(
422
- `data: ${JSON.stringify({
423
- type: "elicitation_complete",
424
- toolName
425
- })}
375
+ `data: ${JSON.stringify({ type: "elicitation_complete", toolName })}
426
376
 
427
377
  `
428
378
  )
429
379
  );
430
- }
431
- controller.enqueue(encoder.encode(`data: [DONE]
380
+ controller.enqueue(encoder.encode(`data: [DONE]
432
381
 
433
382
  `));
434
- } catch (error) {
435
- const errorMsg = error instanceof Error ? error.message : "Unknown error";
383
+ controller.close();
384
+ return;
385
+ }
386
+ } catch (err) {
436
387
  controller.enqueue(
437
388
  encoder.encode(
438
- `data: ${JSON.stringify({
439
- type: "tool_error",
440
- error: errorMsg
441
- })}
389
+ `data: ${JSON.stringify({ type: "tool_error", error: err instanceof Error ? err.message : String(err) })}
442
390
 
443
391
  `
444
392
  )
445
393
  );
394
+ controller.enqueue(encoder.encode(`data: [DONE]
395
+
396
+ `));
397
+ controller.close();
446
398
  } finally {
447
- if (client) {
448
- await client.disconnect();
399
+ const mcpJamClientManager2 = c.mcpJamClientManager;
400
+ if (mcpJamClientManager2) {
401
+ mcpJamClientManager2.clearElicitationCallback();
449
402
  }
450
- controller.close();
451
403
  }
452
404
  }
453
405
  });
@@ -460,12 +412,6 @@ tools.post("/", async (c) => {
460
412
  });
461
413
  } catch (error) {
462
414
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
463
- if (client) {
464
- try {
465
- await client.disconnect();
466
- } catch (cleanupError) {
467
- }
468
- }
469
415
  return c.json(
470
416
  {
471
417
  success: false,
@@ -478,30 +424,17 @@ tools.post("/", async (c) => {
478
424
  var tools_default = tools;
479
425
 
480
426
  // routes/mcp/resources.ts
481
- import { Hono as Hono3 } from "hono";
482
- var resources = new Hono3();
427
+ import { Hono as Hono4 } from "hono";
428
+ var resources = new Hono4();
483
429
  resources.post("/list", async (c) => {
484
430
  try {
485
- const { serverConfig } = await c.req.json();
486
- const validation = validateServerConfig(serverConfig);
487
- if (!validation.success) {
488
- return c.json(
489
- { success: false, error: validation.error.message },
490
- validation.error.status
491
- );
492
- }
493
- const client = createMCPClient(
494
- validation.config,
495
- `resources-list-${Date.now()}`
496
- );
497
- try {
498
- const resources2 = await client.resources.list();
499
- await client.disconnect();
500
- return c.json({ resources: resources2 });
501
- } catch (error) {
502
- await client.disconnect();
503
- throw error;
431
+ const { serverId } = await c.req.json();
432
+ if (!serverId) {
433
+ return c.json({ success: false, error: "serverId is required" }, 400);
504
434
  }
435
+ const mcpClientManager = c.mcpJamClientManager;
436
+ const serverResources = mcpClientManager.getResourcesForServer(serverId);
437
+ return c.json({ resources: { [serverId]: serverResources } });
505
438
  } catch (error) {
506
439
  console.error("Error fetching resources:", error);
507
440
  return c.json(
@@ -515,13 +448,9 @@ resources.post("/list", async (c) => {
515
448
  });
516
449
  resources.post("/read", async (c) => {
517
450
  try {
518
- const { serverConfig, uri } = await c.req.json();
519
- const validation = validateServerConfig(serverConfig);
520
- if (!validation.success) {
521
- return c.json(
522
- { success: false, error: validation.error.message },
523
- validation.error.status
524
- );
451
+ const { serverId, uri } = await c.req.json();
452
+ if (!serverId) {
453
+ return c.json({ success: false, error: "serverId is required" }, 400);
525
454
  }
526
455
  if (!uri) {
527
456
  return c.json(
@@ -532,18 +461,9 @@ resources.post("/read", async (c) => {
532
461
  400
533
462
  );
534
463
  }
535
- const client = createMCPClient(
536
- validation.config,
537
- `resources-read-${Date.now()}`
538
- );
539
- try {
540
- const content = await client.resources.read("server", uri);
541
- await client.disconnect();
542
- return c.json({ content });
543
- } catch (error) {
544
- await client.disconnect();
545
- throw error;
546
- }
464
+ const mcpClientManager = c.mcpJamClientManager;
465
+ const content = await mcpClientManager.getResource(uri, serverId);
466
+ return c.json({ content });
547
467
  } catch (error) {
548
468
  console.error("Error reading resource:", error);
549
469
  return c.json(
@@ -558,30 +478,17 @@ resources.post("/read", async (c) => {
558
478
  var resources_default = resources;
559
479
 
560
480
  // routes/mcp/prompts.ts
561
- import { Hono as Hono4 } from "hono";
562
- var prompts = new Hono4();
481
+ import { Hono as Hono5 } from "hono";
482
+ var prompts = new Hono5();
563
483
  prompts.post("/list", async (c) => {
564
484
  try {
565
- const { serverConfig } = await c.req.json();
566
- const validation = validateServerConfig(serverConfig);
567
- if (!validation.success) {
568
- return c.json(
569
- { success: false, error: validation.error.message },
570
- validation.error.status
571
- );
572
- }
573
- const client = createMCPClient(
574
- validation.config,
575
- `prompts-list-${Date.now()}`
576
- );
577
- try {
578
- const prompts2 = await client.prompts.list();
579
- await client.disconnect();
580
- return c.json({ prompts: prompts2 });
581
- } catch (error) {
582
- await client.disconnect();
583
- throw error;
485
+ const { serverId } = await c.req.json();
486
+ if (!serverId) {
487
+ return c.json({ success: false, error: "serverId is required" }, 400);
584
488
  }
489
+ const mcpJamClientManager2 = c.mcpJamClientManager;
490
+ const serverPrompts = mcpJamClientManager2.getPromptsForServer(serverId);
491
+ return c.json({ prompts: { [serverId]: serverPrompts } });
585
492
  } catch (error) {
586
493
  console.error("Error fetching prompts:", error);
587
494
  return c.json(
@@ -595,13 +502,9 @@ prompts.post("/list", async (c) => {
595
502
  });
596
503
  prompts.post("/get", async (c) => {
597
504
  try {
598
- const { serverConfig, name, args } = await c.req.json();
599
- const validation = validateServerConfig(serverConfig);
600
- if (!validation.success) {
601
- return c.json(
602
- { success: false, error: validation.error.message },
603
- validation.error.status
604
- );
505
+ const { serverId, name, args } = await c.req.json();
506
+ if (!serverId) {
507
+ return c.json({ success: false, error: "serverId is required" }, 400);
605
508
  }
606
509
  if (!name) {
607
510
  return c.json(
@@ -612,22 +515,13 @@ prompts.post("/get", async (c) => {
612
515
  400
613
516
  );
614
517
  }
615
- const client = createMCPClient(
616
- validation.config,
617
- `prompts-get-${Date.now()}`
518
+ const mcpJamClientManager2 = c.mcpJamClientManager;
519
+ const content = await mcpJamClientManager2.getPrompt(
520
+ name,
521
+ serverId,
522
+ args || {}
618
523
  );
619
- try {
620
- const content = await client.prompts.get({
621
- serverName: "server",
622
- name,
623
- args: args || {}
624
- });
625
- await client.disconnect();
626
- return c.json({ content });
627
- } catch (error) {
628
- await client.disconnect();
629
- throw error;
630
- }
524
+ return c.json({ content });
631
525
  } catch (error) {
632
526
  console.error("Error getting prompt:", error);
633
527
  return c.json(
@@ -642,15 +536,196 @@ prompts.post("/get", async (c) => {
642
536
  var prompts_default = prompts;
643
537
 
644
538
  // routes/mcp/chat.ts
645
- import { Hono as Hono5 } from "hono";
539
+ import { Hono as Hono6 } from "hono";
540
+
541
+ // utils/mcp-utils.ts
542
+ import { MCPClient } from "@mastra/mcp";
543
+ function validateServerConfig(serverConfig) {
544
+ if (!serverConfig) {
545
+ return {
546
+ success: false,
547
+ error: {
548
+ message: "Server configuration is required",
549
+ status: 400
550
+ }
551
+ };
552
+ }
553
+ const config = { ...serverConfig };
554
+ if (config.url) {
555
+ try {
556
+ if (typeof config.url === "string") {
557
+ const parsed = new URL(config.url);
558
+ parsed.search = "";
559
+ parsed.hash = "";
560
+ config.url = parsed;
561
+ } else if (typeof config.url === "object" && !config.url.href) {
562
+ return {
563
+ success: false,
564
+ error: {
565
+ message: "Invalid URL configuration",
566
+ status: 400
567
+ }
568
+ };
569
+ }
570
+ if (config.oauth?.access_token) {
571
+ const authHeaders = {
572
+ Authorization: `Bearer ${config.oauth.access_token}`,
573
+ ...config.requestInit?.headers || {}
574
+ };
575
+ config.requestInit = {
576
+ ...config.requestInit,
577
+ headers: authHeaders
578
+ };
579
+ config.eventSourceInit = {
580
+ fetch(input, init) {
581
+ const headers = new Headers(init?.headers || {});
582
+ headers.set(
583
+ "Authorization",
584
+ `Bearer ${config.oauth.access_token}`
585
+ );
586
+ if (config.requestInit?.headers) {
587
+ const requestHeaders = new Headers(config.requestInit.headers);
588
+ requestHeaders.forEach((value, key) => {
589
+ if (key.toLowerCase() !== "authorization") {
590
+ headers.set(key, value);
591
+ }
592
+ });
593
+ }
594
+ return fetch(input, {
595
+ ...init,
596
+ headers
597
+ });
598
+ }
599
+ };
600
+ } else if (config.requestInit?.headers) {
601
+ config.eventSourceInit = {
602
+ fetch(input, init) {
603
+ const headers = new Headers(init?.headers || {});
604
+ const requestHeaders = new Headers(config.requestInit.headers);
605
+ requestHeaders.forEach((value, key) => {
606
+ headers.set(key, value);
607
+ });
608
+ return fetch(input, {
609
+ ...init,
610
+ headers
611
+ });
612
+ }
613
+ };
614
+ }
615
+ } catch (error) {
616
+ return {
617
+ success: false,
618
+ error: {
619
+ message: `Invalid URL format: ${error}`,
620
+ status: 400
621
+ }
622
+ };
623
+ }
624
+ }
625
+ return {
626
+ success: true,
627
+ config
628
+ };
629
+ }
630
+ var validateMultipleServerConfigs = (serverConfigs) => {
631
+ if (!serverConfigs || Object.keys(serverConfigs).length === 0) {
632
+ return {
633
+ success: false,
634
+ error: {
635
+ message: "At least one server configuration is required",
636
+ status: 400
637
+ }
638
+ };
639
+ }
640
+ const validConfigs = {};
641
+ const errors = {};
642
+ let hasErrors = false;
643
+ for (const [serverName, serverConfig] of Object.entries(serverConfigs)) {
644
+ const validationResult = validateServerConfig(serverConfig);
645
+ if (validationResult.success && validationResult.config) {
646
+ validConfigs[serverName] = validationResult.config;
647
+ } else {
648
+ hasErrors = true;
649
+ let errorMessage = "Configuration validation failed";
650
+ if (validationResult.error) {
651
+ errorMessage = validationResult.error.message;
652
+ }
653
+ errors[serverName] = errorMessage;
654
+ }
655
+ }
656
+ if (!hasErrors) {
657
+ return {
658
+ success: true,
659
+ validConfigs
660
+ };
661
+ }
662
+ if (Object.keys(validConfigs).length > 0) {
663
+ return {
664
+ success: false,
665
+ validConfigs,
666
+ errors
667
+ };
668
+ }
669
+ return {
670
+ success: false,
671
+ errors,
672
+ error: {
673
+ message: "All server configurations failed validation",
674
+ status: 400
675
+ }
676
+ };
677
+ };
678
+ function createMCPClientWithMultipleConnections(serverConfigs) {
679
+ const originalMCPClient = new MCPClient({
680
+ id: `chat-${Date.now()}`,
681
+ servers: serverConfigs
682
+ });
683
+ const originalGetTools = originalMCPClient.getTools.bind(originalMCPClient);
684
+ originalMCPClient.getTools = async () => {
685
+ const tools2 = await originalGetTools();
686
+ const fixedTools = {};
687
+ for (const [toolName, toolConfig] of Object.entries(tools2)) {
688
+ const parts = toolName.split("_");
689
+ if (parts.length >= 3 && parts[0] === parts[1]) {
690
+ const fixedName = parts.slice(1).join("_");
691
+ fixedTools[fixedName] = toolConfig;
692
+ } else {
693
+ fixedTools[toolName] = toolConfig;
694
+ }
695
+ }
696
+ return fixedTools;
697
+ };
698
+ return originalMCPClient;
699
+ }
700
+ function normalizeServerConfigName(serverName) {
701
+ return serverName.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
702
+ }
703
+
704
+ // routes/mcp/chat.ts
646
705
  import { Agent } from "@mastra/core/agent";
647
706
  import { createAnthropic } from "@ai-sdk/anthropic";
648
707
  import { createOpenAI } from "@ai-sdk/openai";
649
708
  import { createOllama } from "ollama-ai-provider";
650
- import { MCPClient as MCPClient2 } from "@mastra/mcp";
651
- import { TextEncoder as TextEncoder2 } from "util";
652
- var chat = new Hono5();
709
+ import { TextEncoder as TextEncoder3 } from "util";
710
+
711
+ // ../client/src/lib/chat-utils.ts
712
+ import { clsx } from "clsx";
713
+ import { twMerge } from "tailwind-merge";
714
+ function getDefaultTemperatureByProvider(provider) {
715
+ switch (provider) {
716
+ case "openai":
717
+ return 1;
718
+ case "anthropic":
719
+ return 0;
720
+ default:
721
+ return 0;
722
+ }
723
+ }
724
+
725
+ // routes/mcp/chat.ts
653
726
  var DEBUG_ENABLED = process.env.MCP_DEBUG !== "false";
727
+ var ELICITATION_TIMEOUT = 3e5;
728
+ var MAX_AGENT_STEPS = 10;
654
729
  var dbg = (...args) => {
655
730
  if (DEBUG_ENABLED) console.log("[mcp/chat]", ...args);
656
731
  };
@@ -659,6 +734,310 @@ try {
659
734
  } catch {
660
735
  }
661
736
  var pendingElicitations2 = /* @__PURE__ */ new Map();
737
+ var chat = new Hono6();
738
+ var createLlmModel = (modelDefinition, apiKey, ollamaBaseUrl) => {
739
+ if (!modelDefinition?.id || !modelDefinition?.provider) {
740
+ throw new Error(
741
+ `Invalid model definition: ${JSON.stringify(modelDefinition)}`
742
+ );
743
+ }
744
+ switch (modelDefinition.provider) {
745
+ case "anthropic":
746
+ return createAnthropic({ apiKey })(modelDefinition.id);
747
+ case "openai":
748
+ return createOpenAI({ apiKey })(modelDefinition.id);
749
+ case "deepseek":
750
+ return createOpenAI({ apiKey, baseURL: "https://api.deepseek.com/v1" })(
751
+ modelDefinition.id
752
+ );
753
+ case "ollama":
754
+ const baseUrl = ollamaBaseUrl || "http://localhost:11434";
755
+ return createOllama({
756
+ baseURL: `${baseUrl}`
757
+ })(modelDefinition.id, {
758
+ simulateStreaming: true
759
+ });
760
+ default:
761
+ throw new Error(
762
+ `Unsupported provider: ${modelDefinition.provider} for model: ${modelDefinition.id}`
763
+ );
764
+ }
765
+ };
766
+ var createElicitationHandler = (streamingContext) => {
767
+ return async (elicitationRequest) => {
768
+ const requestId = `elicit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
769
+ if (streamingContext.controller && streamingContext.encoder) {
770
+ streamingContext.controller.enqueue(
771
+ streamingContext.encoder.encode(
772
+ `data: ${JSON.stringify({
773
+ type: "elicitation_request",
774
+ requestId,
775
+ message: elicitationRequest.message,
776
+ schema: elicitationRequest.requestedSchema,
777
+ timestamp: /* @__PURE__ */ new Date()
778
+ })}
779
+
780
+ `
781
+ )
782
+ );
783
+ }
784
+ return new Promise((resolve, reject) => {
785
+ pendingElicitations2.set(requestId, { resolve, reject });
786
+ setTimeout(() => {
787
+ if (pendingElicitations2.has(requestId)) {
788
+ pendingElicitations2.delete(requestId);
789
+ reject(new Error("Elicitation timeout"));
790
+ }
791
+ }, ELICITATION_TIMEOUT);
792
+ });
793
+ };
794
+ };
795
+ var wrapToolsWithStreaming = (tools2, streamingContext) => {
796
+ const wrappedTools = {};
797
+ for (const [name, tool] of Object.entries(tools2)) {
798
+ wrappedTools[name] = {
799
+ ...tool,
800
+ execute: async (params) => {
801
+ const currentToolCallId = ++streamingContext.toolCallId;
802
+ const startedAt = Date.now();
803
+ if (streamingContext.controller && streamingContext.encoder) {
804
+ streamingContext.controller.enqueue(
805
+ streamingContext.encoder.encode(
806
+ `data: ${JSON.stringify({
807
+ type: "tool_call",
808
+ toolCall: {
809
+ id: currentToolCallId,
810
+ name,
811
+ parameters: params,
812
+ timestamp: /* @__PURE__ */ new Date(),
813
+ status: "executing"
814
+ }
815
+ })}
816
+
817
+ `
818
+ )
819
+ );
820
+ }
821
+ dbg("Tool executing", { name, currentToolCallId, params });
822
+ try {
823
+ const result = await tool.execute(params);
824
+ dbg("Tool result", {
825
+ name,
826
+ currentToolCallId,
827
+ ms: Date.now() - startedAt
828
+ });
829
+ if (streamingContext.controller && streamingContext.encoder) {
830
+ streamingContext.controller.enqueue(
831
+ streamingContext.encoder.encode(
832
+ `data: ${JSON.stringify({
833
+ type: "tool_result",
834
+ toolResult: {
835
+ id: currentToolCallId,
836
+ toolCallId: currentToolCallId,
837
+ result,
838
+ timestamp: /* @__PURE__ */ new Date()
839
+ }
840
+ })}
841
+
842
+ `
843
+ )
844
+ );
845
+ }
846
+ return result;
847
+ } catch (error) {
848
+ dbg("Tool error", {
849
+ name,
850
+ currentToolCallId,
851
+ error: error instanceof Error ? error.message : String(error)
852
+ });
853
+ if (streamingContext.controller && streamingContext.encoder) {
854
+ streamingContext.controller.enqueue(
855
+ streamingContext.encoder.encode(
856
+ `data: ${JSON.stringify({
857
+ type: "tool_result",
858
+ toolResult: {
859
+ id: currentToolCallId,
860
+ toolCallId: currentToolCallId,
861
+ error: error instanceof Error ? error.message : String(error),
862
+ timestamp: /* @__PURE__ */ new Date()
863
+ }
864
+ })}
865
+
866
+ `
867
+ )
868
+ );
869
+ }
870
+ throw error;
871
+ }
872
+ }
873
+ };
874
+ }
875
+ return wrappedTools;
876
+ };
877
+ var handleAgentStepFinish = (streamingContext, text, toolCalls, toolResults) => {
878
+ try {
879
+ if (toolCalls && Array.isArray(toolCalls)) {
880
+ for (const call of toolCalls) {
881
+ const currentToolCallId = ++streamingContext.toolCallId;
882
+ streamingContext.lastEmittedToolCallId = currentToolCallId;
883
+ if (streamingContext.controller && streamingContext.encoder) {
884
+ streamingContext.controller.enqueue(
885
+ streamingContext.encoder.encode(
886
+ `data: ${JSON.stringify({
887
+ type: "tool_call",
888
+ toolCall: {
889
+ id: currentToolCallId,
890
+ name: call.name || call.toolName,
891
+ parameters: call.params || call.args || {},
892
+ timestamp: /* @__PURE__ */ new Date(),
893
+ status: "executing"
894
+ }
895
+ })}
896
+
897
+ `
898
+ )
899
+ );
900
+ }
901
+ }
902
+ }
903
+ if (toolResults && Array.isArray(toolResults)) {
904
+ for (const result of toolResults) {
905
+ const currentToolCallId = streamingContext.lastEmittedToolCallId != null ? streamingContext.lastEmittedToolCallId : ++streamingContext.toolCallId;
906
+ if (streamingContext.controller && streamingContext.encoder) {
907
+ streamingContext.controller.enqueue(
908
+ streamingContext.encoder.encode(
909
+ `data: ${JSON.stringify({
910
+ type: "tool_result",
911
+ toolResult: {
912
+ id: currentToolCallId,
913
+ toolCallId: currentToolCallId,
914
+ result: result.result,
915
+ error: result.error,
916
+ timestamp: /* @__PURE__ */ new Date()
917
+ }
918
+ })}
919
+
920
+ `
921
+ )
922
+ );
923
+ }
924
+ }
925
+ }
926
+ streamingContext.stepIndex = (streamingContext.stepIndex || 0) + 1;
927
+ if (streamingContext.controller && streamingContext.encoder) {
928
+ streamingContext.controller.enqueue(
929
+ streamingContext.encoder.encode(
930
+ `data: ${JSON.stringify({
931
+ type: "trace_step",
932
+ step: streamingContext.stepIndex,
933
+ text,
934
+ toolCalls: (toolCalls || []).map((c) => ({
935
+ name: c.name || c.toolName,
936
+ params: c.params || c.args || {}
937
+ })),
938
+ toolResults: (toolResults || []).map((r) => ({
939
+ result: r.result,
940
+ error: r.error
941
+ })),
942
+ timestamp: /* @__PURE__ */ new Date()
943
+ })}
944
+
945
+ `
946
+ )
947
+ );
948
+ }
949
+ } catch (err) {
950
+ dbg("onStepFinish error", err);
951
+ }
952
+ };
953
+ var streamAgentResponse = async (streamingContext, stream) => {
954
+ let hasContent = false;
955
+ let chunkCount = 0;
956
+ for await (const chunk of stream.textStream) {
957
+ if (chunk && chunk.trim()) {
958
+ hasContent = true;
959
+ chunkCount++;
960
+ streamingContext.controller.enqueue(
961
+ streamingContext.encoder.encode(
962
+ `data: ${JSON.stringify({ type: "text", content: chunk })}
963
+
964
+ `
965
+ )
966
+ );
967
+ }
968
+ }
969
+ dbg("Streaming finished", { hasContent, chunkCount });
970
+ return { hasContent, chunkCount };
971
+ };
972
+ var fallbackToCompletion = async (agent, messages, streamingContext, provider) => {
973
+ try {
974
+ const result = await agent.generate(messages, {
975
+ temperature: getDefaultTemperatureByProvider(provider)
976
+ });
977
+ if (result.text && result.text.trim()) {
978
+ streamingContext.controller.enqueue(
979
+ streamingContext.encoder.encode(
980
+ `data: ${JSON.stringify({
981
+ type: "text",
982
+ content: result.text
983
+ })}
984
+
985
+ `
986
+ )
987
+ );
988
+ }
989
+ } catch (fallbackErr) {
990
+ streamingContext.controller.enqueue(
991
+ streamingContext.encoder.encode(
992
+ `data: ${JSON.stringify({
993
+ type: "text",
994
+ content: "Failed to generate response. Please try again. ",
995
+ error: fallbackErr instanceof Error ? fallbackErr.message : "Unknown error"
996
+ })}
997
+
998
+ `
999
+ )
1000
+ );
1001
+ }
1002
+ };
1003
+ var safeDisconnect = async (client) => {
1004
+ if (client) {
1005
+ try {
1006
+ await client.disconnect();
1007
+ } catch (cleanupError) {
1008
+ console.warn("[mcp/chat] Error cleaning up MCP client:", cleanupError);
1009
+ }
1010
+ }
1011
+ };
1012
+ var createStreamingResponse = async (agent, messages, toolsets, streamingContext, provider) => {
1013
+ const stream = await agent.stream(messages, {
1014
+ maxSteps: MAX_AGENT_STEPS,
1015
+ temperature: getDefaultTemperatureByProvider(provider),
1016
+ toolsets,
1017
+ onStepFinish: ({ text, toolCalls, toolResults }) => {
1018
+ handleAgentStepFinish(streamingContext, text, toolCalls, toolResults);
1019
+ }
1020
+ });
1021
+ const { hasContent } = await streamAgentResponse(streamingContext, stream);
1022
+ if (!hasContent) {
1023
+ dbg("No content from textStream; falling back to completion");
1024
+ await fallbackToCompletion(agent, messages, streamingContext, provider);
1025
+ }
1026
+ streamingContext.controller.enqueue(
1027
+ streamingContext.encoder.encode(
1028
+ `data: ${JSON.stringify({
1029
+ type: "elicitation_complete"
1030
+ })}
1031
+
1032
+ `
1033
+ )
1034
+ );
1035
+ streamingContext.controller.enqueue(
1036
+ streamingContext.encoder.encode(`data: [DONE]
1037
+
1038
+ `)
1039
+ );
1040
+ };
662
1041
  chat.post("/", async (c) => {
663
1042
  let client = null;
664
1043
  try {
@@ -666,6 +1045,7 @@ chat.post("/", async (c) => {
666
1045
  const {
667
1046
  serverConfigs,
668
1047
  model,
1048
+ provider,
669
1049
  apiKey,
670
1050
  systemPrompt,
671
1051
  messages,
@@ -698,7 +1078,7 @@ chat.post("/", async (c) => {
698
1078
  pendingElicitations2.delete(requestId);
699
1079
  return c.json({ success: true });
700
1080
  }
701
- if (!model || !model.id || !apiKey || !messages) {
1081
+ if (!model?.id || !apiKey || !messages) {
702
1082
  return c.json(
703
1083
  {
704
1084
  success: false,
@@ -707,345 +1087,357 @@ chat.post("/", async (c) => {
707
1087
  400
708
1088
  );
709
1089
  }
710
- dbg("Incoming chat request", {
711
- provider: model?.provider,
712
- modelId: model?.id,
713
- messageCount: messages?.length,
714
- serverCount: serverConfigs ? Object.keys(serverConfigs).length : 0
715
- });
716
- if (serverConfigs && Object.keys(serverConfigs).length > 0) {
717
- const validation = validateMultipleServerConfigs(serverConfigs);
718
- if (!validation.success) {
719
- dbg("Server config validation failed", validation.errors || validation.error);
720
- return c.json(
721
- {
722
- success: false,
723
- error: validation.error.message,
724
- details: validation.errors
725
- },
726
- validation.error.status
727
- );
728
- }
729
- client = createMCPClientWithMultipleConnections(validation.validConfigs);
730
- dbg("Created MCP client with servers", Object.keys(validation.validConfigs));
731
- } else {
732
- client = new MCPClient2({
733
- id: `chat-${Date.now()}`,
734
- servers: {}
735
- });
736
- dbg("Created MCP client without servers");
737
- }
738
- const llmModel = getLlmModel(model, apiKey, ollamaBaseUrl);
739
- dbg("LLM model initialized", { provider: model.provider, id: model.id });
740
- let toolCallId = 0;
741
- let streamController = null;
742
- let encoder = null;
743
- let lastEmittedToolCallId = null;
744
- const elicitationHandler = async (elicitationRequest) => {
745
- const requestId2 = `elicit_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
746
- if (streamController && encoder) {
747
- streamController.enqueue(
748
- encoder.encode(
749
- `data: ${JSON.stringify({
750
- type: "elicitation_request",
751
- requestId: requestId2,
752
- message: elicitationRequest.message,
753
- schema: elicitationRequest.requestedSchema,
754
- timestamp: /* @__PURE__ */ new Date()
755
- })}
756
-
757
- `
758
- )
759
- );
760
- }
761
- dbg("Elicitation requested", { requestId: requestId2 });
762
- return new Promise((resolve, reject) => {
763
- pendingElicitations2.set(requestId2, { resolve, reject });
764
- setTimeout(() => {
765
- if (pendingElicitations2.has(requestId2)) {
766
- pendingElicitations2.delete(requestId2);
767
- reject(new Error("Elicitation timeout"));
768
- }
769
- }, 3e5);
770
- });
771
- };
772
- if (client.elicitation && client.elicitation.onRequest && serverConfigs) {
773
- for (const serverName of Object.keys(serverConfigs)) {
774
- const normalizedName = serverName.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
775
- client.elicitation.onRequest(normalizedName, elicitationHandler);
776
- dbg("Registered elicitation handler", { serverName, normalizedName });
777
- }
1090
+ if (!serverConfigs || Object.keys(serverConfigs).length === 0) {
1091
+ return c.json(
1092
+ {
1093
+ success: false,
1094
+ error: "No server configs provided"
1095
+ },
1096
+ 400
1097
+ );
778
1098
  }
779
- const tools2 = await client.getTools();
780
- const originalTools = tools2 && Object.keys(tools2).length > 0 ? tools2 : {};
781
- const wrappedTools = {};
782
- for (const [name, tool] of Object.entries(originalTools)) {
783
- wrappedTools[name] = {
784
- ...tool,
785
- execute: async (params) => {
786
- const currentToolCallId = ++toolCallId;
787
- const startedAt = Date.now();
788
- if (streamController && encoder) {
789
- streamController.enqueue(
790
- encoder.encode(
791
- `data: ${JSON.stringify({
792
- type: "tool_call",
793
- toolCall: {
794
- id: currentToolCallId,
795
- name,
796
- parameters: params,
797
- timestamp: /* @__PURE__ */ new Date(),
798
- status: "executing"
799
- }
800
- })}
801
-
802
- `
803
- )
804
- );
805
- }
806
- dbg("Tool executing", { name, currentToolCallId, params });
807
- try {
808
- const result = await tool.execute(params);
809
- dbg("Tool result", { name, currentToolCallId, ms: Date.now() - startedAt });
810
- if (streamController && encoder) {
811
- streamController.enqueue(
812
- encoder.encode(
813
- `data: ${JSON.stringify({
814
- type: "tool_result",
815
- toolResult: {
816
- id: currentToolCallId,
817
- toolCallId: currentToolCallId,
818
- result,
819
- timestamp: /* @__PURE__ */ new Date()
820
- }
821
- })}
822
-
823
- `
824
- )
825
- );
826
- }
827
- return result;
828
- } catch (error) {
829
- dbg("Tool error", { name, currentToolCallId, error: error instanceof Error ? error.message : String(error) });
830
- if (streamController && encoder) {
831
- streamController.enqueue(
832
- encoder.encode(
833
- `data: ${JSON.stringify({
834
- type: "tool_result",
835
- toolResult: {
836
- id: currentToolCallId,
837
- toolCallId: currentToolCallId,
838
- error: error instanceof Error ? error.message : String(error),
839
- timestamp: /* @__PURE__ */ new Date()
840
- }
841
- })}
842
-
843
- `
844
- )
845
- );
846
- }
847
- throw error;
848
- }
849
- }
850
- };
1099
+ const validation = validateMultipleServerConfigs(serverConfigs);
1100
+ if (!validation.success) {
1101
+ dbg(
1102
+ "Server config validation failed",
1103
+ validation.errors || validation.error
1104
+ );
1105
+ return c.json(
1106
+ {
1107
+ success: false,
1108
+ error: validation.error.message,
1109
+ details: validation.errors
1110
+ },
1111
+ validation.error.status
1112
+ );
851
1113
  }
1114
+ client = createMCPClientWithMultipleConnections(validation.validConfigs);
1115
+ const llmModel = createLlmModel(model, apiKey, ollamaBaseUrl);
852
1116
  const agent = new Agent({
853
1117
  name: "MCP Chat Agent",
854
1118
  instructions: systemPrompt || "You are a helpful assistant with access to MCP tools.",
855
1119
  model: llmModel,
856
- tools: Object.keys(wrappedTools).length > 0 ? wrappedTools : void 0
1120
+ tools: void 0
1121
+ // Start without tools, add them in streaming context
857
1122
  });
858
1123
  const formattedMessages = messages.map((msg) => ({
859
1124
  role: msg.role,
860
1125
  content: msg.content
861
1126
  }));
862
- const toolsets = serverConfigs ? await client.getToolsets() : void 0;
1127
+ const toolsets = await client.getToolsets();
863
1128
  dbg("Streaming start", {
864
- toolsetServers: toolsets ? Object.keys(toolsets) : [],
1129
+ toolsetServers: Object.keys(toolsets),
865
1130
  messageCount: formattedMessages.length
866
1131
  });
867
- let streamedAnyText = false;
868
- const stream = await agent.stream(formattedMessages, {
869
- maxSteps: 10,
870
- // Allow up to 10 steps for tool usage
871
- toolsets,
872
- onStepFinish: ({ text, toolCalls, toolResults }) => {
1132
+ const encoder = new TextEncoder3();
1133
+ const readableStream = new ReadableStream({
1134
+ async start(controller) {
1135
+ const streamingContext = {
1136
+ controller,
1137
+ encoder,
1138
+ toolCallId: 0,
1139
+ lastEmittedToolCallId: null,
1140
+ stepIndex: 0
1141
+ };
1142
+ const flattenedTools = {};
1143
+ Object.values(toolsets).forEach((serverTools) => {
1144
+ Object.assign(flattenedTools, serverTools);
1145
+ });
1146
+ const streamingWrappedTools = wrapToolsWithStreaming(
1147
+ flattenedTools,
1148
+ streamingContext
1149
+ );
1150
+ const streamingAgent = new Agent({
1151
+ name: agent.name,
1152
+ instructions: agent.instructions,
1153
+ model: agent.model,
1154
+ tools: Object.keys(streamingWrappedTools).length > 0 ? streamingWrappedTools : void 0
1155
+ });
1156
+ if (client?.elicitation?.onRequest) {
1157
+ for (const serverName of Object.keys(serverConfigs)) {
1158
+ const normalizedName = normalizeServerConfigName(serverName);
1159
+ const elicitationHandler = createElicitationHandler(streamingContext);
1160
+ client.elicitation.onRequest(normalizedName, elicitationHandler);
1161
+ }
1162
+ }
873
1163
  try {
874
- if (text && streamController && encoder) {
875
- streamedAnyText = true;
876
- streamController.enqueue(
877
- encoder.encode(
878
- `data: ${JSON.stringify({ type: "text", content: text })}
879
-
880
- `
881
- )
1164
+ if (client) {
1165
+ await createStreamingResponse(
1166
+ streamingAgent,
1167
+ formattedMessages,
1168
+ toolsets,
1169
+ streamingContext,
1170
+ provider
882
1171
  );
1172
+ } else {
1173
+ throw new Error("MCP client is null");
883
1174
  }
884
- const tcList = toolCalls;
885
- if (tcList && Array.isArray(tcList)) {
886
- for (const call of tcList) {
887
- const currentToolCallId = ++toolCallId;
888
- lastEmittedToolCallId = currentToolCallId;
889
- if (streamController && encoder) {
890
- streamController.enqueue(
891
- encoder.encode(
892
- `data: ${JSON.stringify({
893
- type: "tool_call",
894
- toolCall: {
895
- id: currentToolCallId,
896
- name: call.name || call.toolName,
897
- parameters: call.params || call.args || {},
898
- timestamp: /* @__PURE__ */ new Date(),
899
- status: "executing"
900
- }
901
- })}
902
-
903
- `
904
- )
905
- );
906
- }
907
- }
908
- }
909
- const trList = toolResults;
910
- if (trList && Array.isArray(trList)) {
911
- for (const result of trList) {
912
- const currentToolCallId = lastEmittedToolCallId != null ? lastEmittedToolCallId : ++toolCallId;
913
- if (streamController && encoder) {
914
- streamController.enqueue(
915
- encoder.encode(
916
- `data: ${JSON.stringify({
917
- type: "tool_result",
918
- toolResult: {
919
- id: currentToolCallId,
920
- toolCallId: currentToolCallId,
921
- result: result.result,
922
- error: result.error,
923
- timestamp: /* @__PURE__ */ new Date()
924
- }
925
- })}
1175
+ } catch (error) {
1176
+ controller.enqueue(
1177
+ encoder.encode(
1178
+ `data: ${JSON.stringify({
1179
+ type: "error",
1180
+ error: error instanceof Error ? error.message : "Unknown error"
1181
+ })}
926
1182
 
927
1183
  `
928
- )
929
- );
930
- }
931
- }
932
- }
933
- } catch (err) {
934
- dbg("onStepFinish error", err);
1184
+ )
1185
+ );
1186
+ } finally {
1187
+ await safeDisconnect(client);
1188
+ controller.close();
935
1189
  }
1190
+ }
1191
+ });
1192
+ return new Response(readableStream, {
1193
+ headers: {
1194
+ "Content-Type": "text/event-stream",
1195
+ "Cache-Control": "no-cache",
1196
+ Connection: "keep-alive"
1197
+ }
1198
+ });
1199
+ } catch (error) {
1200
+ console.error("[mcp/chat] Error in chat API:", error);
1201
+ await safeDisconnect(client);
1202
+ return c.json(
1203
+ {
1204
+ success: false,
1205
+ error: error instanceof Error ? error.message : "Unknown error"
936
1206
  },
937
- onFinish: ({ text, finishReason }) => {
938
- dbg("onFinish called", { finishReason, hasText: Boolean(text) });
939
- try {
940
- if (text && streamController && encoder) {
941
- streamedAnyText = true;
942
- streamController.enqueue(
943
- encoder.encode(
944
- `data: ${JSON.stringify({ type: "text", content: text })}
1207
+ 500
1208
+ );
1209
+ }
1210
+ });
1211
+ var chat_default = chat;
945
1212
 
946
- `
947
- )
948
- );
949
- }
950
- } catch (err) {
951
- dbg("onFinish enqueue error", err);
952
- }
1213
+ // routes/mcp/tests.ts
1214
+ import { Hono as Hono7 } from "hono";
1215
+ import { writeFile, mkdir } from "fs/promises";
1216
+ import { join, dirname } from "path";
1217
+ import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
1218
+ import { createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
1219
+ import { createOllama as createOllama2 } from "ollama-ai-provider";
1220
+ import { Agent as Agent2 } from "@mastra/core/agent";
1221
+ var tests = new Hono7();
1222
+ tests.post("/generate", async (c) => {
1223
+ try {
1224
+ const body = await c.req.json();
1225
+ const test = body?.test;
1226
+ const servers2 = body?.servers;
1227
+ const model = body?.model;
1228
+ if (!test?.id || !test?.prompt || !servers2 || Object.keys(servers2).length === 0) {
1229
+ return c.json(
1230
+ { success: false, error: "Missing test, servers, or prompt" },
1231
+ 400
1232
+ );
1233
+ }
1234
+ const safeName = String(test.title || test.id).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
1235
+ const filename = `@TestAgent_${safeName || test.id}.ts`;
1236
+ const fileContents = `import { Agent } from "@mastra/core/agent";
1237
+ import { MCPClient } from "@mastra/mcp";
1238
+ import { createAnthropic } from "@ai-sdk/anthropic";
1239
+ import { createOpenAI } from "@ai-sdk/openai";
1240
+ import { createOllama } from "ollama-ai-provider";
1241
+
1242
+ const servers = ${JSON.stringify(servers2, null, 2)} as const;
1243
+
1244
+ function createModel() {
1245
+ const def = ${JSON.stringify(model || null)} as any;
1246
+ if (!def) throw new Error("Model not provided by UI when generating test agent");
1247
+ switch (def.provider) {
1248
+ case "anthropic": return createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! })(def.id);
1249
+ case "openai": return createOpenAI({ apiKey: process.env.OPENAI_API_KEY! })(def.id);
1250
+ case "deepseek": return createOpenAI({ apiKey: process.env.DEEPSEEK_API_KEY!, baseURL: "https://api.deepseek.com/v1" })(def.id);
1251
+ case "ollama": return createOllama({ baseURL: process.env.OLLAMA_BASE_URL || "http://localhost:11434" })(def.id, { simulateStreaming: true });
1252
+ default: throw new Error("Unsupported provider: " + def.provider);
1253
+ }
1254
+ }
1255
+
1256
+ export const createTestAgent = async () => {
1257
+ const mcp = new MCPClient({ servers });
1258
+ const toolsets = await mcp.getToolsets();
1259
+ return new Agent({
1260
+ name: ${JSON.stringify(test.title || "Test Agent")},
1261
+ instructions: ${JSON.stringify(test.prompt)},
1262
+ model: createModel(),
1263
+ tools: undefined,
1264
+ defaultGenerateOptions: { toolChoice: "auto" }
1265
+ });
1266
+ };
1267
+ `;
1268
+ const targetPath = join(process.cwd(), "server", "agents", filename);
1269
+ await mkdir(dirname(targetPath), { recursive: true });
1270
+ await writeFile(targetPath, fileContents, "utf8");
1271
+ return c.json({ success: true, file: `server/agents/${filename}` });
1272
+ } catch (err) {
1273
+ const msg = err instanceof Error ? err.message : "Unknown error";
1274
+ return c.json({ success: false, error: msg }, 500);
1275
+ }
1276
+ });
1277
+ var tests_default = tests;
1278
+ tests.post("/run-all", async (c) => {
1279
+ const encoder = new TextEncoder();
1280
+ try {
1281
+ let createModel2 = function(model) {
1282
+ switch (model.provider) {
1283
+ case "anthropic":
1284
+ return createAnthropic2({
1285
+ apiKey: providerApiKeys?.anthropic || process.env.ANTHROPIC_API_KEY || ""
1286
+ })(model.id);
1287
+ case "openai":
1288
+ return createOpenAI2({
1289
+ apiKey: providerApiKeys?.openai || process.env.OPENAI_API_KEY || ""
1290
+ })(model.id);
1291
+ case "deepseek":
1292
+ return createOpenAI2({
1293
+ apiKey: providerApiKeys?.deepseek || process.env.DEEPSEEK_API_KEY || "",
1294
+ baseURL: "https://api.deepseek.com/v1"
1295
+ })(model.id);
1296
+ case "ollama":
1297
+ return createOllama2({
1298
+ baseURL: ollamaBaseUrl || process.env.OLLAMA_BASE_URL || "http://localhost:11434"
1299
+ })(model.id, { simulateStreaming: true });
1300
+ default:
1301
+ throw new Error(`Unsupported provider: ${model.provider}`);
953
1302
  }
954
- });
955
- encoder = new TextEncoder2();
1303
+ };
1304
+ var createModel = createModel2;
1305
+ const body = await c.req.json();
1306
+ const testsInput = body?.tests || [];
1307
+ const allServers = body?.allServers || {};
1308
+ const providerApiKeys = body?.providerApiKeys || {};
1309
+ const ollamaBaseUrl = body?.ollamaBaseUrl;
1310
+ const maxConcurrency = Math.max(
1311
+ 1,
1312
+ Math.min(8, body?.concurrency ?? 5)
1313
+ );
1314
+ if (!Array.isArray(testsInput) || testsInput.length === 0) {
1315
+ return c.json({ success: false, error: "No tests provided" }, 400);
1316
+ }
956
1317
  const readableStream = new ReadableStream({
957
1318
  async start(controller) {
958
- streamController = controller;
959
- try {
960
- let hasContent = false;
961
- let chunkCount = 0;
962
- for await (const chunk of stream.textStream) {
963
- if (chunk && chunk.trim()) {
964
- hasContent = true;
965
- chunkCount++;
1319
+ let active = 0;
1320
+ let index = 0;
1321
+ let failed = false;
1322
+ const runNext = async () => {
1323
+ if (index >= testsInput.length) {
1324
+ if (active === 0) {
966
1325
  controller.enqueue(
967
1326
  encoder.encode(
968
- `data: ${JSON.stringify({ type: "text", content: chunk })}
1327
+ `data: ${JSON.stringify({ type: "run_complete", passed: !failed })}
969
1328
 
970
1329
  `
971
1330
  )
972
1331
  );
1332
+ controller.enqueue(encoder.encode(`data: [DONE]
1333
+
1334
+ `));
1335
+ controller.close();
973
1336
  }
1337
+ return;
974
1338
  }
975
- dbg("Streaming finished", { hasContent, chunkCount });
976
- if (!hasContent && !streamedAnyText) {
977
- dbg("No content from textStream/callbacks; falling back to generate()");
1339
+ const test = testsInput[index++];
1340
+ active++;
1341
+ (async () => {
1342
+ const calledTools = /* @__PURE__ */ new Set();
1343
+ const expectedSet = new Set(test.expectedTools || []);
1344
+ let step = 0;
1345
+ let client = null;
978
1346
  try {
979
- const gen = await agent.generate(formattedMessages, {
980
- maxSteps: 10,
981
- toolsets
982
- });
983
- const finalText = gen.text || "";
984
- if (finalText) {
985
- controller.enqueue(
986
- encoder.encode(
987
- `data: ${JSON.stringify({ type: "text", content: finalText })}
988
-
989
- `
990
- )
991
- );
1347
+ let serverConfigs = {};
1348
+ if (test.selectedServers && test.selectedServers.length > 0) {
1349
+ for (const name of test.selectedServers) {
1350
+ if (allServers[name]) serverConfigs[name] = allServers[name];
1351
+ }
1352
+ } else {
1353
+ for (const [name, cfg] of Object.entries(allServers)) {
1354
+ serverConfigs[name] = cfg;
1355
+ }
1356
+ }
1357
+ const validation = validateMultipleServerConfigs(serverConfigs);
1358
+ let finalServers = {};
1359
+ if (validation.success && validation.validConfigs) {
1360
+ finalServers = validation.validConfigs;
1361
+ } else if (validation.validConfigs && Object.keys(validation.validConfigs).length > 0) {
1362
+ finalServers = validation.validConfigs;
992
1363
  } else {
993
- dbg("generate() also returned empty text");
994
- controller.enqueue(
995
- encoder.encode(
996
- `data: ${JSON.stringify({ type: "text", content: "I apologize, but I couldn't generate a response. Please try again." })}
1364
+ throw new Error("No valid MCP server configs for test");
1365
+ }
1366
+ client = createMCPClientWithMultipleConnections(finalServers);
1367
+ const model = createModel2(test.model);
1368
+ const agent = new Agent2({
1369
+ name: `TestAgent-${test.id}`,
1370
+ instructions: "You are a helpful assistant with access to MCP tools",
1371
+ model
1372
+ });
1373
+ const toolsets = await client.getToolsets();
1374
+ const stream = await agent.stream(
1375
+ [{ role: "user", content: test.prompt || "" }],
1376
+ {
1377
+ maxSteps: 10,
1378
+ toolsets,
1379
+ onStepFinish: ({ text, toolCalls, toolResults }) => {
1380
+ step += 1;
1381
+ (toolCalls || []).forEach((c2) => {
1382
+ const toolName = c2?.name || c2?.toolName;
1383
+ if (toolName) {
1384
+ calledTools.add(toolName);
1385
+ }
1386
+ });
1387
+ controller.enqueue(
1388
+ encoder.encode(
1389
+ `data: ${JSON.stringify({
1390
+ type: "trace_step",
1391
+ testId: test.id,
1392
+ step,
1393
+ text,
1394
+ toolCalls,
1395
+ toolResults
1396
+ })}
997
1397
 
998
1398
  `
999
- )
1000
- );
1399
+ )
1400
+ );
1401
+ }
1402
+ }
1403
+ );
1404
+ for await (const _ of stream.textStream) {
1001
1405
  }
1002
- } catch (fallbackErr) {
1003
- console.error("[mcp/chat] Fallback generate() error:", fallbackErr);
1406
+ const called = Array.from(calledTools);
1407
+ const missing = Array.from(expectedSet).filter(
1408
+ (t) => !calledTools.has(t)
1409
+ );
1410
+ const unexpected = called.filter((t) => !expectedSet.has(t));
1411
+ const passed = missing.length === 0 && unexpected.length === 0;
1412
+ if (!passed) failed = true;
1004
1413
  controller.enqueue(
1005
1414
  encoder.encode(
1006
- `data: ${JSON.stringify({ type: "error", error: fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr) })}
1415
+ `data: ${JSON.stringify({ type: "result", testId: test.id, passed, calledTools: called, missingTools: missing, unexpectedTools: unexpected })}
1007
1416
 
1008
1417
  `
1009
1418
  )
1010
1419
  );
1011
- }
1012
- }
1013
- controller.enqueue(
1014
- encoder.encode(
1015
- `data: ${JSON.stringify({
1016
- type: "elicitation_complete"
1017
- })}
1018
-
1019
- `
1020
- )
1021
- );
1022
- controller.enqueue(encoder.encode(`data: [DONE]
1023
-
1024
- `));
1025
- } catch (error) {
1026
- console.error("[mcp/chat] Streaming error:", error);
1027
- controller.enqueue(
1028
- encoder.encode(
1029
- `data: ${JSON.stringify({
1030
- type: "error",
1031
- error: error instanceof Error ? error.message : "Unknown error"
1032
- })}
1420
+ } catch (err) {
1421
+ failed = true;
1422
+ controller.enqueue(
1423
+ encoder.encode(
1424
+ `data: ${JSON.stringify({ type: "result", testId: test.id, passed: false, error: err?.message })}
1033
1425
 
1034
1426
  `
1035
- )
1036
- );
1037
- } finally {
1038
- if (client) {
1039
- try {
1040
- await client.disconnect();
1041
- } catch (cleanupError) {
1042
- console.warn(
1043
- "[mcp/chat] Error cleaning up MCP client after streaming:",
1044
- cleanupError
1427
+ )
1045
1428
  );
1429
+ } finally {
1430
+ try {
1431
+ await client?.disconnect();
1432
+ } catch {
1433
+ }
1434
+ active--;
1435
+ runNext();
1046
1436
  }
1047
- }
1048
- controller.close();
1437
+ })();
1438
+ };
1439
+ for (let i = 0; i < Math.min(maxConcurrency, testsInput.length); i++) {
1440
+ runNext();
1049
1441
  }
1050
1442
  }
1051
1443
  });
@@ -1056,55 +1448,17 @@ chat.post("/", async (c) => {
1056
1448
  Connection: "keep-alive"
1057
1449
  }
1058
1450
  });
1059
- } catch (error) {
1060
- console.error("[mcp/chat] Error in chat API:", error);
1061
- if (client) {
1062
- try {
1063
- await client.disconnect();
1064
- } catch (cleanupError) {
1065
- console.warn("Error cleaning up MCP client after error:", cleanupError);
1066
- }
1067
- }
1451
+ } catch (err) {
1068
1452
  return c.json(
1069
- {
1070
- success: false,
1071
- error: error instanceof Error ? error.message : "Unknown error"
1072
- },
1453
+ { success: false, error: err?.message || "Unknown error" },
1073
1454
  500
1074
1455
  );
1075
1456
  }
1076
1457
  });
1077
- var getLlmModel = (modelDefinition, apiKey, ollamaBaseUrl) => {
1078
- if (!modelDefinition || !modelDefinition.id || !modelDefinition.provider) {
1079
- throw new Error(
1080
- `Invalid model definition: ${JSON.stringify(modelDefinition)}`
1081
- );
1082
- }
1083
- switch (modelDefinition.provider) {
1084
- case "anthropic":
1085
- return createAnthropic({ apiKey })(modelDefinition.id);
1086
- case "openai":
1087
- return createOpenAI({ apiKey })(modelDefinition.id);
1088
- case "ollama":
1089
- const baseUrl = ollamaBaseUrl || "http://localhost:11434";
1090
- return createOllama({
1091
- // The provider expects the root Ollama URL; it internally targets the /api endpoints
1092
- baseURL: `${baseUrl}`
1093
- })(modelDefinition.id, {
1094
- simulateStreaming: true
1095
- // Enable streaming for Ollama models
1096
- });
1097
- default:
1098
- throw new Error(
1099
- `Unsupported provider: ${modelDefinition.provider} for model: ${modelDefinition.id}`
1100
- );
1101
- }
1102
- };
1103
- var chat_default = chat;
1104
1458
 
1105
1459
  // routes/mcp/oauth.ts
1106
- import { Hono as Hono6 } from "hono";
1107
- var oauth = new Hono6();
1460
+ import { Hono as Hono8 } from "hono";
1461
+ var oauth = new Hono8();
1108
1462
  oauth.get("/metadata", async (c) => {
1109
1463
  try {
1110
1464
  const url = c.req.query("url");
@@ -1150,7 +1504,7 @@ oauth.get("/metadata", async (c) => {
1150
1504
  var oauth_default = oauth;
1151
1505
 
1152
1506
  // routes/mcp/index.ts
1153
- var mcp = new Hono7();
1507
+ var mcp = new Hono9();
1154
1508
  mcp.get("/health", (c) => {
1155
1509
  return c.json({
1156
1510
  service: "MCP API",
@@ -1160,12 +1514,311 @@ mcp.get("/health", (c) => {
1160
1514
  });
1161
1515
  mcp.route("/chat", chat_default);
1162
1516
  mcp.route("/connect", connect_default);
1517
+ mcp.route("/servers", servers_default);
1163
1518
  mcp.route("/tools", tools_default);
1519
+ mcp.route("/tests", tests_default);
1164
1520
  mcp.route("/resources", resources_default);
1165
1521
  mcp.route("/prompts", prompts_default);
1166
1522
  mcp.route("/oauth", oauth_default);
1167
1523
  var mcp_default = mcp;
1168
1524
 
1525
+ // services/mcpjam-client-manager.ts
1526
+ import { MCPClient as MCPClient2 } from "@mastra/mcp";
1527
+ function normalizeServerId(serverId) {
1528
+ return serverId.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
1529
+ }
1530
+ var MCPJamClientManager = class {
1531
+ mcpClients = /* @__PURE__ */ new Map();
1532
+ statuses = /* @__PURE__ */ new Map();
1533
+ configs = /* @__PURE__ */ new Map();
1534
+ toolRegistry = /* @__PURE__ */ new Map();
1535
+ resourceRegistry = /* @__PURE__ */ new Map();
1536
+ promptRegistry = /* @__PURE__ */ new Map();
1537
+ // Store for pending elicitation requests with Promise resolvers
1538
+ pendingElicitations = /* @__PURE__ */ new Map();
1539
+ // Optional callback for handling elicitation requests
1540
+ elicitationCallback;
1541
+ async connectToServer(serverId, serverConfig) {
1542
+ const id = normalizeServerId(serverId);
1543
+ if (this.mcpClients.has(id)) return;
1544
+ const validation = validateServerConfig(serverConfig);
1545
+ if (!validation.success) {
1546
+ this.statuses.set(id, "error");
1547
+ throw new Error(validation.error.message);
1548
+ }
1549
+ this.configs.set(id, validation.config);
1550
+ this.statuses.set(id, "connecting");
1551
+ const client = new MCPClient2({
1552
+ id: `mcpjam-${id}`,
1553
+ servers: { [id]: validation.config }
1554
+ });
1555
+ try {
1556
+ await client.getTools();
1557
+ this.mcpClients.set(id, client);
1558
+ this.statuses.set(id, "connected");
1559
+ if (client.elicitation?.onRequest) {
1560
+ const normalizedName = normalizeServerConfigName(serverId);
1561
+ client.elicitation.onRequest(
1562
+ normalizedName,
1563
+ async (elicitationRequest) => {
1564
+ return await this.handleElicitationRequest(elicitationRequest);
1565
+ }
1566
+ );
1567
+ }
1568
+ await this.discoverServerResources(id);
1569
+ } catch (err) {
1570
+ this.statuses.set(id, "error");
1571
+ try {
1572
+ await client.disconnect();
1573
+ } catch {
1574
+ }
1575
+ this.mcpClients.delete(id);
1576
+ throw err;
1577
+ }
1578
+ }
1579
+ async disconnectFromServer(serverId) {
1580
+ const id = normalizeServerId(serverId);
1581
+ const client = this.mcpClients.get(id);
1582
+ if (client) {
1583
+ try {
1584
+ await client.disconnect();
1585
+ } catch {
1586
+ }
1587
+ }
1588
+ this.mcpClients.delete(id);
1589
+ this.statuses.set(id, "disconnected");
1590
+ for (const key of Array.from(this.toolRegistry.keys())) {
1591
+ const item = this.toolRegistry.get(key);
1592
+ if (item.serverId === id) this.toolRegistry.delete(key);
1593
+ }
1594
+ for (const key of Array.from(this.resourceRegistry.keys())) {
1595
+ const item = this.resourceRegistry.get(key);
1596
+ if (item.serverId === id) this.resourceRegistry.delete(key);
1597
+ }
1598
+ for (const key of Array.from(this.promptRegistry.keys())) {
1599
+ const item = this.promptRegistry.get(key);
1600
+ if (item.serverId === id) this.promptRegistry.delete(key);
1601
+ }
1602
+ }
1603
+ getConnectionStatus(serverId) {
1604
+ const id = normalizeServerId(serverId);
1605
+ return this.statuses.get(id) || "disconnected";
1606
+ }
1607
+ getConnectedServers() {
1608
+ const servers2 = {};
1609
+ for (const [serverId, status] of this.statuses.entries()) {
1610
+ servers2[serverId] = {
1611
+ status,
1612
+ config: this.configs.get(serverId)
1613
+ };
1614
+ }
1615
+ return servers2;
1616
+ }
1617
+ async discoverAllResources() {
1618
+ const serverIds = Array.from(this.mcpClients.keys());
1619
+ await Promise.all(serverIds.map((id) => this.discoverServerResources(id)));
1620
+ }
1621
+ async discoverServerResources(serverId) {
1622
+ const id = normalizeServerId(serverId);
1623
+ const client = this.mcpClients.get(id);
1624
+ if (!client) return;
1625
+ const tools2 = await client.getTools();
1626
+ for (const [name, tool] of Object.entries(tools2)) {
1627
+ this.toolRegistry.set(`${id}:${name}`, {
1628
+ name,
1629
+ description: tool.description,
1630
+ inputSchema: tool.inputSchema,
1631
+ outputSchema: tool.outputSchema,
1632
+ serverId: id
1633
+ });
1634
+ }
1635
+ try {
1636
+ const res = await client.resources.list();
1637
+ for (const [group, list] of Object.entries(res)) {
1638
+ for (const r of list) {
1639
+ this.resourceRegistry.set(`${id}:${r.uri}`, {
1640
+ uri: r.uri,
1641
+ name: r.name,
1642
+ description: r.description,
1643
+ mimeType: r.mimeType,
1644
+ serverId: id
1645
+ });
1646
+ }
1647
+ }
1648
+ } catch {
1649
+ }
1650
+ try {
1651
+ const prompts2 = await client.prompts.list();
1652
+ for (const [group, list] of Object.entries(prompts2)) {
1653
+ for (const p of list) {
1654
+ this.promptRegistry.set(`${id}:${p.name}`, {
1655
+ name: p.name,
1656
+ description: p.description,
1657
+ arguments: p.arguments,
1658
+ serverId: id
1659
+ });
1660
+ }
1661
+ }
1662
+ } catch {
1663
+ }
1664
+ }
1665
+ getAvailableTools() {
1666
+ return Array.from(this.toolRegistry.values());
1667
+ }
1668
+ async getToolsetsForServer(serverId) {
1669
+ const id = normalizeServerId(serverId);
1670
+ const client = this.mcpClients.get(id);
1671
+ if (!client) {
1672
+ throw new Error(`No MCP client available for server: ${serverId}`);
1673
+ }
1674
+ const toolsets = await client.getToolsets();
1675
+ const flattenedTools = {};
1676
+ Object.values(toolsets).forEach((serverTools) => {
1677
+ Object.assign(flattenedTools, serverTools);
1678
+ });
1679
+ return flattenedTools;
1680
+ }
1681
+ getAvailableResources() {
1682
+ return Array.from(this.resourceRegistry.values());
1683
+ }
1684
+ getResourcesForServer(serverId) {
1685
+ const id = normalizeServerId(serverId);
1686
+ return Array.from(this.resourceRegistry.values()).filter(
1687
+ (r) => r.serverId === id
1688
+ );
1689
+ }
1690
+ getAvailablePrompts() {
1691
+ return Array.from(this.promptRegistry.values());
1692
+ }
1693
+ getPromptsForServer(serverId) {
1694
+ const id = normalizeServerId(serverId);
1695
+ return Array.from(this.promptRegistry.values()).filter(
1696
+ (p) => p.serverId === id
1697
+ );
1698
+ }
1699
+ async executeToolDirect(toolName, parameters) {
1700
+ let serverId = "";
1701
+ let name = toolName;
1702
+ if (toolName.includes(":")) {
1703
+ const [sid, n] = toolName.split(":", 2);
1704
+ serverId = normalizeServerId(sid);
1705
+ name = n;
1706
+ } else {
1707
+ for (const [key, tool2] of this.toolRegistry.entries()) {
1708
+ if (tool2.name === toolName) {
1709
+ serverId = tool2.serverId;
1710
+ name = toolName;
1711
+ break;
1712
+ }
1713
+ }
1714
+ }
1715
+ if (!serverId) {
1716
+ for (const [clientServerId, client2] of this.mcpClients.entries()) {
1717
+ try {
1718
+ const toolsets2 = await client2.getToolsets();
1719
+ const flattenedTools2 = {};
1720
+ Object.values(toolsets2).forEach((serverTools) => {
1721
+ Object.assign(flattenedTools2, serverTools);
1722
+ });
1723
+ if (flattenedTools2[toolName]) {
1724
+ serverId = clientServerId;
1725
+ name = toolName;
1726
+ break;
1727
+ }
1728
+ } catch {
1729
+ }
1730
+ }
1731
+ }
1732
+ if (!serverId) {
1733
+ throw new Error(`Tool not found in any connected server: ${toolName}`);
1734
+ }
1735
+ const client = this.mcpClients.get(serverId);
1736
+ if (!client)
1737
+ throw new Error(`No MCP client available for server: ${serverId}`);
1738
+ const toolsets = await client.getToolsets();
1739
+ const flattenedTools = {};
1740
+ Object.values(toolsets).forEach((serverTools) => {
1741
+ Object.assign(flattenedTools, serverTools);
1742
+ });
1743
+ const tool = flattenedTools[name];
1744
+ if (!tool)
1745
+ throw new Error(`Tool '${name}' not found in server '${serverId}'`);
1746
+ const result = await tool.execute({ context: parameters || {} });
1747
+ return { result };
1748
+ }
1749
+ async getResource(resourceUri, serverId) {
1750
+ let uri = resourceUri;
1751
+ const client = this.mcpClients.get(serverId);
1752
+ if (!client) throw new Error("No MCP client available");
1753
+ const content = await client.resources.read(serverId, uri);
1754
+ return { contents: content?.contents || [] };
1755
+ }
1756
+ async getPrompt(promptName, serverId, args) {
1757
+ const client = this.mcpClients.get(serverId);
1758
+ if (!client) throw new Error("No MCP client available");
1759
+ const content = await client.prompts.get({
1760
+ serverName: serverId,
1761
+ name: promptName,
1762
+ args: args || {}
1763
+ });
1764
+ return { content };
1765
+ }
1766
+ /**
1767
+ * Handles elicitation requests from MCP servers during direct tool execution
1768
+ */
1769
+ async handleElicitationRequest(elicitationRequest) {
1770
+ const requestId = `elicit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1771
+ return new Promise((resolve, reject) => {
1772
+ this.pendingElicitations.set(requestId, { resolve, reject });
1773
+ if (this.elicitationCallback) {
1774
+ this.elicitationCallback({
1775
+ requestId,
1776
+ message: elicitationRequest.message,
1777
+ schema: elicitationRequest.requestedSchema
1778
+ }).then(resolve).catch(reject);
1779
+ } else {
1780
+ const error = new Error("ELICITATION_REQUIRED");
1781
+ error.elicitationRequest = {
1782
+ requestId,
1783
+ message: elicitationRequest.message,
1784
+ schema: elicitationRequest.requestedSchema
1785
+ };
1786
+ reject(error);
1787
+ }
1788
+ });
1789
+ }
1790
+ /**
1791
+ * Responds to a pending elicitation request
1792
+ */
1793
+ respondToElicitation(requestId, response) {
1794
+ const pending = this.pendingElicitations.get(requestId);
1795
+ if (!pending) {
1796
+ return false;
1797
+ }
1798
+ pending.resolve(response);
1799
+ this.pendingElicitations.delete(requestId);
1800
+ return true;
1801
+ }
1802
+ /**
1803
+ * Gets the pending elicitations map for external access
1804
+ */
1805
+ getPendingElicitations() {
1806
+ return this.pendingElicitations;
1807
+ }
1808
+ /**
1809
+ * Sets a callback to handle elicitation requests
1810
+ */
1811
+ setElicitationCallback(callback) {
1812
+ this.elicitationCallback = callback;
1813
+ }
1814
+ /**
1815
+ * Clears the elicitation callback
1816
+ */
1817
+ clearElicitationCallback() {
1818
+ this.elicitationCallback = void 0;
1819
+ }
1820
+ };
1821
+
1169
1822
  // index.ts
1170
1823
  function logBox(content, title) {
1171
1824
  const lines = content.split("\n");
@@ -1199,7 +1852,12 @@ function getMCPConfigFromEnv() {
1199
1852
  // Default name for CLI-provided servers
1200
1853
  };
1201
1854
  }
1202
- var app = new Hono8();
1855
+ var app = new Hono10();
1856
+ var mcpJamClientManager = new MCPJamClientManager();
1857
+ app.use("*", async (c, next) => {
1858
+ c.mcpJamClientManager = mcpJamClientManager;
1859
+ await next();
1860
+ });
1203
1861
  app.use("*", logger());
1204
1862
  var serverPort = process.env.PORT || "3001";
1205
1863
  var corsOrigins = [
@@ -1231,7 +1889,7 @@ if (process.env.NODE_ENV === "production") {
1231
1889
  if (path.startsWith("/api/")) {
1232
1890
  return c.notFound();
1233
1891
  }
1234
- const indexPath = join(process.cwd(), "dist", "client", "index.html");
1892
+ const indexPath = join2(process.cwd(), "dist", "client", "index.html");
1235
1893
  let htmlContent = readFileSync(indexPath, "utf-8");
1236
1894
  const mcpConfig = getMCPConfigFromEnv();
1237
1895
  if (mcpConfig) {