@mcpjam/inspector 0.9.2 → 0.9.4
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/LICENSE +13 -0
- package/README.md +25 -6
- package/bin/start.js +6 -3
- package/dist/client/assets/index-C-vxFpw4.css +1 -0
- package/dist/client/assets/index-CTiiyjex.js +1723 -0
- package/dist/client/assets/index-CTiiyjex.js.map +1 -0
- package/dist/client/deepseek_logo.svg +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/index.js +381 -211
- package/dist/server/index.js.map +1 -1
- package/package.json +15 -10
- package/dist/client/assets/index-BxtnlkQW.css +0 -1
- package/dist/client/assets/index-C2tpp_KC.js +0 -357
- package/dist/client/assets/index-C2tpp_KC.js.map +0 -1
- package/dist/main/main.cjs +0 -1315
- package/dist/preload/preload.js +0 -26
- package/dist/renderer/assets/index-CYiU4_x2.css +0 -1
- package/dist/renderer/assets/index-woGCpEdp.js +0 -356
- package/dist/renderer/catalyst.png +0 -0
- package/dist/renderer/claude_logo.png +0 -0
- 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,1315 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const electron = require("electron");
|
|
3
|
-
const nodeServer = require("@hono/node-server");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const hono = require("hono");
|
|
6
|
-
const cors = require("hono/cors");
|
|
7
|
-
const logger = require("hono/logger");
|
|
8
|
-
const serveStatic = require("@hono/node-server/serve-static");
|
|
9
|
-
const mcp$1 = require("@mastra/mcp");
|
|
10
|
-
const zodToJsonSchema = require("zod-to-json-schema");
|
|
11
|
-
const agent = require("@mastra/core/agent");
|
|
12
|
-
const anthropic = require("@ai-sdk/anthropic");
|
|
13
|
-
const openai = require("@ai-sdk/openai");
|
|
14
|
-
const ollamaAiProvider = require("ollama-ai-provider");
|
|
15
|
-
const log = require("electron-log");
|
|
16
|
-
const updateElectronApp = require("update-electron-app");
|
|
17
|
-
const fs = require("fs/promises");
|
|
18
|
-
function validateServerConfig(serverConfig) {
|
|
19
|
-
var _a, _b, _c;
|
|
20
|
-
if (!serverConfig) {
|
|
21
|
-
return {
|
|
22
|
-
success: false,
|
|
23
|
-
error: {
|
|
24
|
-
message: "Server configuration is required",
|
|
25
|
-
status: 400
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
const config = { ...serverConfig };
|
|
30
|
-
if (config.url) {
|
|
31
|
-
try {
|
|
32
|
-
if (typeof config.url === "string") {
|
|
33
|
-
config.url = new URL(config.url);
|
|
34
|
-
} else if (typeof config.url === "object" && !config.url.href) {
|
|
35
|
-
return {
|
|
36
|
-
success: false,
|
|
37
|
-
error: {
|
|
38
|
-
message: "Invalid URL configuration",
|
|
39
|
-
status: 400
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
if ((_a = config.oauth) == null ? void 0 : _a.access_token) {
|
|
44
|
-
const authHeaders = {
|
|
45
|
-
Authorization: `Bearer ${config.oauth.access_token}`,
|
|
46
|
-
...((_b = config.requestInit) == null ? void 0 : _b.headers) || {}
|
|
47
|
-
};
|
|
48
|
-
config.requestInit = {
|
|
49
|
-
...config.requestInit,
|
|
50
|
-
headers: authHeaders
|
|
51
|
-
};
|
|
52
|
-
config.eventSourceInit = {
|
|
53
|
-
fetch(input, init) {
|
|
54
|
-
var _a2;
|
|
55
|
-
const headers = new Headers((init == null ? void 0 : init.headers) || {});
|
|
56
|
-
headers.set(
|
|
57
|
-
"Authorization",
|
|
58
|
-
`Bearer ${config.oauth.access_token}`
|
|
59
|
-
);
|
|
60
|
-
if ((_a2 = config.requestInit) == null ? void 0 : _a2.headers) {
|
|
61
|
-
const requestHeaders = new Headers(config.requestInit.headers);
|
|
62
|
-
requestHeaders.forEach((value, key) => {
|
|
63
|
-
if (key.toLowerCase() !== "authorization") {
|
|
64
|
-
headers.set(key, value);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
return fetch(input, {
|
|
69
|
-
...init,
|
|
70
|
-
headers
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
} else if ((_c = config.requestInit) == null ? void 0 : _c.headers) {
|
|
75
|
-
config.eventSourceInit = {
|
|
76
|
-
fetch(input, init) {
|
|
77
|
-
const headers = new Headers((init == null ? void 0 : init.headers) || {});
|
|
78
|
-
const requestHeaders = new Headers(config.requestInit.headers);
|
|
79
|
-
requestHeaders.forEach((value, key) => {
|
|
80
|
-
headers.set(key, value);
|
|
81
|
-
});
|
|
82
|
-
return fetch(input, {
|
|
83
|
-
...init,
|
|
84
|
-
headers
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
} catch (error) {
|
|
90
|
-
return {
|
|
91
|
-
success: false,
|
|
92
|
-
error: {
|
|
93
|
-
message: `Invalid URL format: ${error}`,
|
|
94
|
-
status: 400
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return {
|
|
100
|
-
success: true,
|
|
101
|
-
config
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
function createMCPClient(config, id) {
|
|
105
|
-
return new mcp$1.MCPClient({
|
|
106
|
-
id,
|
|
107
|
-
servers: {
|
|
108
|
-
server: config
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
const validateMultipleServerConfigs = (serverConfigs) => {
|
|
113
|
-
if (!serverConfigs || Object.keys(serverConfigs).length === 0) {
|
|
114
|
-
return {
|
|
115
|
-
success: false,
|
|
116
|
-
error: {
|
|
117
|
-
message: "At least one server configuration is required",
|
|
118
|
-
status: 400
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
const validConfigs = {};
|
|
123
|
-
const errors = {};
|
|
124
|
-
let hasErrors = false;
|
|
125
|
-
for (const [serverName, serverConfig] of Object.entries(serverConfigs)) {
|
|
126
|
-
const validationResult = validateServerConfig(serverConfig);
|
|
127
|
-
if (validationResult.success && validationResult.config) {
|
|
128
|
-
validConfigs[serverName] = validationResult.config;
|
|
129
|
-
} else {
|
|
130
|
-
hasErrors = true;
|
|
131
|
-
let errorMessage = "Configuration validation failed";
|
|
132
|
-
if (validationResult.error) {
|
|
133
|
-
errorMessage = validationResult.error.message;
|
|
134
|
-
}
|
|
135
|
-
errors[serverName] = errorMessage;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (!hasErrors) {
|
|
139
|
-
return {
|
|
140
|
-
success: true,
|
|
141
|
-
validConfigs
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
if (Object.keys(validConfigs).length > 0) {
|
|
145
|
-
return {
|
|
146
|
-
success: false,
|
|
147
|
-
validConfigs,
|
|
148
|
-
errors
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
return {
|
|
152
|
-
success: false,
|
|
153
|
-
errors,
|
|
154
|
-
error: {
|
|
155
|
-
message: "All server configurations failed validation",
|
|
156
|
-
status: 400
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
};
|
|
160
|
-
function createMCPClientWithMultipleConnections(serverConfigs) {
|
|
161
|
-
const normalizedConfigs = {};
|
|
162
|
-
for (const [serverName, config] of Object.entries(serverConfigs)) {
|
|
163
|
-
const normalizedName = normalizeServerConfigName(serverName);
|
|
164
|
-
normalizedConfigs[normalizedName] = config;
|
|
165
|
-
}
|
|
166
|
-
return new mcp$1.MCPClient({
|
|
167
|
-
id: `chat-${Date.now()}`,
|
|
168
|
-
servers: normalizedConfigs
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
function normalizeServerConfigName(serverName) {
|
|
172
|
-
return serverName.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
173
|
-
}
|
|
174
|
-
const connect = new hono.Hono();
|
|
175
|
-
connect.post("/", async (c) => {
|
|
176
|
-
try {
|
|
177
|
-
const { serverConfig } = await c.req.json();
|
|
178
|
-
const validation = validateServerConfig(serverConfig);
|
|
179
|
-
if (!validation.success) {
|
|
180
|
-
const error = validation.error;
|
|
181
|
-
return c.json(
|
|
182
|
-
{
|
|
183
|
-
success: false,
|
|
184
|
-
error: error.message
|
|
185
|
-
},
|
|
186
|
-
error.status
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
let client;
|
|
190
|
-
try {
|
|
191
|
-
client = createMCPClient(validation.config, `test-${Date.now()}`);
|
|
192
|
-
} catch (error) {
|
|
193
|
-
return c.json(
|
|
194
|
-
{
|
|
195
|
-
success: false,
|
|
196
|
-
error: `Failed to create a MCP client. Please double check your server configuration: ${JSON.stringify(serverConfig)}`,
|
|
197
|
-
details: error instanceof Error ? error.message : "Unknown error"
|
|
198
|
-
},
|
|
199
|
-
500
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
await client.getTools();
|
|
204
|
-
await client.disconnect();
|
|
205
|
-
return c.json({
|
|
206
|
-
success: true
|
|
207
|
-
});
|
|
208
|
-
} catch (error) {
|
|
209
|
-
return c.json(
|
|
210
|
-
{
|
|
211
|
-
success: false,
|
|
212
|
-
error: `MCP configuration is invalid. Please double check your server configuration: ${JSON.stringify(serverConfig)}`,
|
|
213
|
-
details: error instanceof Error ? error.message : "Unknown error"
|
|
214
|
-
},
|
|
215
|
-
500
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
} catch (error) {
|
|
219
|
-
return c.json(
|
|
220
|
-
{
|
|
221
|
-
success: false,
|
|
222
|
-
error: "Failed to parse request body",
|
|
223
|
-
details: error instanceof Error ? error.message : "Unknown error"
|
|
224
|
-
},
|
|
225
|
-
400
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
const tools = new hono.Hono();
|
|
230
|
-
const pendingElicitations$1 = /* @__PURE__ */ new Map();
|
|
231
|
-
tools.post("/", async (c) => {
|
|
232
|
-
let client = null;
|
|
233
|
-
let encoder = null;
|
|
234
|
-
let streamController = null;
|
|
235
|
-
let action;
|
|
236
|
-
let toolName;
|
|
237
|
-
try {
|
|
238
|
-
const requestData = await c.req.json();
|
|
239
|
-
action = requestData.action;
|
|
240
|
-
toolName = requestData.toolName;
|
|
241
|
-
const { serverConfig, parameters, requestId, response } = requestData;
|
|
242
|
-
if (!action || !["list", "execute", "respond"].includes(action)) {
|
|
243
|
-
return c.json(
|
|
244
|
-
{
|
|
245
|
-
success: false,
|
|
246
|
-
error: "Action must be 'list', 'execute', or 'respond'"
|
|
247
|
-
},
|
|
248
|
-
400
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
if (action === "respond") {
|
|
252
|
-
if (!requestId) {
|
|
253
|
-
return c.json(
|
|
254
|
-
{
|
|
255
|
-
success: false,
|
|
256
|
-
error: "requestId is required for respond action"
|
|
257
|
-
},
|
|
258
|
-
400
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
const pending = pendingElicitations$1.get(requestId);
|
|
262
|
-
if (!pending) {
|
|
263
|
-
return c.json(
|
|
264
|
-
{
|
|
265
|
-
success: false,
|
|
266
|
-
error: "No pending elicitation found for this requestId"
|
|
267
|
-
},
|
|
268
|
-
404
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
pending.resolve(response);
|
|
272
|
-
pendingElicitations$1.delete(requestId);
|
|
273
|
-
return c.json({ success: true });
|
|
274
|
-
}
|
|
275
|
-
const validation = validateServerConfig(serverConfig);
|
|
276
|
-
if (!validation.success) {
|
|
277
|
-
return c.json(
|
|
278
|
-
{ success: false, error: validation.error.message },
|
|
279
|
-
validation.error.status
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
encoder = new TextEncoder();
|
|
283
|
-
const readableStream = new ReadableStream({
|
|
284
|
-
async start(controller) {
|
|
285
|
-
streamController = controller;
|
|
286
|
-
try {
|
|
287
|
-
const clientId = `tools-${action}-${Date.now()}`;
|
|
288
|
-
client = createMCPClient(validation.config, clientId);
|
|
289
|
-
if (action === "list") {
|
|
290
|
-
controller.enqueue(
|
|
291
|
-
encoder.encode(
|
|
292
|
-
`data: ${JSON.stringify({
|
|
293
|
-
type: "tools_loading",
|
|
294
|
-
message: "Fetching tools from server..."
|
|
295
|
-
})}
|
|
296
|
-
|
|
297
|
-
`
|
|
298
|
-
)
|
|
299
|
-
);
|
|
300
|
-
const tools2 = await client.getTools();
|
|
301
|
-
const toolsWithJsonSchema = Object.fromEntries(
|
|
302
|
-
Object.entries(tools2).map(([toolName2, tool]) => {
|
|
303
|
-
return [
|
|
304
|
-
toolName2,
|
|
305
|
-
{
|
|
306
|
-
...tool,
|
|
307
|
-
inputSchema: zodToJsonSchema.zodToJsonSchema(
|
|
308
|
-
tool.inputSchema
|
|
309
|
-
)
|
|
310
|
-
}
|
|
311
|
-
];
|
|
312
|
-
})
|
|
313
|
-
);
|
|
314
|
-
controller.enqueue(
|
|
315
|
-
encoder.encode(
|
|
316
|
-
`data: ${JSON.stringify({
|
|
317
|
-
type: "tools_list",
|
|
318
|
-
tools: toolsWithJsonSchema
|
|
319
|
-
})}
|
|
320
|
-
|
|
321
|
-
`
|
|
322
|
-
)
|
|
323
|
-
);
|
|
324
|
-
} else if (action === "execute") {
|
|
325
|
-
if (!toolName) {
|
|
326
|
-
controller.enqueue(
|
|
327
|
-
encoder.encode(
|
|
328
|
-
`data: ${JSON.stringify({
|
|
329
|
-
type: "tool_error",
|
|
330
|
-
error: "Tool name is required for execution"
|
|
331
|
-
})}
|
|
332
|
-
|
|
333
|
-
`
|
|
334
|
-
)
|
|
335
|
-
);
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
controller.enqueue(
|
|
339
|
-
encoder.encode(
|
|
340
|
-
`data: ${JSON.stringify({
|
|
341
|
-
type: "tool_executing",
|
|
342
|
-
toolName,
|
|
343
|
-
parameters: parameters || {},
|
|
344
|
-
message: "Executing tool..."
|
|
345
|
-
})}
|
|
346
|
-
|
|
347
|
-
`
|
|
348
|
-
)
|
|
349
|
-
);
|
|
350
|
-
const tools2 = await client.getTools();
|
|
351
|
-
const tool = tools2[toolName];
|
|
352
|
-
if (!tool) {
|
|
353
|
-
controller.enqueue(
|
|
354
|
-
encoder.encode(
|
|
355
|
-
`data: ${JSON.stringify({
|
|
356
|
-
type: "tool_error",
|
|
357
|
-
error: `Tool '${toolName}' not found`
|
|
358
|
-
})}
|
|
359
|
-
|
|
360
|
-
`
|
|
361
|
-
)
|
|
362
|
-
);
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
const toolArgs = parameters && typeof parameters === "object" ? parameters : {};
|
|
366
|
-
const elicitationHandler = async (elicitationRequest) => {
|
|
367
|
-
const requestId2 = `elicit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
368
|
-
if (streamController && encoder) {
|
|
369
|
-
streamController.enqueue(
|
|
370
|
-
encoder.encode(
|
|
371
|
-
`data: ${JSON.stringify({
|
|
372
|
-
type: "elicitation_request",
|
|
373
|
-
requestId: requestId2,
|
|
374
|
-
message: elicitationRequest.message,
|
|
375
|
-
schema: elicitationRequest.requestedSchema,
|
|
376
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
377
|
-
})}
|
|
378
|
-
|
|
379
|
-
`
|
|
380
|
-
)
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
return new Promise((resolve, reject) => {
|
|
384
|
-
pendingElicitations$1.set(requestId2, { resolve, reject });
|
|
385
|
-
setTimeout(() => {
|
|
386
|
-
if (pendingElicitations$1.has(requestId2)) {
|
|
387
|
-
pendingElicitations$1.delete(requestId2);
|
|
388
|
-
reject(new Error("Elicitation timeout"));
|
|
389
|
-
}
|
|
390
|
-
}, 3e5);
|
|
391
|
-
});
|
|
392
|
-
};
|
|
393
|
-
if (client.elicitation && client.elicitation.onRequest) {
|
|
394
|
-
const serverName = "server";
|
|
395
|
-
client.elicitation.onRequest(serverName, elicitationHandler);
|
|
396
|
-
}
|
|
397
|
-
const result = await tool.execute({
|
|
398
|
-
context: toolArgs
|
|
399
|
-
});
|
|
400
|
-
controller.enqueue(
|
|
401
|
-
encoder.encode(
|
|
402
|
-
`data: ${JSON.stringify({
|
|
403
|
-
type: "tool_result",
|
|
404
|
-
toolName,
|
|
405
|
-
result
|
|
406
|
-
})}
|
|
407
|
-
|
|
408
|
-
`
|
|
409
|
-
)
|
|
410
|
-
);
|
|
411
|
-
controller.enqueue(
|
|
412
|
-
encoder.encode(
|
|
413
|
-
`data: ${JSON.stringify({
|
|
414
|
-
type: "elicitation_complete",
|
|
415
|
-
toolName
|
|
416
|
-
})}
|
|
417
|
-
|
|
418
|
-
`
|
|
419
|
-
)
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
controller.enqueue(encoder.encode(`data: [DONE]
|
|
423
|
-
|
|
424
|
-
`));
|
|
425
|
-
} catch (error) {
|
|
426
|
-
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
427
|
-
controller.enqueue(
|
|
428
|
-
encoder.encode(
|
|
429
|
-
`data: ${JSON.stringify({
|
|
430
|
-
type: "tool_error",
|
|
431
|
-
error: errorMsg
|
|
432
|
-
})}
|
|
433
|
-
|
|
434
|
-
`
|
|
435
|
-
)
|
|
436
|
-
);
|
|
437
|
-
} finally {
|
|
438
|
-
if (client) {
|
|
439
|
-
await client.disconnect();
|
|
440
|
-
}
|
|
441
|
-
controller.close();
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
return new Response(readableStream, {
|
|
446
|
-
headers: {
|
|
447
|
-
"Content-Type": "text/event-stream",
|
|
448
|
-
"Cache-Control": "no-cache",
|
|
449
|
-
Connection: "keep-alive"
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
} catch (error) {
|
|
453
|
-
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
454
|
-
if (client) {
|
|
455
|
-
try {
|
|
456
|
-
await client.disconnect();
|
|
457
|
-
} catch (cleanupError) {
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
return c.json(
|
|
461
|
-
{
|
|
462
|
-
success: false,
|
|
463
|
-
error: errorMsg
|
|
464
|
-
},
|
|
465
|
-
500
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
});
|
|
469
|
-
const resources = new hono.Hono();
|
|
470
|
-
resources.post("/list", async (c) => {
|
|
471
|
-
try {
|
|
472
|
-
const { serverConfig } = await c.req.json();
|
|
473
|
-
const validation = validateServerConfig(serverConfig);
|
|
474
|
-
if (!validation.success) {
|
|
475
|
-
return c.json(
|
|
476
|
-
{ success: false, error: validation.error.message },
|
|
477
|
-
validation.error.status
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
const client = createMCPClient(
|
|
481
|
-
validation.config,
|
|
482
|
-
`resources-list-${Date.now()}`
|
|
483
|
-
);
|
|
484
|
-
try {
|
|
485
|
-
const resources2 = await client.resources.list();
|
|
486
|
-
await client.disconnect();
|
|
487
|
-
return c.json({ resources: resources2 });
|
|
488
|
-
} catch (error) {
|
|
489
|
-
await client.disconnect();
|
|
490
|
-
throw error;
|
|
491
|
-
}
|
|
492
|
-
} catch (error) {
|
|
493
|
-
console.error("Error fetching resources:", error);
|
|
494
|
-
return c.json(
|
|
495
|
-
{
|
|
496
|
-
success: false,
|
|
497
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
498
|
-
},
|
|
499
|
-
500
|
|
500
|
-
);
|
|
501
|
-
}
|
|
502
|
-
});
|
|
503
|
-
resources.post("/read", async (c) => {
|
|
504
|
-
try {
|
|
505
|
-
const { serverConfig, uri } = await c.req.json();
|
|
506
|
-
const validation = validateServerConfig(serverConfig);
|
|
507
|
-
if (!validation.success) {
|
|
508
|
-
return c.json(
|
|
509
|
-
{ success: false, error: validation.error.message },
|
|
510
|
-
validation.error.status
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
if (!uri) {
|
|
514
|
-
return c.json(
|
|
515
|
-
{
|
|
516
|
-
success: false,
|
|
517
|
-
error: "Resource URI is required"
|
|
518
|
-
},
|
|
519
|
-
400
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
const client = createMCPClient(
|
|
523
|
-
validation.config,
|
|
524
|
-
`resources-read-${Date.now()}`
|
|
525
|
-
);
|
|
526
|
-
try {
|
|
527
|
-
const content = await client.resources.read("server", uri);
|
|
528
|
-
await client.disconnect();
|
|
529
|
-
return c.json({ content });
|
|
530
|
-
} catch (error) {
|
|
531
|
-
await client.disconnect();
|
|
532
|
-
throw error;
|
|
533
|
-
}
|
|
534
|
-
} catch (error) {
|
|
535
|
-
console.error("Error reading resource:", error);
|
|
536
|
-
return c.json(
|
|
537
|
-
{
|
|
538
|
-
success: false,
|
|
539
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
540
|
-
},
|
|
541
|
-
500
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
const prompts = new hono.Hono();
|
|
546
|
-
prompts.post("/list", async (c) => {
|
|
547
|
-
try {
|
|
548
|
-
const { serverConfig } = await c.req.json();
|
|
549
|
-
const validation = validateServerConfig(serverConfig);
|
|
550
|
-
if (!validation.success) {
|
|
551
|
-
return c.json(
|
|
552
|
-
{ success: false, error: validation.error.message },
|
|
553
|
-
validation.error.status
|
|
554
|
-
);
|
|
555
|
-
}
|
|
556
|
-
const client = createMCPClient(
|
|
557
|
-
validation.config,
|
|
558
|
-
`prompts-list-${Date.now()}`
|
|
559
|
-
);
|
|
560
|
-
try {
|
|
561
|
-
const prompts2 = await client.prompts.list();
|
|
562
|
-
await client.disconnect();
|
|
563
|
-
return c.json({ prompts: prompts2 });
|
|
564
|
-
} catch (error) {
|
|
565
|
-
await client.disconnect();
|
|
566
|
-
throw error;
|
|
567
|
-
}
|
|
568
|
-
} catch (error) {
|
|
569
|
-
console.error("Error fetching prompts:", error);
|
|
570
|
-
return c.json(
|
|
571
|
-
{
|
|
572
|
-
success: false,
|
|
573
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
574
|
-
},
|
|
575
|
-
500
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
});
|
|
579
|
-
prompts.post("/get", async (c) => {
|
|
580
|
-
try {
|
|
581
|
-
const { serverConfig, name, args } = await c.req.json();
|
|
582
|
-
const validation = validateServerConfig(serverConfig);
|
|
583
|
-
if (!validation.success) {
|
|
584
|
-
return c.json(
|
|
585
|
-
{ success: false, error: validation.error.message },
|
|
586
|
-
validation.error.status
|
|
587
|
-
);
|
|
588
|
-
}
|
|
589
|
-
if (!name) {
|
|
590
|
-
return c.json(
|
|
591
|
-
{
|
|
592
|
-
success: false,
|
|
593
|
-
error: "Prompt name is required"
|
|
594
|
-
},
|
|
595
|
-
400
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
const client = createMCPClient(
|
|
599
|
-
validation.config,
|
|
600
|
-
`prompts-get-${Date.now()}`
|
|
601
|
-
);
|
|
602
|
-
try {
|
|
603
|
-
const content = await client.prompts.get({
|
|
604
|
-
serverName: "server",
|
|
605
|
-
name,
|
|
606
|
-
args: args || {}
|
|
607
|
-
});
|
|
608
|
-
await client.disconnect();
|
|
609
|
-
return c.json({ content });
|
|
610
|
-
} catch (error) {
|
|
611
|
-
await client.disconnect();
|
|
612
|
-
throw error;
|
|
613
|
-
}
|
|
614
|
-
} catch (error) {
|
|
615
|
-
console.error("Error getting prompt:", error);
|
|
616
|
-
return c.json(
|
|
617
|
-
{
|
|
618
|
-
success: false,
|
|
619
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
620
|
-
},
|
|
621
|
-
500
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
const chat = new hono.Hono();
|
|
626
|
-
const pendingElicitations = /* @__PURE__ */ new Map();
|
|
627
|
-
chat.post("/", async (c) => {
|
|
628
|
-
let client = null;
|
|
629
|
-
try {
|
|
630
|
-
const requestData = await c.req.json();
|
|
631
|
-
const {
|
|
632
|
-
serverConfigs,
|
|
633
|
-
model,
|
|
634
|
-
apiKey,
|
|
635
|
-
systemPrompt,
|
|
636
|
-
messages,
|
|
637
|
-
ollamaBaseUrl,
|
|
638
|
-
action,
|
|
639
|
-
requestId,
|
|
640
|
-
response
|
|
641
|
-
} = requestData;
|
|
642
|
-
if (action === "elicitation_response") {
|
|
643
|
-
if (!requestId) {
|
|
644
|
-
return c.json(
|
|
645
|
-
{
|
|
646
|
-
success: false,
|
|
647
|
-
error: "requestId is required for elicitation_response action"
|
|
648
|
-
},
|
|
649
|
-
400
|
|
650
|
-
);
|
|
651
|
-
}
|
|
652
|
-
const pending = pendingElicitations.get(requestId);
|
|
653
|
-
if (!pending) {
|
|
654
|
-
return c.json(
|
|
655
|
-
{
|
|
656
|
-
success: false,
|
|
657
|
-
error: "No pending elicitation found for this requestId"
|
|
658
|
-
},
|
|
659
|
-
404
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
pending.resolve(response);
|
|
663
|
-
pendingElicitations.delete(requestId);
|
|
664
|
-
return c.json({ success: true });
|
|
665
|
-
}
|
|
666
|
-
if (!model || !model.id || !apiKey || !messages) {
|
|
667
|
-
return c.json(
|
|
668
|
-
{
|
|
669
|
-
success: false,
|
|
670
|
-
error: "model (with id), apiKey, and messages are required"
|
|
671
|
-
},
|
|
672
|
-
400
|
|
673
|
-
);
|
|
674
|
-
}
|
|
675
|
-
if (serverConfigs && Object.keys(serverConfigs).length > 0) {
|
|
676
|
-
const validation = validateMultipleServerConfigs(serverConfigs);
|
|
677
|
-
if (!validation.success) {
|
|
678
|
-
return c.json(
|
|
679
|
-
{
|
|
680
|
-
success: false,
|
|
681
|
-
error: validation.error.message,
|
|
682
|
-
details: validation.errors
|
|
683
|
-
},
|
|
684
|
-
validation.error.status
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
client = createMCPClientWithMultipleConnections(validation.validConfigs);
|
|
688
|
-
} else {
|
|
689
|
-
client = new mcp$1.MCPClient({
|
|
690
|
-
id: `chat-${Date.now()}`,
|
|
691
|
-
servers: {}
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
const tools2 = await client.getTools();
|
|
695
|
-
const llmModel = getLlmModel(model, apiKey, ollamaBaseUrl);
|
|
696
|
-
let toolCallId = 0;
|
|
697
|
-
let streamController = null;
|
|
698
|
-
let encoder = null;
|
|
699
|
-
const elicitationHandler = async (elicitationRequest) => {
|
|
700
|
-
const requestId2 = `elicit_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
701
|
-
if (streamController && encoder) {
|
|
702
|
-
streamController.enqueue(
|
|
703
|
-
encoder.encode(
|
|
704
|
-
`data: ${JSON.stringify({
|
|
705
|
-
type: "elicitation_request",
|
|
706
|
-
requestId: requestId2,
|
|
707
|
-
message: elicitationRequest.message,
|
|
708
|
-
schema: elicitationRequest.requestedSchema,
|
|
709
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
710
|
-
})}
|
|
711
|
-
|
|
712
|
-
`
|
|
713
|
-
)
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
return new Promise((resolve, reject) => {
|
|
717
|
-
pendingElicitations.set(requestId2, { resolve, reject });
|
|
718
|
-
setTimeout(() => {
|
|
719
|
-
if (pendingElicitations.has(requestId2)) {
|
|
720
|
-
pendingElicitations.delete(requestId2);
|
|
721
|
-
reject(new Error("Elicitation timeout"));
|
|
722
|
-
}
|
|
723
|
-
}, 3e5);
|
|
724
|
-
});
|
|
725
|
-
};
|
|
726
|
-
if (client.elicitation && client.elicitation.onRequest && serverConfigs) {
|
|
727
|
-
for (const serverName of Object.keys(serverConfigs)) {
|
|
728
|
-
const normalizedName = serverName.toLowerCase().replace(/[\s\-]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
729
|
-
client.elicitation.onRequest(normalizedName, elicitationHandler);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
const originalTools = tools2 && Object.keys(tools2).length > 0 ? tools2 : {};
|
|
733
|
-
const wrappedTools = {};
|
|
734
|
-
for (const [name, tool] of Object.entries(originalTools)) {
|
|
735
|
-
wrappedTools[name] = {
|
|
736
|
-
...tool,
|
|
737
|
-
execute: async (params) => {
|
|
738
|
-
const currentToolCallId = ++toolCallId;
|
|
739
|
-
if (streamController && encoder) {
|
|
740
|
-
streamController.enqueue(
|
|
741
|
-
encoder.encode(
|
|
742
|
-
`data: ${JSON.stringify({
|
|
743
|
-
type: "tool_call",
|
|
744
|
-
toolCall: {
|
|
745
|
-
id: currentToolCallId,
|
|
746
|
-
name,
|
|
747
|
-
parameters: params,
|
|
748
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
749
|
-
status: "executing"
|
|
750
|
-
}
|
|
751
|
-
})}
|
|
752
|
-
|
|
753
|
-
`
|
|
754
|
-
)
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
try {
|
|
758
|
-
const result = await tool.execute(params);
|
|
759
|
-
if (streamController && encoder) {
|
|
760
|
-
streamController.enqueue(
|
|
761
|
-
encoder.encode(
|
|
762
|
-
`data: ${JSON.stringify({
|
|
763
|
-
type: "tool_result",
|
|
764
|
-
toolResult: {
|
|
765
|
-
id: currentToolCallId,
|
|
766
|
-
toolCallId: currentToolCallId,
|
|
767
|
-
result,
|
|
768
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
769
|
-
}
|
|
770
|
-
})}
|
|
771
|
-
|
|
772
|
-
`
|
|
773
|
-
)
|
|
774
|
-
);
|
|
775
|
-
}
|
|
776
|
-
return result;
|
|
777
|
-
} catch (error) {
|
|
778
|
-
if (streamController && encoder) {
|
|
779
|
-
streamController.enqueue(
|
|
780
|
-
encoder.encode(
|
|
781
|
-
`data: ${JSON.stringify({
|
|
782
|
-
type: "tool_result",
|
|
783
|
-
toolResult: {
|
|
784
|
-
id: currentToolCallId,
|
|
785
|
-
toolCallId: currentToolCallId,
|
|
786
|
-
error: error instanceof Error ? error.message : String(error),
|
|
787
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
788
|
-
}
|
|
789
|
-
})}
|
|
790
|
-
|
|
791
|
-
`
|
|
792
|
-
)
|
|
793
|
-
);
|
|
794
|
-
}
|
|
795
|
-
throw error;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
const agent$1 = new agent.Agent({
|
|
801
|
-
name: "MCP Chat Agent",
|
|
802
|
-
instructions: systemPrompt || "You are a helpful assistant with access to MCP tools.",
|
|
803
|
-
model: llmModel,
|
|
804
|
-
tools: Object.keys(wrappedTools).length > 0 ? wrappedTools : void 0
|
|
805
|
-
});
|
|
806
|
-
const formattedMessages = messages.map((msg) => ({
|
|
807
|
-
role: msg.role,
|
|
808
|
-
content: msg.content
|
|
809
|
-
}));
|
|
810
|
-
const stream = await agent$1.stream(formattedMessages, {
|
|
811
|
-
maxSteps: 10
|
|
812
|
-
// Allow up to 10 steps for tool usage
|
|
813
|
-
});
|
|
814
|
-
encoder = new TextEncoder();
|
|
815
|
-
const readableStream = new ReadableStream({
|
|
816
|
-
async start(controller) {
|
|
817
|
-
streamController = controller;
|
|
818
|
-
try {
|
|
819
|
-
let hasContent = false;
|
|
820
|
-
for await (const chunk of stream.textStream) {
|
|
821
|
-
if (chunk && chunk.trim()) {
|
|
822
|
-
hasContent = true;
|
|
823
|
-
controller.enqueue(
|
|
824
|
-
encoder.encode(
|
|
825
|
-
`data: ${JSON.stringify({ type: "text", content: chunk })}
|
|
826
|
-
|
|
827
|
-
`
|
|
828
|
-
)
|
|
829
|
-
);
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
if (!hasContent) {
|
|
833
|
-
controller.enqueue(
|
|
834
|
-
encoder.encode(
|
|
835
|
-
`data: ${JSON.stringify({ type: "text", content: "I apologize, but I couldn't generate a response. Please try again." })}
|
|
836
|
-
|
|
837
|
-
`
|
|
838
|
-
)
|
|
839
|
-
);
|
|
840
|
-
}
|
|
841
|
-
controller.enqueue(
|
|
842
|
-
encoder.encode(
|
|
843
|
-
`data: ${JSON.stringify({
|
|
844
|
-
type: "elicitation_complete"
|
|
845
|
-
})}
|
|
846
|
-
|
|
847
|
-
`
|
|
848
|
-
)
|
|
849
|
-
);
|
|
850
|
-
controller.enqueue(encoder.encode(`data: [DONE]
|
|
851
|
-
|
|
852
|
-
`));
|
|
853
|
-
} catch (error) {
|
|
854
|
-
console.error("Streaming error:", error);
|
|
855
|
-
controller.enqueue(
|
|
856
|
-
encoder.encode(
|
|
857
|
-
`data: ${JSON.stringify({
|
|
858
|
-
type: "error",
|
|
859
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
860
|
-
})}
|
|
861
|
-
|
|
862
|
-
`
|
|
863
|
-
)
|
|
864
|
-
);
|
|
865
|
-
} finally {
|
|
866
|
-
if (client) {
|
|
867
|
-
try {
|
|
868
|
-
await client.disconnect();
|
|
869
|
-
} catch (cleanupError) {
|
|
870
|
-
console.warn(
|
|
871
|
-
"Error cleaning up MCP client after streaming:",
|
|
872
|
-
cleanupError
|
|
873
|
-
);
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
controller.close();
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
});
|
|
880
|
-
return new Response(readableStream, {
|
|
881
|
-
headers: {
|
|
882
|
-
"Content-Type": "text/event-stream",
|
|
883
|
-
"Cache-Control": "no-cache",
|
|
884
|
-
Connection: "keep-alive"
|
|
885
|
-
}
|
|
886
|
-
});
|
|
887
|
-
} catch (error) {
|
|
888
|
-
console.error("Error in chat API:", error);
|
|
889
|
-
if (client) {
|
|
890
|
-
try {
|
|
891
|
-
await client.disconnect();
|
|
892
|
-
} catch (cleanupError) {
|
|
893
|
-
console.warn("Error cleaning up MCP client after error:", cleanupError);
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
return c.json(
|
|
897
|
-
{
|
|
898
|
-
success: false,
|
|
899
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
900
|
-
},
|
|
901
|
-
500
|
|
902
|
-
);
|
|
903
|
-
}
|
|
904
|
-
});
|
|
905
|
-
const getLlmModel = (modelDefinition, apiKey, ollamaBaseUrl) => {
|
|
906
|
-
if (!modelDefinition || !modelDefinition.id || !modelDefinition.provider) {
|
|
907
|
-
throw new Error(
|
|
908
|
-
`Invalid model definition: ${JSON.stringify(modelDefinition)}`
|
|
909
|
-
);
|
|
910
|
-
}
|
|
911
|
-
switch (modelDefinition.provider) {
|
|
912
|
-
case "anthropic":
|
|
913
|
-
return anthropic.createAnthropic({ apiKey })(modelDefinition.id);
|
|
914
|
-
case "openai":
|
|
915
|
-
return openai.createOpenAI({ apiKey })(modelDefinition.id);
|
|
916
|
-
case "ollama":
|
|
917
|
-
const baseUrl = ollamaBaseUrl || "http://localhost:11434";
|
|
918
|
-
return ollamaAiProvider.createOllama({
|
|
919
|
-
baseURL: `${baseUrl}/api`
|
|
920
|
-
// Configurable Ollama API endpoint
|
|
921
|
-
})(modelDefinition.id, {
|
|
922
|
-
simulateStreaming: true
|
|
923
|
-
// Enable streaming for Ollama models
|
|
924
|
-
});
|
|
925
|
-
default:
|
|
926
|
-
throw new Error(
|
|
927
|
-
`Unsupported provider: ${modelDefinition.provider} for model: ${modelDefinition.id}`
|
|
928
|
-
);
|
|
929
|
-
}
|
|
930
|
-
};
|
|
931
|
-
const oauth = new hono.Hono();
|
|
932
|
-
oauth.get("/metadata", async (c) => {
|
|
933
|
-
try {
|
|
934
|
-
const url = c.req.query("url");
|
|
935
|
-
if (!url) {
|
|
936
|
-
return c.json({ error: "Missing url parameter" }, 400);
|
|
937
|
-
}
|
|
938
|
-
let metadataUrl;
|
|
939
|
-
try {
|
|
940
|
-
metadataUrl = new URL(url);
|
|
941
|
-
if (metadataUrl.protocol !== "https:") {
|
|
942
|
-
return c.json({ error: "Only HTTPS URLs are allowed" }, 400);
|
|
943
|
-
}
|
|
944
|
-
} catch (error) {
|
|
945
|
-
return c.json({ error: "Invalid URL format" }, 400);
|
|
946
|
-
}
|
|
947
|
-
const response = await fetch(metadataUrl.toString(), {
|
|
948
|
-
method: "GET",
|
|
949
|
-
headers: {
|
|
950
|
-
Accept: "application/json",
|
|
951
|
-
"User-Agent": "MCP-Inspector/1.0"
|
|
952
|
-
}
|
|
953
|
-
});
|
|
954
|
-
if (!response.ok) {
|
|
955
|
-
return c.json(
|
|
956
|
-
{
|
|
957
|
-
error: `Failed to fetch OAuth metadata: ${response.status} ${response.statusText}`
|
|
958
|
-
},
|
|
959
|
-
response.status
|
|
960
|
-
);
|
|
961
|
-
}
|
|
962
|
-
const metadata = await response.json();
|
|
963
|
-
return c.json(metadata);
|
|
964
|
-
} catch (error) {
|
|
965
|
-
console.error("OAuth metadata proxy error:", error);
|
|
966
|
-
return c.json(
|
|
967
|
-
{
|
|
968
|
-
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
969
|
-
},
|
|
970
|
-
500
|
|
971
|
-
);
|
|
972
|
-
}
|
|
973
|
-
});
|
|
974
|
-
const mcp = new hono.Hono();
|
|
975
|
-
mcp.get("/health", (c) => {
|
|
976
|
-
return c.json({
|
|
977
|
-
service: "MCP API",
|
|
978
|
-
status: "ready",
|
|
979
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
980
|
-
});
|
|
981
|
-
});
|
|
982
|
-
mcp.route("/chat", chat);
|
|
983
|
-
mcp.route("/connect", connect);
|
|
984
|
-
mcp.route("/tools", tools);
|
|
985
|
-
mcp.route("/resources", resources);
|
|
986
|
-
mcp.route("/prompts", prompts);
|
|
987
|
-
mcp.route("/oauth", oauth);
|
|
988
|
-
function createHonoApp() {
|
|
989
|
-
const app = new hono.Hono();
|
|
990
|
-
app.use("*", logger.logger());
|
|
991
|
-
app.use(
|
|
992
|
-
"*",
|
|
993
|
-
cors.cors({
|
|
994
|
-
origin: [
|
|
995
|
-
"http://localhost:8080",
|
|
996
|
-
"http://localhost:3000",
|
|
997
|
-
"http://localhost:3001",
|
|
998
|
-
"http://127.0.0.1:3001",
|
|
999
|
-
"http://127.0.0.1:3000"
|
|
1000
|
-
],
|
|
1001
|
-
credentials: true
|
|
1002
|
-
})
|
|
1003
|
-
);
|
|
1004
|
-
app.route("/api/mcp", mcp);
|
|
1005
|
-
app.get("/health", (c) => {
|
|
1006
|
-
return c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1007
|
-
});
|
|
1008
|
-
const isElectron = process.env.ELECTRON_APP === "true";
|
|
1009
|
-
if (process.env.NODE_ENV === "production" || isElectron) {
|
|
1010
|
-
app.use("/*", serveStatic.serveStatic({ root: "./dist/client" }));
|
|
1011
|
-
app.get("*", (c) => {
|
|
1012
|
-
const path2 = c.req.path;
|
|
1013
|
-
if (path2.startsWith("/api/")) {
|
|
1014
|
-
return c.notFound();
|
|
1015
|
-
}
|
|
1016
|
-
return serveStatic.serveStatic({ path: "./dist/client/index.html" })(c);
|
|
1017
|
-
});
|
|
1018
|
-
} else {
|
|
1019
|
-
app.get("/", (c) => {
|
|
1020
|
-
return c.json({
|
|
1021
|
-
message: "MCP Inspector API Server",
|
|
1022
|
-
environment: "development",
|
|
1023
|
-
frontend: "http://localhost:8080"
|
|
1024
|
-
});
|
|
1025
|
-
});
|
|
1026
|
-
}
|
|
1027
|
-
return app;
|
|
1028
|
-
}
|
|
1029
|
-
function registerAppListeners(mainWindow2) {
|
|
1030
|
-
electron.ipcMain.handle("app:version", () => {
|
|
1031
|
-
return electron.app.getVersion();
|
|
1032
|
-
});
|
|
1033
|
-
electron.ipcMain.handle("app:platform", () => {
|
|
1034
|
-
return process.platform;
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
function registerWindowListeners(mainWindow2) {
|
|
1038
|
-
electron.ipcMain.on("window:minimize", () => {
|
|
1039
|
-
mainWindow2.minimize();
|
|
1040
|
-
});
|
|
1041
|
-
electron.ipcMain.on("window:maximize", () => {
|
|
1042
|
-
if (mainWindow2.isMaximized()) {
|
|
1043
|
-
mainWindow2.unmaximize();
|
|
1044
|
-
} else {
|
|
1045
|
-
mainWindow2.maximize();
|
|
1046
|
-
}
|
|
1047
|
-
});
|
|
1048
|
-
electron.ipcMain.on("window:close", () => {
|
|
1049
|
-
mainWindow2.close();
|
|
1050
|
-
});
|
|
1051
|
-
electron.ipcMain.handle("window:is-maximized", () => {
|
|
1052
|
-
return mainWindow2.isMaximized();
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
function registerFileListeners(mainWindow2) {
|
|
1056
|
-
electron.ipcMain.handle("dialog:open", async (event, options = {}) => {
|
|
1057
|
-
const result = await electron.dialog.showOpenDialog(mainWindow2, {
|
|
1058
|
-
properties: ["openFile"],
|
|
1059
|
-
filters: [
|
|
1060
|
-
{ name: "JSON Files", extensions: ["json"] },
|
|
1061
|
-
{ name: "All Files", extensions: ["*"] }
|
|
1062
|
-
],
|
|
1063
|
-
...options
|
|
1064
|
-
});
|
|
1065
|
-
return result.canceled ? void 0 : result.filePaths;
|
|
1066
|
-
});
|
|
1067
|
-
electron.ipcMain.handle("dialog:save", async (event, data) => {
|
|
1068
|
-
const result = await electron.dialog.showSaveDialog(mainWindow2, {
|
|
1069
|
-
filters: [
|
|
1070
|
-
{ name: "JSON Files", extensions: ["json"] },
|
|
1071
|
-
{ name: "All Files", extensions: ["*"] }
|
|
1072
|
-
]
|
|
1073
|
-
});
|
|
1074
|
-
if (!result.canceled && result.filePath) {
|
|
1075
|
-
try {
|
|
1076
|
-
await fs.writeFile(result.filePath, JSON.stringify(data, null, 2));
|
|
1077
|
-
return result.filePath;
|
|
1078
|
-
} catch (error) {
|
|
1079
|
-
throw new Error(`Failed to save file: ${error}`);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
return void 0;
|
|
1083
|
-
});
|
|
1084
|
-
electron.ipcMain.handle("dialog:message", async (event, options) => {
|
|
1085
|
-
return await electron.dialog.showMessageBox(mainWindow2, options);
|
|
1086
|
-
});
|
|
1087
|
-
}
|
|
1088
|
-
function registerListeners(mainWindow2) {
|
|
1089
|
-
registerAppListeners();
|
|
1090
|
-
registerWindowListeners(mainWindow2);
|
|
1091
|
-
registerFileListeners(mainWindow2);
|
|
1092
|
-
}
|
|
1093
|
-
log.transports.file.level = "info";
|
|
1094
|
-
log.transports.console.level = "debug";
|
|
1095
|
-
updateElectronApp.updateElectronApp();
|
|
1096
|
-
if (process.platform === "win32") {
|
|
1097
|
-
electron.app.setAppUserModelId("com.mcpjam.inspector");
|
|
1098
|
-
}
|
|
1099
|
-
let mainWindow = null;
|
|
1100
|
-
let server = null;
|
|
1101
|
-
let serverPort = 0;
|
|
1102
|
-
const isDev = process.env.NODE_ENV === "development";
|
|
1103
|
-
async function findAvailablePort(startPort = 3001) {
|
|
1104
|
-
return new Promise((resolve, reject) => {
|
|
1105
|
-
const net = require("net");
|
|
1106
|
-
const server2 = net.createServer();
|
|
1107
|
-
server2.listen(startPort, () => {
|
|
1108
|
-
var _a;
|
|
1109
|
-
const port = (_a = server2.address()) == null ? void 0 : _a.port;
|
|
1110
|
-
server2.close(() => {
|
|
1111
|
-
resolve(port);
|
|
1112
|
-
});
|
|
1113
|
-
});
|
|
1114
|
-
server2.on("error", () => {
|
|
1115
|
-
findAvailablePort(startPort + 1).then(resolve).catch(reject);
|
|
1116
|
-
});
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
async function startHonoServer() {
|
|
1120
|
-
try {
|
|
1121
|
-
const port = await findAvailablePort(3001);
|
|
1122
|
-
process.env.ELECTRON_APP = "true";
|
|
1123
|
-
const honoApp = createHonoApp();
|
|
1124
|
-
server = nodeServer.serve({
|
|
1125
|
-
fetch: honoApp.fetch,
|
|
1126
|
-
port,
|
|
1127
|
-
hostname: "127.0.0.1"
|
|
1128
|
-
});
|
|
1129
|
-
log.info(`🚀 MCP Inspector Server started on port ${port}`);
|
|
1130
|
-
return port;
|
|
1131
|
-
} catch (error) {
|
|
1132
|
-
log.error("Failed to start Hono server:", error);
|
|
1133
|
-
throw error;
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
function createMainWindow(serverUrl) {
|
|
1137
|
-
const window = new electron.BrowserWindow({
|
|
1138
|
-
width: 1400,
|
|
1139
|
-
height: 900,
|
|
1140
|
-
minWidth: 800,
|
|
1141
|
-
minHeight: 600,
|
|
1142
|
-
icon: path.join(__dirname, "../assets/icon.png"),
|
|
1143
|
-
// You can add an icon later
|
|
1144
|
-
webPreferences: {
|
|
1145
|
-
nodeIntegration: false,
|
|
1146
|
-
contextIsolation: true,
|
|
1147
|
-
enableRemoteModule: false,
|
|
1148
|
-
preload: path.join(__dirname, "../preload/index.js")
|
|
1149
|
-
},
|
|
1150
|
-
titleBarStyle: process.platform === "darwin" ? "hiddenInset" : "default",
|
|
1151
|
-
show: false
|
|
1152
|
-
// Don't show until ready
|
|
1153
|
-
});
|
|
1154
|
-
registerListeners(window);
|
|
1155
|
-
window.loadURL(serverUrl);
|
|
1156
|
-
if (isDev) {
|
|
1157
|
-
window.webContents.openDevTools();
|
|
1158
|
-
}
|
|
1159
|
-
window.once("ready-to-show", () => {
|
|
1160
|
-
window.show();
|
|
1161
|
-
if (isDev) {
|
|
1162
|
-
window.webContents.openDevTools();
|
|
1163
|
-
}
|
|
1164
|
-
});
|
|
1165
|
-
window.webContents.setWindowOpenHandler(({ url }) => {
|
|
1166
|
-
electron.shell.openExternal(url);
|
|
1167
|
-
return { action: "deny" };
|
|
1168
|
-
});
|
|
1169
|
-
window.on("closed", () => {
|
|
1170
|
-
mainWindow = null;
|
|
1171
|
-
});
|
|
1172
|
-
return window;
|
|
1173
|
-
}
|
|
1174
|
-
function createAppMenu() {
|
|
1175
|
-
const isMac = process.platform === "darwin";
|
|
1176
|
-
const template = [
|
|
1177
|
-
...isMac ? [{
|
|
1178
|
-
label: electron.app.getName(),
|
|
1179
|
-
submenu: [
|
|
1180
|
-
{ role: "about" },
|
|
1181
|
-
{ type: "separator" },
|
|
1182
|
-
{ role: "services" },
|
|
1183
|
-
{ type: "separator" },
|
|
1184
|
-
{ role: "hide" },
|
|
1185
|
-
{ role: "hideothers" },
|
|
1186
|
-
{ role: "unhide" },
|
|
1187
|
-
{ type: "separator" },
|
|
1188
|
-
{ role: "quit" }
|
|
1189
|
-
]
|
|
1190
|
-
}] : [],
|
|
1191
|
-
{
|
|
1192
|
-
label: "File",
|
|
1193
|
-
submenu: [
|
|
1194
|
-
isMac ? { role: "close" } : { role: "quit" }
|
|
1195
|
-
]
|
|
1196
|
-
},
|
|
1197
|
-
{
|
|
1198
|
-
label: "Edit",
|
|
1199
|
-
submenu: [
|
|
1200
|
-
{ role: "undo" },
|
|
1201
|
-
{ role: "redo" },
|
|
1202
|
-
{ type: "separator" },
|
|
1203
|
-
{ role: "cut" },
|
|
1204
|
-
{ role: "copy" },
|
|
1205
|
-
{ role: "paste" },
|
|
1206
|
-
...isMac ? [
|
|
1207
|
-
{ role: "pasteAndMatchStyle" },
|
|
1208
|
-
{ role: "delete" },
|
|
1209
|
-
{ role: "selectAll" },
|
|
1210
|
-
{ type: "separator" },
|
|
1211
|
-
{
|
|
1212
|
-
label: "Speech",
|
|
1213
|
-
submenu: [
|
|
1214
|
-
{ role: "startSpeaking" },
|
|
1215
|
-
{ role: "stopSpeaking" }
|
|
1216
|
-
]
|
|
1217
|
-
}
|
|
1218
|
-
] : [
|
|
1219
|
-
{ role: "delete" },
|
|
1220
|
-
{ type: "separator" },
|
|
1221
|
-
{ role: "selectAll" }
|
|
1222
|
-
]
|
|
1223
|
-
]
|
|
1224
|
-
},
|
|
1225
|
-
{
|
|
1226
|
-
label: "View",
|
|
1227
|
-
submenu: [
|
|
1228
|
-
{ role: "reload" },
|
|
1229
|
-
{ role: "forceReload" },
|
|
1230
|
-
{ role: "toggleDevTools" },
|
|
1231
|
-
{ type: "separator" },
|
|
1232
|
-
{ role: "resetZoom" },
|
|
1233
|
-
{ role: "zoomIn" },
|
|
1234
|
-
{ role: "zoomOut" },
|
|
1235
|
-
{ type: "separator" },
|
|
1236
|
-
{ role: "togglefullscreen" }
|
|
1237
|
-
]
|
|
1238
|
-
},
|
|
1239
|
-
{
|
|
1240
|
-
label: "Window",
|
|
1241
|
-
submenu: [
|
|
1242
|
-
{ role: "minimize" },
|
|
1243
|
-
{ role: "close" },
|
|
1244
|
-
...isMac ? [
|
|
1245
|
-
{ type: "separator" },
|
|
1246
|
-
{ role: "front" },
|
|
1247
|
-
{ type: "separator" },
|
|
1248
|
-
{ role: "window" }
|
|
1249
|
-
] : []
|
|
1250
|
-
]
|
|
1251
|
-
}
|
|
1252
|
-
];
|
|
1253
|
-
const menu = electron.Menu.buildFromTemplate(template);
|
|
1254
|
-
electron.Menu.setApplicationMenu(menu);
|
|
1255
|
-
}
|
|
1256
|
-
electron.app.whenReady().then(async () => {
|
|
1257
|
-
try {
|
|
1258
|
-
serverPort = await startHonoServer();
|
|
1259
|
-
const serverUrl = `http://127.0.0.1:${serverPort}`;
|
|
1260
|
-
createAppMenu();
|
|
1261
|
-
mainWindow = createMainWindow(serverUrl);
|
|
1262
|
-
log.info("MCP Inspector Electron app ready");
|
|
1263
|
-
} catch (error) {
|
|
1264
|
-
log.error("Failed to initialize app:", error);
|
|
1265
|
-
electron.app.quit();
|
|
1266
|
-
}
|
|
1267
|
-
});
|
|
1268
|
-
electron.app.on("window-all-closed", () => {
|
|
1269
|
-
var _a;
|
|
1270
|
-
if (server) {
|
|
1271
|
-
(_a = server.close) == null ? void 0 : _a.call(server);
|
|
1272
|
-
}
|
|
1273
|
-
if (process.platform !== "darwin") {
|
|
1274
|
-
electron.app.quit();
|
|
1275
|
-
}
|
|
1276
|
-
});
|
|
1277
|
-
electron.app.on("activate", async () => {
|
|
1278
|
-
if (electron.BrowserWindow.getAllWindows().length === 0) {
|
|
1279
|
-
if (serverPort > 0) {
|
|
1280
|
-
const serverUrl = `http://127.0.0.1:${serverPort}`;
|
|
1281
|
-
mainWindow = createMainWindow(serverUrl);
|
|
1282
|
-
} else {
|
|
1283
|
-
try {
|
|
1284
|
-
serverPort = await startHonoServer();
|
|
1285
|
-
const serverUrl = `http://127.0.0.1:${serverPort}`;
|
|
1286
|
-
mainWindow = createMainWindow(serverUrl);
|
|
1287
|
-
} catch (error) {
|
|
1288
|
-
log.error("Failed to restart server:", error);
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
});
|
|
1293
|
-
electron.app.on("web-contents-created", (_, contents) => {
|
|
1294
|
-
contents.on("new-window", (navigationEvent, navigationUrl) => {
|
|
1295
|
-
navigationEvent.preventDefault();
|
|
1296
|
-
electron.shell.openExternal(navigationUrl);
|
|
1297
|
-
});
|
|
1298
|
-
});
|
|
1299
|
-
electron.app.on("before-quit", () => {
|
|
1300
|
-
var _a;
|
|
1301
|
-
if (server) {
|
|
1302
|
-
(_a = server.close) == null ? void 0 : _a.call(server);
|
|
1303
|
-
}
|
|
1304
|
-
});
|
|
1305
|
-
const gotTheLock = electron.app.requestSingleInstanceLock();
|
|
1306
|
-
if (!gotTheLock) {
|
|
1307
|
-
electron.app.quit();
|
|
1308
|
-
} else {
|
|
1309
|
-
electron.app.on("second-instance", () => {
|
|
1310
|
-
if (mainWindow) {
|
|
1311
|
-
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
1312
|
-
mainWindow.focus();
|
|
1313
|
-
}
|
|
1314
|
-
});
|
|
1315
|
-
}
|