Skip to content

Commit d8d6949

Browse files
committed
Merge branch 'feat/mcp-ui-apps' of https://github.com/mcp-use/mcp-use-ts into feat/mcp-ui-apps
2 parents 24873c9 + cec4ed2 commit d8d6949

28 files changed

+3039
-636
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
"test_app"
99
],
1010
"scripts": {
11-
"build": "pnpm run -r build",
11+
"build": "pnpm run build:mcp-use && pnpm run build:other",
12+
"build:mcp-use": "pnpm --filter mcp-use build",
13+
"build:other": "pnpm --filter '!mcp-use' --filter '!test_app' build",
1214
"test": "pnpm run -r test",
1315
"lint": "eslint .",
1416
"lint:fix": "eslint . --fix",

packages/cli/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,12 @@ program
171171
processes.push(serverProc);
172172

173173
// Auto-open inspector if enabled
174-
if (options.open) {
174+
if (options.open !== false) {
175175
const startTime = Date.now();
176176
const ready = await waitForServer(port);
177177
if (ready) {
178-
const inspectorUrl = `http://localhost:${port}/inspector`;
179178
const mcpUrl = `http://localhost:${port}/mcp`;
179+
const inspectorUrl = `http://localhost:${port}/inspector?autoConnect=${encodeURIComponent(mcpUrl)}`;
180180
const readyTime = Date.now() - startTime;
181181
console.log(`\n\x1b[32m✓\x1b[0m Ready in ${readyTime}ms`);
182182
console.log(`Local: http://localhost:${port}`);

packages/inspector/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@
2626
"dist"
2727
],
2828
"scripts": {
29-
"dev": "concurrently \"npm run dev:client\" \"npm run dev:server\"",
30-
"dev:client": "vite",
31-
"dev:server": "tsx watch src/server/standalone.ts",
29+
"dev": "tsx watch src/server/standalone.ts",
3230
"build": "npm run build:client && npm run build:server",
3331
"build:client": "vite build",
3432
"build:server": "tsc -p tsconfig.server.json",
@@ -62,17 +60,20 @@
6260
"@types/react-syntax-highlighter": "^15.5.13",
6361
"class-variance-authority": "^0.7.1",
6462
"clsx": "^2.1.1",
63+
"cmdk": "^1.1.1",
6564
"express": "^4.18.0",
6665
"framer-motion": "^12.23.22",
6766
"hono": "^4.0.0",
6867
"lucide-react": "^0.545.0",
6968
"mcp-use": "workspace:*",
7069
"motion": "^12.23.22",
70+
"next-themes": "^0.4.6",
7171
"react": "^19.2.0",
7272
"react-dom": "^19.2.0",
7373
"react-resizable-panels": "^3.0.6",
7474
"react-router-dom": "^7.9.3",
7575
"react-syntax-highlighter": "^15.6.6",
76+
"sonner": "^2.0.7",
7677
"tailwind-merge": "^3.3.1"
7778
},
7879
"publishConfig": {
Lines changed: 252 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,53 @@
11
#!/usr/bin/env node
22

3-
import express from 'express'
4-
import { mountInspector } from '../server/middleware.js'
3+
import { serve } from '@hono/node-server'
4+
import { Hono } from 'hono'
5+
import { cors } from 'hono/cors'
6+
import { logger } from 'hono/logger'
7+
import { existsSync } from 'node:fs'
8+
import { join, dirname } from 'node:path'
9+
import { fileURLToPath } from 'node:url'
10+
import { exec } from 'node:child_process'
11+
import { promisify } from 'node:util'
12+
import faviconProxy from '../server/favicon-proxy.js'
13+
import { MCPInspector } from '../server/mcp-inspector.js'
14+
15+
const __filename = fileURLToPath(import.meta.url)
16+
const __dirname = dirname(__filename)
17+
const execAsync = promisify(exec)
18+
19+
// Find available port starting from 8080
20+
async function findAvailablePort(startPort = 8080): Promise<number> {
21+
const net = await import('node:net')
22+
23+
for (let port = startPort; port < startPort + 100; port++) {
24+
try {
25+
await new Promise<void>((resolve, reject) => {
26+
const server = net.createServer()
27+
server.listen(port, () => {
28+
server.close(() => resolve())
29+
})
30+
server.on('error', () => reject(new Error(`Port ${port} is in use`)))
31+
})
32+
return port
33+
} catch {
34+
continue
35+
}
36+
}
37+
throw new Error(`No available port found starting from ${startPort}`)
38+
}
539

640
// Parse command line arguments
741
const args = process.argv.slice(2)
842
let mcpUrl: string | undefined
9-
let port = 3100
43+
let startPort = 8080
1044

1145
for (let i = 0; i < args.length; i++) {
1246
if (args[i] === '--url' && i + 1 < args.length) {
1347
mcpUrl = args[i + 1]
1448
i++
1549
} else if (args[i] === '--port' && i + 1 < args.length) {
16-
port = parseInt(args[i + 1], 10)
50+
startPort = parseInt(args[i + 1], 10)
1751
i++
1852
} else if (args[i] === '--help' || args[i] === '-h') {
1953
console.log(`
@@ -24,15 +58,15 @@ Usage:
2458
2559
Options:
2660
--url <url> MCP server URL to auto-connect to (e.g., http://localhost:3000/mcp)
27-
--port <port> Port to run the inspector on (default: 3100)
61+
--port <port> Starting port to try (default: 8080, will find next available)
2862
--help, -h Show this help message
2963
3064
Examples:
3165
# Run inspector with auto-connect
3266
npx @mcp-use/inspect --url http://localhost:3000/mcp
3367
34-
# Run on custom port
35-
npx @mcp-use/inspect --url http://localhost:3000/mcp --port 8080
68+
# Run starting from custom port
69+
npx @mcp-use/inspect --url http://localhost:3000/mcp --port 9000
3670
3771
# Run without auto-connect
3872
npx @mcp-use/inspect
@@ -41,17 +75,219 @@ Examples:
4175
}
4276
}
4377

44-
const app = express()
78+
const app = new Hono()
4579

46-
// Mount the inspector
47-
mountInspector(app, '/', mcpUrl)
80+
// Middleware
81+
app.use('*', cors())
82+
app.use('*', logger())
4883

49-
// Start the server
50-
app.listen(port, () => {
51-
console.log(`MCP Inspector running at http://localhost:${port}`)
52-
if (mcpUrl) {
53-
console.log(`📡 Auto-connecting to: ${mcpUrl}`)
84+
// Mount favicon proxy
85+
app.route('/api/favicon', faviconProxy)
86+
87+
// Health check
88+
app.get('/health', (c) => {
89+
return c.json({ status: 'ok', timestamp: new Date().toISOString() })
90+
})
91+
92+
// MCP Inspector routes
93+
const mcpInspector = new MCPInspector()
94+
95+
// List available MCP servers
96+
app.get('/api/servers', async (c) => {
97+
try {
98+
const servers = await mcpInspector.listServers()
99+
return c.json({ servers })
100+
}
101+
catch {
102+
return c.json({ error: 'Failed to list servers' }, 500)
103+
}
104+
})
105+
106+
// Connect to an MCP server
107+
app.post('/api/servers/connect', async (c) => {
108+
try {
109+
const { url, command } = await c.req.json()
110+
const server = await mcpInspector.connectToServer(url, command)
111+
return c.json({ server })
112+
}
113+
catch {
114+
return c.json({ error: 'Failed to connect to server' }, 500)
115+
}
116+
})
117+
118+
// Get server details
119+
app.get('/api/servers/:id', async (c) => {
120+
try {
121+
const id = c.req.param('id')
122+
const server = await mcpInspector.getServer(id)
123+
if (!server) {
124+
return c.json({ error: 'Server not found' }, 404)
125+
}
126+
return c.json({ server })
127+
}
128+
catch {
129+
return c.json({ error: 'Failed to get server details' }, 500)
130+
}
131+
})
132+
133+
// Execute a tool on a server
134+
app.post('/api/servers/:id/tools/:toolName/execute', async (c) => {
135+
try {
136+
const id = c.req.param('id')
137+
const toolName = c.req.param('toolName')
138+
const input = await c.req.json()
139+
140+
const result = await mcpInspector.executeTool(id, toolName, input)
141+
return c.json({ result })
142+
}
143+
catch {
144+
return c.json({ error: 'Failed to execute tool' }, 500)
145+
}
146+
})
147+
148+
// Get server tools
149+
app.get('/api/servers/:id/tools', async (c) => {
150+
try {
151+
const id = c.req.param('id')
152+
const tools = await mcpInspector.getServerTools(id)
153+
return c.json({ tools })
154+
}
155+
catch {
156+
return c.json({ error: 'Failed to get server tools' }, 500)
157+
}
158+
})
159+
160+
// Get server resources
161+
app.get('/api/servers/:id/resources', async (c) => {
162+
try {
163+
const id = c.req.param('id')
164+
const resources = await mcpInspector.getServerResources(id)
165+
return c.json({ resources })
166+
}
167+
catch {
168+
return c.json({ error: 'Failed to get server resources' }, 500)
169+
}
170+
})
171+
172+
// Disconnect from a server
173+
app.delete('/api/servers/:id', async (c) => {
174+
try {
175+
const id = c.req.param('id')
176+
await mcpInspector.disconnectServer(id)
177+
return c.json({ success: true })
178+
}
179+
catch {
180+
return c.json({ error: 'Failed to disconnect server' }, 500)
54181
}
55-
console.log(`\nOpen http://localhost:${port} in your browser to inspect MCP servers`)
56182
})
57183

184+
// Serve static assets from the built client
185+
const clientDistPath = join(__dirname, '../../dist/client')
186+
187+
if (existsSync(clientDistPath)) {
188+
// Serve static assets from /inspector/assets/* (matching Vite's base path)
189+
app.get('/inspector/assets/*', async (c) => {
190+
const path = c.req.path.replace('/inspector/assets/', 'assets/')
191+
const fullPath = join(clientDistPath, path)
192+
193+
if (existsSync(fullPath)) {
194+
const content = await import('node:fs').then(fs => fs.readFileSync(fullPath))
195+
196+
// Set appropriate content type based on file extension
197+
if (path.endsWith('.js')) {
198+
c.header('Content-Type', 'application/javascript')
199+
} else if (path.endsWith('.css')) {
200+
c.header('Content-Type', 'text/css')
201+
} else if (path.endsWith('.svg')) {
202+
c.header('Content-Type', 'image/svg+xml')
203+
}
204+
205+
return c.body(content)
206+
}
207+
208+
return c.notFound()
209+
})
210+
211+
// Redirect root path to /inspector
212+
app.get('/', (c) => {
213+
return c.redirect('/inspector')
214+
})
215+
216+
// Serve the main HTML file for /inspector and all other routes (SPA routing)
217+
app.get('*', (c) => {
218+
const indexPath = join(clientDistPath, 'index.html')
219+
if (existsSync(indexPath)) {
220+
const content = import('node:fs').then(fs => fs.readFileSync(indexPath, 'utf-8'))
221+
return c.html(content)
222+
}
223+
return c.html(`
224+
<!DOCTYPE html>
225+
<html>
226+
<head>
227+
<title>MCP Inspector</title>
228+
</head>
229+
<body>
230+
<h1>MCP Inspector</h1>
231+
<p>Client files not found. Please run 'yarn build' to build the UI.</p>
232+
<p>API is available at <a href="/api/servers">/api/servers</a></p>
233+
</body>
234+
</html>
235+
`)
236+
})
237+
} else {
238+
console.warn(`⚠️ MCP Inspector client files not found at ${clientDistPath}`)
239+
console.warn(` Run 'yarn build' in the inspector package to build the UI`)
240+
241+
// Fallback for when client is not built
242+
app.get('*', (c) => {
243+
return c.html(`
244+
<!DOCTYPE html>
245+
<html>
246+
<head>
247+
<title>MCP Inspector</title>
248+
</head>
249+
<body>
250+
<h1>MCP Inspector</h1>
251+
<p>Client files not found. Please run 'yarn build' to build the UI.</p>
252+
<p>API is available at <a href="/api/servers">/api/servers</a></p>
253+
</body>
254+
</html>
255+
`)
256+
})
257+
}
258+
259+
// Start the server with automatic port selection
260+
async function startServer() {
261+
try {
262+
const port = await findAvailablePort(startPort)
263+
264+
serve({
265+
fetch: app.fetch,
266+
port,
267+
})
268+
269+
console.log(`🚀 MCP Inspector running on http://localhost:${port}`)
270+
271+
if (mcpUrl) {
272+
console.log(`📡 Auto-connecting to: ${mcpUrl}`)
273+
}
274+
275+
// Auto-open browser
276+
try {
277+
const command = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open'
278+
await execAsync(`${command} http://localhost:${port}`)
279+
console.log(`🌐 Browser opened automatically`)
280+
} catch (error) {
281+
console.log(`🌐 Please open http://localhost:${port} in your browser`)
282+
}
283+
284+
return { port, fetch: app.fetch }
285+
} catch (error) {
286+
console.error('Failed to start server:', error)
287+
process.exit(1)
288+
}
289+
}
290+
291+
// Start the server
292+
startServer()
293+

0 commit comments

Comments
 (0)