@mcp-use/inspector 0.2.1 → 0.2.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.
@@ -8,7 +8,7 @@
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap" rel="stylesheet">
10
10
  <title>MCP Inspector</title>
11
- <script type="module" crossorigin src="/inspector/assets/index-Risbd_Le.js"></script>
11
+ <script type="module" crossorigin src="/inspector/assets/index-DZm4vi0J.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/inspector/assets/index-BOon65c9.css">
13
13
  </head>
14
14
  <body>
@@ -0,0 +1,123 @@
1
+ import { Hono } from 'hono';
2
+ import { cors } from 'hono/cors';
3
+ const app = new Hono();
4
+ // Enable CORS for all routes
5
+ app.use('*', cors());
6
+ const faviconCache = new Map();
7
+ const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
8
+ const MAX_CACHE_SIZE = 1000; // Maximum number of cached favicons
9
+ // Clean up expired cache entries
10
+ function cleanupCache() {
11
+ const now = Date.now();
12
+ for (const [key, entry] of faviconCache.entries()) {
13
+ if (now - entry.timestamp > entry.ttl) {
14
+ faviconCache.delete(key);
15
+ }
16
+ }
17
+ }
18
+ // Run cleanup every hour
19
+ setInterval(cleanupCache, 60 * 60 * 1000);
20
+ // Get cache key for a URL
21
+ function getCacheKey(url) {
22
+ try {
23
+ const urlObj = new URL(url);
24
+ return urlObj.hostname.toLowerCase();
25
+ }
26
+ catch {
27
+ return url.toLowerCase();
28
+ }
29
+ }
30
+ // Favicon proxy endpoint
31
+ app.get('/:url', async (c) => {
32
+ const url = c.req.param('url');
33
+ if (!url) {
34
+ return c.json({ error: 'URL parameter is required' }, 400);
35
+ }
36
+ try {
37
+ // Decode the URL
38
+ const decodedUrl = decodeURIComponent(url);
39
+ // Add protocol if missing
40
+ let fullUrl = decodedUrl;
41
+ if (!decodedUrl.startsWith('http://') && !decodedUrl.startsWith('https://')) {
42
+ fullUrl = `https://${decodedUrl}`;
43
+ }
44
+ // Validate URL
45
+ const urlObj = new URL(fullUrl);
46
+ const cacheKey = getCacheKey(fullUrl);
47
+ // Check cache first
48
+ const cachedEntry = faviconCache.get(cacheKey);
49
+ if (cachedEntry && (Date.now() - cachedEntry.timestamp) < cachedEntry.ttl) {
50
+ return new Response(cachedEntry.data, {
51
+ headers: {
52
+ 'Content-Type': cachedEntry.contentType,
53
+ 'Cache-Control': 'public, max-age=86400, immutable', // Cache for 24 hours
54
+ 'Access-Control-Allow-Origin': '*',
55
+ 'X-Cache': 'HIT',
56
+ },
57
+ });
58
+ }
59
+ const protocol = urlObj.protocol;
60
+ const baseDomain = `${protocol}//${urlObj.origin.split('.').slice(-2).join('.')}`;
61
+ // Try to fetch favicon from common locations
62
+ const faviconUrls = [
63
+ `${urlObj.origin}/favicon.ico`,
64
+ `${urlObj.origin}/favicon.png`,
65
+ `${urlObj.origin}/apple-touch-icon.png`,
66
+ `${urlObj.origin}/icon.png`,
67
+ `${baseDomain}/favicon.ico`,
68
+ `${baseDomain}/favicon.png`,
69
+ `${baseDomain}/apple-touch-icon.png`,
70
+ `${baseDomain}/icon.png`,
71
+ ];
72
+ for (const faviconUrl of faviconUrls) {
73
+ try {
74
+ const response = await fetch(faviconUrl, {
75
+ headers: {
76
+ 'User-Agent': 'Mozilla/5.0 (compatible; MCP-Inspector/1.0)',
77
+ },
78
+ });
79
+ if (response.ok) {
80
+ const contentType = response.headers.get('content-type') || 'image/x-icon';
81
+ const buffer = await response.arrayBuffer();
82
+ // Cache the result
83
+ if (faviconCache.size >= MAX_CACHE_SIZE) {
84
+ // Remove oldest entries if cache is full
85
+ const entries = Array.from(faviconCache.entries());
86
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
87
+ const toRemove = entries.slice(0, Math.floor(MAX_CACHE_SIZE / 4));
88
+ toRemove.forEach(([key]) => faviconCache.delete(key));
89
+ }
90
+ faviconCache.set(cacheKey, {
91
+ data: buffer,
92
+ contentType,
93
+ timestamp: Date.now(),
94
+ ttl: CACHE_TTL,
95
+ });
96
+ return new Response(buffer, {
97
+ headers: {
98
+ 'Content-Type': contentType,
99
+ 'Cache-Control': 'public, max-age=86400, immutable', // Cache for 24 hours
100
+ 'Access-Control-Allow-Origin': '*',
101
+ 'X-Cache': 'MISS',
102
+ },
103
+ });
104
+ }
105
+ }
106
+ catch {
107
+ // Continue to next URL
108
+ continue;
109
+ }
110
+ }
111
+ // If no favicon found, return a default icon
112
+ return c.json({ error: 'No favicon found' }, 404);
113
+ }
114
+ catch (error) {
115
+ console.error('Favicon proxy error:', error);
116
+ return c.json({ error: 'Invalid URL or fetch failed' }, 400);
117
+ }
118
+ });
119
+ // Health check endpoint
120
+ app.get('/health', (c) => {
121
+ return c.json({ status: 'ok', service: 'favicon-proxy' });
122
+ });
123
+ export default app;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * MCP Inspector - Middleware for mounting inspector UI on Express apps
3
+ *
4
+ * This is the main entry point for importing the inspector as a library.
5
+ * For standalone server usage, see standalone.ts
6
+ */
7
+ export { mountInspector } from './middleware.js';
@@ -0,0 +1,132 @@
1
+ import { MCPClient } from 'mcp-use';
2
+ export class MCPInspector {
3
+ constructor() {
4
+ Object.defineProperty(this, "servers", {
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true,
8
+ value: new Map()
9
+ });
10
+ Object.defineProperty(this, "client", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: void 0
15
+ });
16
+ this.client = new MCPClient();
17
+ }
18
+ async listServers() {
19
+ return Array.from(this.servers.values());
20
+ }
21
+ async connectToServer(url, command) {
22
+ const id = Date.now().toString();
23
+ const name = url || command || 'Unknown Server';
24
+ try {
25
+ // Configure server in MCP client
26
+ const serverName = `server_${id}`;
27
+ const serverConfig = url ? { url } : { command };
28
+ this.client.addServer(serverName, serverConfig);
29
+ // Create session
30
+ const session = await this.client.createSession(serverName, true);
31
+ // Mock tools and resources for now
32
+ const tools = [
33
+ {
34
+ name: 'get_weather',
35
+ description: 'Get current weather for a location',
36
+ inputSchema: {
37
+ type: 'object',
38
+ properties: {
39
+ location: { type: 'string', description: 'City name' },
40
+ },
41
+ required: ['location'],
42
+ },
43
+ },
44
+ ];
45
+ const resources = [
46
+ {
47
+ uri: 'file:///home/user/documents',
48
+ name: 'Documents',
49
+ description: 'User documents directory',
50
+ mimeType: 'application/x-directory',
51
+ },
52
+ ];
53
+ const server = {
54
+ id,
55
+ name,
56
+ url,
57
+ command,
58
+ status: 'connected',
59
+ session,
60
+ tools,
61
+ resources,
62
+ lastActivity: new Date(),
63
+ };
64
+ this.servers.set(id, server);
65
+ return server;
66
+ }
67
+ catch (error) {
68
+ const server = {
69
+ id,
70
+ name,
71
+ url,
72
+ command,
73
+ status: 'error',
74
+ tools: [],
75
+ resources: [],
76
+ lastActivity: new Date(),
77
+ };
78
+ this.servers.set(id, server);
79
+ throw error;
80
+ }
81
+ }
82
+ async getServer(id) {
83
+ return this.servers.get(id) || null;
84
+ }
85
+ async executeTool(serverId, toolName, input) {
86
+ const server = this.servers.get(serverId);
87
+ if (!server || !server.session) {
88
+ throw new Error('Server not found or not connected');
89
+ }
90
+ try {
91
+ // Mock tool execution for now
92
+ const result = {
93
+ tool: toolName,
94
+ input,
95
+ result: `Mock result for ${toolName} with input: ${JSON.stringify(input)}`,
96
+ timestamp: new Date().toISOString(),
97
+ };
98
+ server.lastActivity = new Date();
99
+ return result;
100
+ }
101
+ catch (error) {
102
+ server.status = 'error';
103
+ throw error;
104
+ }
105
+ }
106
+ async getServerTools(serverId) {
107
+ const server = this.servers.get(serverId);
108
+ if (!server) {
109
+ throw new Error('Server not found');
110
+ }
111
+ return server.tools;
112
+ }
113
+ async getServerResources(serverId) {
114
+ const server = this.servers.get(serverId);
115
+ if (!server) {
116
+ throw new Error('Server not found');
117
+ }
118
+ return server.resources;
119
+ }
120
+ async disconnectServer(serverId) {
121
+ const server = this.servers.get(serverId);
122
+ if (server && server.session) {
123
+ try {
124
+ await server.session.disconnect();
125
+ }
126
+ catch (error) {
127
+ console.error('Error disconnecting from server:', error);
128
+ }
129
+ }
130
+ this.servers.delete(serverId);
131
+ }
132
+ }
@@ -0,0 +1,145 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ /**
8
+ * Mount the MCP Inspector UI at a specified path on an Express app
9
+ * Similar to how FastAPI mounts Swagger UI at /docs
10
+ *
11
+ * @param app - Express application instance
12
+ * @param path - Mount path (default: '/inspector')
13
+ * @param mcpServerUrl - Optional MCP server URL to auto-connect to
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { createMCPServer } from 'mcp-use'
18
+ * import { mountInspector } from '@mcp-use/inspector'
19
+ *
20
+ * const server = createMCPServer('my-server')
21
+ * mountInspector(server) // Mounts at /inspector
22
+ * ```
23
+ */
24
+ export function mountInspector(app, path = '/inspector', mcpServerUrl) {
25
+ // Ensure path starts with /
26
+ const basePath = path.startsWith('/') ? path : `/${path}`;
27
+ // Find the built client files
28
+ const clientDistPath = join(__dirname, '../../dist/client');
29
+ if (!existsSync(clientDistPath)) {
30
+ console.warn(`⚠️ MCP Inspector client files not found at ${clientDistPath}`);
31
+ console.warn(` Run 'yarn build' in the inspector package to build the UI`);
32
+ return;
33
+ }
34
+ // Serve inspector config endpoint
35
+ app.get(`${basePath}/config.json`, (_req, res) => {
36
+ res.json({
37
+ autoConnectUrl: mcpServerUrl || null,
38
+ });
39
+ });
40
+ // Favicon proxy endpoint
41
+ app.get(`${basePath}/api/favicon/:url`, async (req, res) => {
42
+ const url = req.params.url;
43
+ if (!url) {
44
+ res.status(400).json({ error: 'URL parameter is required' });
45
+ return;
46
+ }
47
+ try {
48
+ // Decode the URL
49
+ const decodedUrl = decodeURIComponent(url);
50
+ // Add protocol if missing
51
+ let fullUrl = decodedUrl;
52
+ if (!decodedUrl.startsWith('http://') && !decodedUrl.startsWith('https://')) {
53
+ fullUrl = `https://${decodedUrl}`;
54
+ }
55
+ // Validate URL
56
+ const urlObj = new URL(fullUrl);
57
+ // Try to fetch favicon from common locations
58
+ const faviconUrls = [
59
+ `${urlObj.origin}/favicon.ico`,
60
+ `${urlObj.origin}/favicon.png`,
61
+ `${urlObj.origin}/apple-touch-icon.png`,
62
+ ];
63
+ for (const faviconUrl of faviconUrls) {
64
+ try {
65
+ const response = await fetch(faviconUrl, {
66
+ headers: {
67
+ 'User-Agent': 'Mozilla/5.0 (compatible; MCP-Inspector/1.0)',
68
+ },
69
+ });
70
+ if (response.ok) {
71
+ const contentType = response.headers.get('content-type') || 'image/x-icon';
72
+ const buffer = await response.arrayBuffer();
73
+ res.setHeader('Content-Type', contentType);
74
+ res.setHeader('Cache-Control', 'public, max-age=86400');
75
+ res.setHeader('Access-Control-Allow-Origin', '*');
76
+ res.send(Buffer.from(buffer));
77
+ return;
78
+ }
79
+ }
80
+ catch {
81
+ // Continue to next URL
82
+ continue;
83
+ }
84
+ }
85
+ // If no favicon found, return 404
86
+ res.status(404).json({ error: 'No favicon found' });
87
+ }
88
+ catch (error) {
89
+ console.error('Favicon proxy error:', error);
90
+ res.status(400).json({ error: 'Invalid URL or fetch failed' });
91
+ }
92
+ });
93
+ // Serve static assets
94
+ app.use(`${basePath}/assets`, (_req, res) => {
95
+ const assetPath = join(clientDistPath, 'assets', _req.path);
96
+ if (existsSync(assetPath)) {
97
+ // Set appropriate content type
98
+ if (assetPath.endsWith('.js')) {
99
+ res.setHeader('Content-Type', 'application/javascript');
100
+ }
101
+ else if (assetPath.endsWith('.css')) {
102
+ res.setHeader('Content-Type', 'text/css');
103
+ }
104
+ res.sendFile(assetPath);
105
+ }
106
+ else {
107
+ res.status(404).send('Asset not found');
108
+ }
109
+ });
110
+ // Handle OAuth callback redirects - redirect /oauth/callback to /inspector/oauth/callback
111
+ // This helps when OAuth providers are configured with the wrong redirect URL
112
+ if (basePath !== '') {
113
+ app.get('/oauth/callback', (req, res) => {
114
+ const queryString = req.url.split('?')[1] || '';
115
+ const redirectUrl = queryString
116
+ ? `${basePath}/oauth/callback?${queryString}`
117
+ : `${basePath}/oauth/callback`;
118
+ res.redirect(302, redirectUrl);
119
+ });
120
+ }
121
+ // Serve the main HTML file for the root inspector path (exact match)
122
+ app.get(basePath, (_req, res) => {
123
+ const indexPath = join(clientDistPath, 'index.html');
124
+ if (!existsSync(indexPath)) {
125
+ res.status(500).send('Inspector UI not found. Please build the inspector package.');
126
+ return;
127
+ }
128
+ // Serve the HTML file (Vite built with base: '/inspector')
129
+ res.sendFile(indexPath);
130
+ });
131
+ // Redirect /inspector/ to /inspector (remove trailing slash)
132
+ app.get(`${basePath}/`, (_req, res) => {
133
+ res.redirect(301, basePath);
134
+ });
135
+ // Serve the main HTML file for all other inspector routes (SPA routing)
136
+ app.get(`${basePath}/*`, (_req, res) => {
137
+ const indexPath = join(clientDistPath, 'index.html');
138
+ if (!existsSync(indexPath)) {
139
+ res.status(500).send('Inspector UI not found. Please build the inspector package.');
140
+ return;
141
+ }
142
+ // Serve the HTML file (Vite built with base: '/inspector')
143
+ res.sendFile(indexPath);
144
+ });
145
+ }