@mcpjam/inspector 0.9.4 → 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
- );
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;
323
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,61 +502,215 @@ 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) {
505
+ const { serverId, name, args } = await c.req.json();
506
+ if (!serverId) {
507
+ return c.json({ success: false, error: "serverId is required" }, 400);
508
+ }
509
+ if (!name) {
601
510
  return c.json(
602
- { success: false, error: validation.error.message },
603
- validation.error.status
511
+ {
512
+ success: false,
513
+ error: "Prompt name is required"
514
+ },
515
+ 400
604
516
  );
605
517
  }
606
- if (!name) {
607
- return c.json(
608
- {
609
- success: false,
610
- error: "Prompt name is required"
611
- },
612
- 400
613
- );
518
+ const mcpJamClientManager2 = c.mcpJamClientManager;
519
+ const content = await mcpJamClientManager2.getPrompt(
520
+ name,
521
+ serverId,
522
+ args || {}
523
+ );
524
+ return c.json({ content });
525
+ } catch (error) {
526
+ console.error("Error getting prompt:", error);
527
+ return c.json(
528
+ {
529
+ success: false,
530
+ error: error instanceof Error ? error.message : "Unknown error"
531
+ },
532
+ 500
533
+ );
534
+ }
535
+ });
536
+ var prompts_default = prompts;
537
+
538
+ // routes/mcp/chat.ts
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
614
675
  }
615
- const client = createMCPClient(
616
- validation.config,
617
- `prompts-get-${Date.now()}`
618
- );
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;
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
+ }
630
695
  }
631
- } catch (error) {
632
- console.error("Error getting prompt:", error);
633
- return c.json(
634
- {
635
- success: false,
636
- error: error instanceof Error ? error.message : "Unknown error"
637
- },
638
- 500
639
- );
640
- }
641
- });
642
- var prompts_default = prompts;
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
+ }
643
703
 
644
704
  // routes/mcp/chat.ts
645
- import { Hono as Hono5 } from "hono";
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 { TextEncoder as TextEncoder2 } from "util";
709
+ import { TextEncoder as TextEncoder3 } from "util";
651
710
 
652
711
  // ../client/src/lib/chat-utils.ts
