@mcp-use/inspector 0.3.1 → 0.3.2

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.
@@ -0,0 +1,482 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
27
+
28
+ // src/cli/inspect.ts
29
+ var import_node_server = require("@hono/node-server");
30
+ var import_hono2 = require("hono");
31
+ var import_cors2 = require("hono/cors");
32
+ var import_logger = require("hono/logger");
33
+ var import_node_fs = require("fs");
34
+ var import_node_path = require("path");
35
+ var import_node_url = require("url");
36
+ var import_node_child_process = require("child_process");
37
+ var import_node_util = require("util");
38
+
39
+ // src/server/favicon-proxy.ts
40
+ var import_hono = require("hono");
41
+ var import_cors = require("hono/cors");
42
+ var app = new import_hono.Hono();
43
+ app.use("*", (0, import_cors.cors)());
44
+ var faviconCache = /* @__PURE__ */ new Map();
45
+ var CACHE_TTL = 24 * 60 * 60 * 1e3;
46
+ var MAX_CACHE_SIZE = 1e3;
47
+ function cleanupCache() {
48
+ const now = Date.now();
49
+ for (const [key, entry] of faviconCache.entries()) {
50
+ if (now - entry.timestamp > entry.ttl) {
51
+ faviconCache.delete(key);
52
+ }
53
+ }
54
+ }
55
+ setInterval(cleanupCache, 60 * 60 * 1e3);
56
+ function getCacheKey(url) {
57
+ try {
58
+ const urlObj = new URL(url);
59
+ return urlObj.hostname.toLowerCase();
60
+ } catch {
61
+ return url.toLowerCase();
62
+ }
63
+ }
64
+ app.get("/:url", async (c) => {
65
+ const url = c.req.param("url");
66
+ if (!url) {
67
+ return c.json({ error: "URL parameter is required" }, 400);
68
+ }
69
+ try {
70
+ const decodedUrl = decodeURIComponent(url);
71
+ let fullUrl = decodedUrl;
72
+ if (!decodedUrl.startsWith("http://") && !decodedUrl.startsWith("https://")) {
73
+ fullUrl = `https://${decodedUrl}`;
74
+ }
75
+ const urlObj = new URL(fullUrl);
76
+ const cacheKey = getCacheKey(fullUrl);
77
+ const cachedEntry = faviconCache.get(cacheKey);
78
+ if (cachedEntry && Date.now() - cachedEntry.timestamp < cachedEntry.ttl) {
79
+ return new Response(cachedEntry.data, {
80
+ headers: {
81
+ "Content-Type": cachedEntry.contentType,
82
+ "Cache-Control": "public, max-age=86400, immutable",
83
+ // Cache for 24 hours
84
+ "Access-Control-Allow-Origin": "*",
85
+ "X-Cache": "HIT"
86
+ }
87
+ });
88
+ }
89
+ const protocol = urlObj.protocol;
90
+ const baseDomain = `${protocol}//${urlObj.origin.split(".").slice(-2).join(".")}`;
91
+ const faviconUrls = [
92
+ `${urlObj.origin}/favicon.ico`,
93
+ `${urlObj.origin}/favicon.png`,
94
+ `${urlObj.origin}/apple-touch-icon.png`,
95
+ `${urlObj.origin}/icon.png`,
96
+ `${baseDomain}/favicon.ico`,
97
+ `${baseDomain}/favicon.png`,
98
+ `${baseDomain}/apple-touch-icon.png`,
99
+ `${baseDomain}/icon.png`
100
+ ];
101
+ for (const faviconUrl of faviconUrls) {
102
+ try {
103
+ const response = await fetch(faviconUrl, {
104
+ headers: {
105
+ "User-Agent": "Mozilla/5.0 (compatible; MCP-Inspector/1.0)"
106
+ }
107
+ });
108
+ if (response.ok) {
109
+ const contentType = response.headers.get("content-type") || "image/x-icon";
110
+ const buffer = await response.arrayBuffer();
111
+ if (faviconCache.size >= MAX_CACHE_SIZE) {
112
+ const entries = Array.from(faviconCache.entries());
113
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
114
+ const toRemove = entries.slice(0, Math.floor(MAX_CACHE_SIZE / 4));
115
+ toRemove.forEach(([key]) => faviconCache.delete(key));
116
+ }
117
+ faviconCache.set(cacheKey, {
118
+ data: buffer,
119
+ contentType,
120
+ timestamp: Date.now(),
121
+ ttl: CACHE_TTL
122
+ });
123
+ return new Response(buffer, {
124
+ headers: {
125
+ "Content-Type": contentType,
126
+ "Cache-Control": "public, max-age=86400, immutable",
127
+ // Cache for 24 hours
128
+ "Access-Control-Allow-Origin": "*",
129
+ "X-Cache": "MISS"
130
+ }
131
+ });
132
+ }
133
+ } catch {
134
+ continue;
135
+ }
136
+ }
137
+ return c.json({ error: "No favicon found" }, 404);
138
+ } catch (error) {
139
+ console.error("Favicon proxy error:", error);
140
+ return c.json({ error: "Invalid URL or fetch failed" }, 400);
141
+ }
142
+ });
143
+ app.get("/health", (c) => {
144
+ return c.json({ status: "ok", service: "favicon-proxy" });
145
+ });
146
+ var favicon_proxy_default = app;
147
+
148
+ // src/server/mcp-inspector.ts
149
+ var import_mcp_use = require("mcp-use");
150
+ var MCPInspector = class {
151
+ constructor() {
152
+ __publicField(this, "servers", /* @__PURE__ */ new Map());
153
+ __publicField(this, "client");
154
+ this.client = new import_mcp_use.MCPClient();
155
+ }
156
+ async listServers() {
157
+ return Array.from(this.servers.values());
158
+ }
159
+ async connectToServer(url, command) {
160
+ const id = Date.now().toString();
161
+ const name = url || command || "Unknown Server";
162
+ try {
163
+ const serverName = `server_${id}`;
164
+ const serverConfig = url ? { url } : { command };
165
+ this.client.addServer(serverName, serverConfig);
166
+ const session = await this.client.createSession(serverName, true);
167
+ const tools = [
168
+ {
169
+ name: "get_weather",
170
+ description: "Get current weather for a location",
171
+ inputSchema: {
172
+ type: "object",
173
+ properties: {
174
+ location: { type: "string", description: "City name" }
175
+ },
176
+ required: ["location"]
177
+ }
178
+ }
179
+ ];
180
+ const resources = [
181
+ {
182
+ uri: "file:///home/user/documents",
183
+ name: "Documents",
184
+ description: "User documents directory",
185
+ mimeType: "application/x-directory"
186
+ }
187
+ ];
188
+ const server = {
189
+ id,
190
+ name,
191
+ url,
192
+ command,
193
+ status: "connected",
194
+ session,
195
+ tools,
196
+ resources,
197
+ lastActivity: /* @__PURE__ */ new Date()
198
+ };
199
+ this.servers.set(id, server);
200
+ return server;
201
+ } catch (error) {
202
+ const server = {
203
+ id,
204
+ name,
205
+ url,
206
+ command,
207
+ status: "error",
208
+ tools: [],
209
+ resources: [],
210
+ lastActivity: /* @__PURE__ */ new Date()
211
+ };
212
+ this.servers.set(id, server);
213
+ throw error;
214
+ }
215
+ }
216
+ async getServer(id) {
217
+ return this.servers.get(id) || null;
218
+ }
219
+ async executeTool(serverId, toolName, input) {
220
+ const server = this.servers.get(serverId);
221
+ if (!server || !server.session) {
222
+ throw new Error("Server not found or not connected");
223
+ }
224
+ try {
225
+ const result = {
226
+ tool: toolName,
227
+ input,
228
+ result: `Mock result for ${toolName} with input: ${JSON.stringify(input)}`,
229
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
230
+ };
231
+ server.lastActivity = /* @__PURE__ */ new Date();
232
+ return result;
233
+ } catch (error) {
234
+ server.status = "error";
235
+ throw error;
236
+ }
237
+ }
238
+ async getServerTools(serverId) {
239
+ const server = this.servers.get(serverId);
240
+ if (!server) {
241
+ throw new Error("Server not found");
242
+ }
243
+ return server.tools;
244
+ }
245
+ async getServerResources(serverId) {
246
+ const server = this.servers.get(serverId);
247
+ if (!server) {
248
+ throw new Error("Server not found");
249
+ }
250
+ return server.resources;
251
+ }
252
+ async disconnectServer(serverId) {
253
+ const server = this.servers.get(serverId);
254
+ if (server && server.session) {
255
+ try {
256
+ await server.session.disconnect();
257
+ } catch (error) {
258
+ console.error("Error disconnecting from server:", error);
259
+ }
260
+ }
261
+ this.servers.delete(serverId);
262
+ }
263
+ };
264
+
265
+ // src/cli/inspect.ts
266
+ var import_meta = {};
267
+ var __filename = (0, import_node_url.fileURLToPath)(import_meta.url);
268
+ var __dirname = (0, import_node_path.dirname)(__filename);
269
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process.exec);
270
+ async function findAvailablePort(startPort2 = 8080) {
271
+ const net = await import("net");
272
+ for (let port = startPort2; port < startPort2 + 100; port++) {
273
+ try {
274
+ await new Promise((resolve, reject) => {
275
+ const server = net.createServer();
276
+ server.listen(port, () => {
277
+ server.close(() => resolve());
278
+ });
279
+ server.on("error", () => reject(new Error(`Port ${port} is in use`)));
280
+ });
281
+ return port;
282
+ } catch {
283
+ continue;
284
+ }
285
+ }
286
+ throw new Error(`No available port found starting from ${startPort2}`);
287
+ }
288
+ var args = process.argv.slice(2);
289
+ var mcpUrl;
290
+ var startPort = 8080;
291
+ for (let i = 0; i < args.length; i++) {
292
+ if (args[i] === "--url" && i + 1 < args.length) {
293
+ mcpUrl = args[i + 1];
294
+ i++;
295
+ } else if (args[i] === "--port" && i + 1 < args.length) {
296
+ startPort = parseInt(args[i + 1], 10);
297
+ i++;
298
+ } else if (args[i] === "--help" || args[i] === "-h") {
299
+ console.log(`
300
+ MCP Inspector - Inspect and debug MCP servers
301
+
302
+ Usage:
303
+ npx @mcp-use/inspect [options]
304
+
305
+ Options:
306
+ --url <url> MCP server URL to auto-connect to (e.g., http://localhost:3000/mcp)
307
+ --port <port> Starting port to try (default: 8080, will find next available)
308
+ --help, -h Show this help message
309
+
310
+ Examples:
311
+ # Run inspector with auto-connect
312
+ npx @mcp-use/inspect --url http://localhost:3000/mcp
313
+
314
+ # Run starting from custom port
315
+ npx @mcp-use/inspect --url http://localhost:3000/mcp --port 9000
316
+
317
+ # Run without auto-connect
318
+ npx @mcp-use/inspect
319
+ `);
320
+ process.exit(0);
321
+ }
322
+ }
323
+ var app2 = new import_hono2.Hono();
324
+ app2.use("*", (0, import_cors2.cors)());
325
+ app2.use("*", (0, import_logger.logger)());
326
+ app2.route("/api/favicon", favicon_proxy_default);
327
+ app2.get("/health", (c) => {
328
+ return c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
329
+ });
330
+ var mcpInspector = new MCPInspector();
331
+ app2.get("/api/servers", async (c) => {
332
+ try {
333
+ const servers = await mcpInspector.listServers();
334
+ return c.json({ servers });
335
+ } catch {
336
+ return c.json({ error: "Failed to list servers" }, 500);
337
+ }
338
+ });
339
+ app2.post("/api/servers/connect", async (c) => {
340
+ try {
341
+ const { url, command } = await c.req.json();
342
+ const server = await mcpInspector.connectToServer(url, command);
343
+ return c.json({ server });
344
+ } catch {
345
+ return c.json({ error: "Failed to connect to server" }, 500);
346
+ }
347
+ });
348
+ app2.get("/api/servers/:id", async (c) => {
349
+ try {
350
+ const id = c.req.param("id");
351
+ const server = await mcpInspector.getServer(id);
352
+ if (!server) {
353
+ return c.json({ error: "Server not found" }, 404);
354
+ }
355
+ return c.json({ server });
356
+ } catch {
357
+ return c.json({ error: "Failed to get server details" }, 500);
358
+ }
359
+ });
360
+ app2.post("/api/servers/:id/tools/:toolName/execute", async (c) => {
361
+ try {
362
+ const id = c.req.param("id");
363
+ const toolName = c.req.param("toolName");
364
+ const input = await c.req.json();
365
+ const result = await mcpInspector.executeTool(id, toolName, input);
366
+ return c.json({ result });
367
+ } catch {
368
+ return c.json({ error: "Failed to execute tool" }, 500);
369
+ }
370
+ });
371
+ app2.get("/api/servers/:id/tools", async (c) => {
372
+ try {
373
+ const id = c.req.param("id");
374
+ const tools = await mcpInspector.getServerTools(id);
375
+ return c.json({ tools });
376
+ } catch {
377
+ return c.json({ error: "Failed to get server tools" }, 500);
378
+ }
379
+ });
380
+ app2.get("/api/servers/:id/resources", async (c) => {
381
+ try {
382
+ const id = c.req.param("id");
383
+ const resources = await mcpInspector.getServerResources(id);
384
+ return c.json({ resources });
385
+ } catch {
386
+ return c.json({ error: "Failed to get server resources" }, 500);
387
+ }
388
+ });
389
+ app2.delete("/api/servers/:id", async (c) => {
390
+ try {
391
+ const id = c.req.param("id");
392
+ await mcpInspector.disconnectServer(id);
393
+ return c.json({ success: true });
394
+ } catch {
395
+ return c.json({ error: "Failed to disconnect server" }, 500);
396
+ }
397
+ });
398
+ var clientDistPath = (0, import_node_path.join)(__dirname, "../../dist/client");
399
+ if ((0, import_node_fs.existsSync)(clientDistPath)) {
400
+ app2.get("/inspector/assets/*", async (c) => {
401
+ const path = c.req.path.replace("/inspector/assets/", "assets/");
402
+ const fullPath = (0, import_node_path.join)(clientDistPath, path);
403
+ if ((0, import_node_fs.existsSync)(fullPath)) {
404
+ const content = await import("fs").then((fs) => fs.readFileSync(fullPath));
405
+ if (path.endsWith(".js")) {
406
+ c.header("Content-Type", "application/javascript");
407
+ } else if (path.endsWith(".css")) {
408
+ c.header("Content-Type", "text/css");
409
+ } else if (path.endsWith(".svg")) {
410
+ c.header("Content-Type", "image/svg+xml");
411
+ }
412
+ return c.body(content);
413
+ }
414
+ return c.notFound();
415
+ });
416
+ app2.get("/", (c) => {
417
+ return c.redirect("/inspector");
418
+ });
419
+ app2.get("*", (c) => {
420
+ const indexPath = (0, import_node_path.join)(clientDistPath, "index.html");
421
+ if ((0, import_node_fs.existsSync)(indexPath)) {
422
+ const content = import("fs").then((fs) => fs.readFileSync(indexPath, "utf-8"));
423
+ return c.html(content);
424
+ }
425
+ return c.html(`
426
+ <!DOCTYPE html>
427
+ <html>
428
+ <head>
429
+ <title>MCP Inspector</title>
430
+ </head>
431
+ <body>
432
+ <h1>MCP Inspector</h1>
433
+ <p>Client files not found. Please run 'yarn build' to build the UI.</p>
434
+ <p>API is available at <a href="/api/servers">/api/servers</a></p>
435
+ </body>
436
+ </html>
437
+ `);
438
+ });
439
+ } else {
440
+ console.warn(`\u26A0\uFE0F MCP Inspector client files not found at ${clientDistPath}`);
441
+ console.warn(` Run 'yarn build' in the inspector package to build the UI`);
442
+ app2.get("*", (c) => {
443
+ return c.html(`
444
+ <!DOCTYPE html>
445
+ <html>
446
+ <head>
447
+ <title>MCP Inspector</title>
448
+ </head>
449
+ <body>
450
+ <h1>MCP Inspector</h1>
451
+ <p>Client files not found. Please run 'yarn build' to build the UI.</p>
452
+ <p>API is available at <a href="/api/servers">/api/servers</a></p>
453
+ </body>
454
+ </html>
455
+ `);
456
+ });
457
+ }
458
+ async function startServer() {
459
+ try {
460
+ const port = await findAvailablePort(startPort);
461
+ (0, import_node_server.serve)({
462
+ fetch: app2.fetch,
463
+ port
464
+ });
465
+ console.log(`\u{1F680} MCP Inspector running on http://localhost:${port}`);
466
+ if (mcpUrl) {
467
+ console.log(`\u{1F4E1} Auto-connecting to: ${mcpUrl}`);
468
+ }
469
+ try {
470
+ const command = process.platform === "win32" ? "start" : process.platform === "darwin" ? "open" : "xdg-open";
471
+ await execAsync(`${command} http://localhost:${port}`);
472
+ console.log(`\u{1F310} Browser opened automatically`);
473
+ } catch (error) {
474
+ console.log(`\u{1F310} Please open http://localhost:${port} in your browser`);
475
+ }
476
+ return { port, fetch: app2.fetch };
477
+ } catch (error) {
478
+ console.error("Failed to start server:", error);
479
+ process.exit(1);
480
+ }
481
+ }
482
+ startServer();