@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.
- package/README.md +8 -0
- package/bin/start.js +7 -9
- package/dist/client/assets/index-DvbqjJFH.js +1768 -0
- package/dist/client/assets/index-DvbqjJFH.js.map +1 -0
- package/dist/client/assets/index-n_nZRL9H.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/index.js +67 -6
- package/dist/server/index.js.map +1 -1
- package/package.json +6 -1
- package/.vite/build/main.cjs +0 -56477
- package/dist/client/assets/index-BlNP9RiD.css +0 -1
- package/dist/client/assets/index-QjBf0I70.js +0 -1736
- package/dist/client/assets/index-QjBf0I70.js.map +0 -1
- package/dist/main/main.cjs +0 -1818
- package/dist/preload/preload.js +0 -26
- package/dist/renderer/assets/index-BXt-im0x.css +0 -1
- package/dist/renderer/assets/index-DopM0wo-.js +0 -1737
- package/dist/renderer/catalyst.png +0 -0
- package/dist/renderer/claude_logo.png +0 -0
- package/dist/renderer/deepseek_logo.svg +0 -1
- package/dist/renderer/demo_1.png +0 -0
- package/dist/renderer/demo_2.png +0 -0
- package/dist/renderer/demo_3.png +0 -0
- package/dist/renderer/file.svg +0 -1
- package/dist/renderer/globe.svg +0 -1
- package/dist/renderer/index.html +0 -14
- package/dist/renderer/mcp.svg +0 -1
- package/dist/renderer/mcp_jam.svg +0 -12
- package/dist/renderer/mcp_jam_dark.png +0 -0
- package/dist/renderer/mcp_jam_light.png +0 -0
- package/dist/renderer/next.svg +0 -1
- package/dist/renderer/ollama_dark.png +0 -0
- package/dist/renderer/ollama_logo.svg +0 -7
- package/dist/renderer/openai_logo.png +0 -0
- package/dist/renderer/vercel.svg +0 -1
- package/dist/renderer/window.svg +0 -1
package/dist/main/main.cjs
DELETED
|
@@ -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
|
-
}
|