712
+ import { clsx } from "clsx";
713
+ import { twMerge } from "tailwind-merge";
653
714
  function getDefaultTemperatureByProvider(provider) {
654
715
  switch (provider) {
655
716
  case "openai":
@@ -673,7 +734,7 @@ try {
673
734
  } catch {
674
735
  }
675
736
  var pendingElicitations2 = /* @__PURE__ */ new Map();
676
- var chat = new Hono5();
737
+ var chat = new Hono6();
677
738
  var createLlmModel = (modelDefinition, apiKey, ollamaBaseUrl) => {
678
739
  if (!modelDefinition?.id || !modelDefinition?.provider) {
679
740
  throw new Error(
@@ -862,6 +923,29 @@ var handleAgentStepFinish = (streamingContext, text, toolCalls, toolResults) =>
862
923
  }
863
924
  }
864
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
+ }
865
949
  } catch (err) {
866
950
  dbg("onStepFinish error", err);
867
951
  }
@@ -1029,7 +1113,6 @@ chat.post("/", async (c) => {
1029
1113
  }
1030
1114
  client = createMCPClientWithMultipleConnections(validation.validConfigs);
1031
1115
  const llmModel = createLlmModel(model, apiKey, ollamaBaseUrl);
1032
- const tools2 = await client.getTools();
1033
1116
  const agent = new Agent({
1034
1117
  name: "MCP Chat Agent",
1035
1118
  instructions: systemPrompt || "You are a helpful assistant with access to MCP tools.",
@@ -1046,17 +1129,22 @@ chat.post("/", async (c) => {
1046
1129
  toolsetServers: Object.keys(toolsets),
1047
1130
  messageCount: formattedMessages.length
1048
1131
  });
1049
- const encoder = new TextEncoder2();
1132
+ const encoder = new TextEncoder3();
1050
1133
  const readableStream = new ReadableStream({
1051
1134
  async start(controller) {
1052
1135
  const streamingContext = {
1053
1136
  controller,
1054
1137
  encoder,
1055
1138
  toolCallId: 0,
1056
- lastEmittedToolCallId: null
1139
+ lastEmittedToolCallId: null,
1140
+ stepIndex: 0
1057
1141
  };
1142
+ const flattenedTools = {};
1143
+ Object.values(toolsets).forEach((serverTools) => {
1144
+ Object.assign(flattenedTools, serverTools);
1145
+ });
1058
1146
  const streamingWrappedTools = wrapToolsWithStreaming(
1059
- tools2,
1147
+ flattenedTools,
1060
1148
  streamingContext
1061
1149
  );
1062
1150
  const streamingAgent = new Agent({
@@ -1122,9 +1210,255 @@ chat.post("/", async (c) => {
1122
1210
  });
1123
1211
  var chat_default = chat;
1124
1212
 
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}`);
1302
+ }
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
+ }
1317
+ const readableStream = new ReadableStream({
1318
+ async start(controller) {
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) {
1325
+ controller.enqueue(
1326
+ encoder.encode(
1327
+ `data: ${JSON.stringify({ type: "run_complete", passed: !failed })}
1328
+
1329
+ `
1330
+ )
1331
+ );
1332
+ controller.enqueue(encoder.encode(`data: [DONE]
1333
+
1334
+ `));
1335
+ controller.close();
1336
+ }
1337
+ return;
1338
+ }
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;
1346
+ try {
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;
1363
+ } else {
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
+ })}
1397
+
1398
+ `
1399
+ )
1400
+ );
1401
+ }
1402
+ }
1403
+ );
1404
+ for await (const _ of stream.textStream) {
1405
+ }
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;
1413
+ controller.enqueue(
1414
+ encoder.encode(
1415
+ `data: ${JSON.stringify({ type: "result", testId: test.id, passed, calledTools: called, missingTools: missing, unexpectedTools: unexpected })}
1416
+
1417
+ `
1418
+ )
1419
+ );
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 })}
1425
+
1426
+ `
1427
+ )
1428
+ );
1429
+ } finally {
1430
+ try {
1431
+ await client?.disconnect();
1432
+ } catch {
1433
+ }
1434
+ active--;
1435
+ runNext();
1436
+ }
1437
+ })();
1438
+ };
1439
+ for (let i = 0; i < Math.min(maxConcurrency, testsInput.length); i++) {
1440
+ runNext();
1441
+ }
1442
+ }
1443
+ });
1444
+ return new Response(readableStream, {
1445
+ headers: {
1446
+ "Content-Type": "text/event-stream",
1447
+ "Cache-Control": "no-cache",
1448
+ Connection: "keep-alive"
1449
+ }
1450
+ });
1451
+ } catch (err) {
1452
+ return c.json(
1453
+ { success: false, error: err?.message || "Unknown error" },
1454
+ 500
1455
+ );
1456
+ }
1457
+ });
1458
+
1125
1459
  // routes/mcp/oauth.ts
1126
- import { Hono as Hono6 } from "hono";
1127
- var oauth = new Hono6();
1460
+ import { Hono as Hono8 } from "hono";
1461
+ var oauth = new Hono8();
1128
1462
  oauth.get("/metadata", async (c) => {
1129
1463
  try {
1130
1464
  const url = c.req.query("url");
@@ -1170,7 +1504,7 @@ oauth.get("/metadata", async (c) => {
1170
1504
  var oauth_default = oauth;
1171
1505
 
1172
1506
  // routes/mcp/index.ts
1173
- var mcp = new Hono7();
1507
+ var mcp = new Hono9();
1174
1508
  mcp.get("/health", (c) => {
1175
1509
  return c.json({
1176
1510
  service: "MCP API",
@@ -1180,12 +1514,311 @@ mcp.get("/health", (c) => {
1180
1514
  });
1181
1515
  mcp.route("/chat", chat_default);
1182
1516
  mcp.route("/connect", connect_default);
1517
+ mcp.route("/servers", servers_default);
1183
1518
  mcp.route("/tools", tools_default);
1519
+ mcp.route("/tests", tests_default);
1184
1520
  mcp.route("/resources", resources_default);
1185
1521
  mcp.route("/prompts", prompts_default);
1186
1522
  mcp.route("/oauth", oauth_default);
1187
1523
  var mcp_default = mcp;
1188
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
+
1189
1822
  // index.ts
1190
1823
  function logBox(content, title) {
1191
1824
  const lines = content.split("\n");
@@ -1219,7 +1852,12 @@ function getMCPConfigFromEnv() {
1219
1852
  // Default name for CLI-provided servers
1220
1853
  };
1221
1854
  }
1222
- 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
+ });
1223
1861
  app.use("*", logger());
1224
1862
  var serverPort = process.env.PORT || "3001";
1225
1863
  var corsOrigins = [
@@ -1251,7 +1889,7 @@ if (process.env.NODE_ENV === "production") {
1251
1889
  if (path.startsWith("/api/")) {
1252
1890
  return c.notFound();
1253
1891
  }
1254
- const indexPath = join(process.cwd(), "dist", "client", "index.html");
1892
+ const indexPath = join2(process.cwd(), "dist", "client", "index.html");
1255
1893
  let htmlContent = readFileSync(indexPath, "utf-8");
1256
1894
  const mcpConfig = getMCPConfigFromEnv();
1257
1895
  if (mcpConfig) {