@mcpjam/inspector 0.9.23 → 0.9.25

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