Skip to content
This repository was archived by the owner on May 21, 2026. It is now read-only.

Commit 75ad75f

Browse files
committed
feat: move static file serving to server implementaiton
1 parent f5e3ddf commit 75ad75f

2 files changed

Lines changed: 58 additions & 80 deletions

File tree

examples/kanban-app/src/server.ts

Lines changed: 12 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,15 @@
1-
import { existsSync, readdirSync } from 'node:fs'
2-
import { join } from 'node:path'
3-
import cors from 'cors'
4-
import express from 'express'
51
import { createMCPServer } from 'mcp-use'
62

7-
// Create an MCP server with UI support
8-
const mcp = createMCPServer('ui-mcp-server', {
3+
// Create an MCP server (which is also an Express app)
4+
const server = createMCPServer('ui-mcp-server', {
95
version: '1.0.0',
106
description: 'An MCP server with React UI widgets',
117
})
128

13-
// Express server for serving UI resources
14-
const app = express()
15-
app.use(cors())
16-
app.use(express.json())
17-
18-
// Serve UI widgets using file-based routing
19-
// Serve static assets (JS, CSS) from the assets directory
20-
app.get('/mcp-use/widgets/:widget/assets/*', (req, res, next) => {
21-
const widget = req.params.widget
22-
const assetFile = (req.params as any)[0]
23-
const assetPath = join(process.cwd(), 'dist', 'resources', 'mcp-use', 'widgets', widget, 'assets', assetFile)
24-
res.sendFile(assetPath, err => (err ? next() : undefined))
25-
})
26-
27-
// Handle assets served from the wrong path (browser resolves ./assets/ relative to /mcp-use/widgets/)
28-
app.get('/mcp-use/widgets/assets/*', (req, res, next) => {
29-
const assetFile = (req.params as any)[0]
30-
// Try to find which widget this asset belongs to by checking all widget directories
31-
const widgetsDir = join(process.cwd(), 'dist', 'resources', 'mcp-use', 'widgets')
32-
33-
try {
34-
const widgets = readdirSync(widgetsDir)
35-
for (const widget of widgets) {
36-
const assetPath = join(widgetsDir, widget, 'assets', assetFile)
37-
if (existsSync(assetPath)) {
38-
return res.sendFile(assetPath)
39-
}
40-
}
41-
next()
42-
}
43-
catch {
44-
next()
45-
}
46-
})
47-
48-
// Serve each widget's index.html at its route
49-
// e.g. GET /mcp-use/widgets/kanban-board -> dist/resources/mcp-use/widgets/kanban-board/index.html
50-
app.get('/mcp-use/widgets/:widget', (req, res, next) => {
51-
const filePath = join(process.cwd(), 'dist', 'resources', 'mcp-use', 'widgets', req.params.widget, 'index.html')
52-
res.sendFile(filePath, err => (err ? next() : undefined))
53-
})
54-
55-
// Pass the Express app to MCP for SSE transport
56-
mcp.setExpressApp(app)
57-
58-
// Start Express server
599
const PORT = process.env.PORT || 3000
60-
app.listen(PORT, () => {
61-
console.log(`🌐 UI server running on http://localhost:${PORT}`)
62-
})
6310

6411
// MCP Resource for server status
65-
mcp.resource({
12+
server.resource({
6613
uri: 'ui://status',
6714
name: 'UI Server Status',
6815
description: 'Status of the UI MCP server',
@@ -82,7 +29,7 @@ mcp.resource({
8229
})
8330

8431
// MCP Resource for Kanban Board widget
85-
mcp.resource({
32+
server.resource({
8633
uri: 'ui://widget/kanban-board',
8734
name: 'Kanban Board Widget',
8835
description: 'Interactive Kanban board widget',
@@ -97,7 +44,7 @@ mcp.resource({
9744
})
9845

9946
// MCP Resource for Todo List widget
100-
mcp.resource({
47+
server.resource({
10148
uri: 'ui://widget/todo-list',
10249
name: 'Todo List Widget',
10350
description: 'Interactive todo list widget',
@@ -112,7 +59,7 @@ mcp.resource({
11259
})
11360

11461
// MCP Resource for Data Visualization widget
115-
mcp.resource({
62+
server.resource({
11663
uri: 'ui://widget/data-visualization',
11764
name: 'Data Visualization Widget',
11865
description: 'Interactive data visualization widget',
@@ -127,7 +74,7 @@ mcp.resource({
12774
})
12875

12976
// Tool for showing Kanban Board
130-
mcp.tool({
77+
server.tool({
13178
name: 'show-kanban',
13279
description: 'Display an interactive Kanban board',
13380
inputs: [
@@ -151,7 +98,7 @@ mcp.tool({
15198
})
15299

153100
// Tool for showing Todo List
154-
mcp.tool({
101+
server.tool({
155102
name: 'show-todo-list',
156103
description: 'Display an interactive todo list',
157104
inputs: [
@@ -175,7 +122,7 @@ mcp.tool({
175122
})
176123

177124
// Tool for showing Data Visualization
178-
mcp.tool({
125+
server.tool({
179126
name: 'show-data-viz',
180127
description: 'Display an interactive data visualization',
181128
inputs: [
@@ -205,7 +152,7 @@ mcp.tool({
205152
})
206153

207154
// Prompt for UI development
208-
mcp.prompt({
155+
server.prompt({
209156
name: 'ui-development',
210157
description: 'Generate UI development prompts',
211158
args: [
@@ -251,24 +198,9 @@ mcp.prompt({
251198

252199
console.log('🚀 Starting UI MCP Server...')
253200
console.log('📋 Server: ui-mcp-server v1.0.0')
254-
console.log(`🌐 UI Server: http://localhost:${PORT}`)
255201
console.log('📦 Resources: ui://status, ui://widget/kanban-board, ui://widget/todo-list, ui://widget/data-visualization')
256202
console.log('🛠️ Tools: show-kanban, show-todo-list, show-data-viz')
257203
console.log('💬 Prompts: ui-development')
258204

259-
// Start the MCP server with HTTP by default (use MCP_TRANSPORT=stdio env var for stdio)
260-
console.log('📡 Setting up MCP server...')
261-
mcp.serve({
262-
transport: (process.env.MCP_TRANSPORT as 'http' | 'stdio') || 'http',
263-
endpoint: '/mcp'
264-
}).then(() => {
265-
const transport = process.env.MCP_TRANSPORT || 'http'
266-
if (transport === 'http') {
267-
console.log(`📡 MCP server accessible at: http://localhost:${PORT}/mcp`)
268-
console.log('✅ Server ready! Connect with MCP clients via HTTP (StreamableHTTP transport)')
269-
} else {
270-
console.log('✅ Server ready! Connect with MCP clients via stdio')
271-
}
272-
}).catch((error) => {
273-
console.error('❌ Failed to start MCP server:', error)
274-
})
205+
// Start the server (MCP endpoints auto-mounted at /mcp)
206+
server.listen(PORT)

packages/mcp-use/src/server/mcp-server.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import type {
88
import { McpServer as OfficialMcpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
99
import { z } from 'zod'
1010
import express, { type Express } from 'express'
11+
import { existsSync, readdirSync } from 'node:fs'
12+
import { join } from 'node:path'
1113

1214
export class McpServer {
1315
private server: OfficialMcpServer
@@ -31,6 +33,9 @@ export class McpServer {
3133
next()
3234
})
3335

36+
// Setup default widget serving routes
37+
this.setupWidgetRoutes()
38+
3439
// Proxy all Express methods to the underlying app
3540
return new Proxy(this, {
3641
get(target, prop) {
@@ -200,6 +205,47 @@ export class McpServer {
200205
})
201206
}
202207

208+
/**
209+
* Setup default widget serving routes
210+
*/
211+
private setupWidgetRoutes(): void {
212+
// Serve static assets (JS, CSS) from the assets directory
213+
this.app.get('/mcp-use/widgets/:widget/assets/*', (req, res, next) => {
214+
const widget = req.params.widget
215+
const assetFile = (req.params as any)[0]
216+
const assetPath = join(process.cwd(), 'dist', 'resources', 'mcp-use', 'widgets', widget, 'assets', assetFile)
217+
res.sendFile(assetPath, err => (err ? next() : undefined))
218+
})
219+
220+
// Handle assets served from the wrong path (browser resolves ./assets/ relative to /mcp-use/widgets/)
221+
this.app.get('/mcp-use/widgets/assets/*', (req, res, next) => {
222+
const assetFile = (req.params as any)[0]
223+
// Try to find which widget this asset belongs to by checking all widget directories
224+
const widgetsDir = join(process.cwd(), 'dist', 'resources', 'mcp-use', 'widgets')
225+
226+
try {
227+
const widgets = readdirSync(widgetsDir)
228+
for (const widget of widgets) {
229+
const assetPath = join(widgetsDir, widget, 'assets', assetFile)
230+
if (existsSync(assetPath)) {
231+
return res.sendFile(assetPath)
232+
}
233+
}
234+
next()
235+
}
236+
catch {
237+
next()
238+
}
239+
})
240+
241+
// Serve each widget's index.html at its route
242+
// e.g. GET /mcp-use/widgets/kanban-board -> dist/resources/mcp-use/widgets/kanban-board/index.html
243+
this.app.get('/mcp-use/widgets/:widget', (req, res, next) => {
244+
const filePath = join(process.cwd(), 'dist', 'resources', 'mcp-use', 'widgets', req.params.widget, 'index.html')
245+
res.sendFile(filePath, err => (err ? next() : undefined))
246+
})
247+
}
248+
203249
/**
204250
* Create input schema for resource templates
205251
*/

0 commit comments

Comments
 (0)