UI MCP Server
An MCP server with React UI widgets created with create-mcp-app that provides interactive web components for MCP clients.
Features
- π¨ React Components: Interactive UI widgets built with React
- π₯ Hot Reloading: Development server with instant updates
- π¦ Production Builds: Optimized bundles for production
- π Web Endpoints: Serve widgets at
/mcp-use/widgets/{widget-name}
- π οΈ Development Tools: Full TypeScript support and modern tooling
Getting Started
Development
npm install
npm run dev
This will start:
- MCP server on port 3000
- Vite dev server on port 3001 with hot reloading
Production
npm run build
npm start
Available Widgets
1. Kanban Board (/mcp-use/widgets/kanban-board)
Interactive Kanban board for task management.
Features:
- Drag and drop tasks between columns
- Add/remove tasks
- Priority levels and assignees
- Real-time updates
Usage:
mcp.tool({
name: 'show-kanban',
inputs: [{ name: 'tasks', type: 'string', required: true }],
fn: async ({ tasks }) => {
}
})
2. Todo List (/mcp-use/widgets/todo-list)
Interactive todo list with filtering and sorting.
Features:
- Add/complete/delete todos
- Filter by status (all/active/completed)
- Sort by priority, due date, or creation time
- Progress tracking
- Categories and due dates
Usage:
mcp.tool({
name: 'show-todo-list',
inputs: [{ name: 'todos', type: 'string', required: true }],
fn: async ({ todos }) => {
}
})
3. Data Visualization (/mcp-use/widgets/data-visualization)
Interactive charts and data visualization.
Features:
- Bar charts, line charts, and pie charts
- Add/remove data points
- Interactive legends
- Data table view
- Multiple chart types
Usage:
mcp.tool({
name: 'show-data-viz',
inputs: [
{ name: 'data', type: 'string', required: true },
{ name: 'chartType', type: 'string', required: false }
],
fn: async ({ data, chartType }) => {
}
})
Development Workflow
1. Create a New Widget
-
Create the React component:
touch resources/my-widget.tsx
-
Create the HTML entry point:
touch resources/my-widget.html
-
Add to Vite config:
rollupOptions: {
input: {
'my-widget': resolve(__dirname, 'resources/my-widget.html')
}
}
-
Add MCP resource:
mcp.resource({
uri: 'ui://widget/my-widget',
name: 'My Widget',
mimeType: 'text/html+skybridge',
fn: async () => {
const widgetUrl = `http://localhost:${PORT}/mcp-use/widgets/my-widget`
return `<div id="my-widget-root"></div><script type="module" src="${widgetUrl}"></script>`
}
})
2. Development with Hot Reloading
npm run dev
open http://localhost:3001/my-widget.html
Changes to your React components will automatically reload!
3. Widget Development Best Practices
Component Structure
import React, { useState, useEffect } from 'react'
import { createRoot } from 'react-dom/client'
interface MyWidgetProps {
initialData?: any
}
const MyWidget: React.FC<MyWidgetProps> = ({ initialData = [] }) => {
const [data, setData] = useState(initialData)
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search)
const dataParam = urlParams.get('data')
if (dataParam) {
try {
const parsedData = JSON.parse(decodeURIComponent(dataParam))
setData(parsedData)
} catch (error) {
console.error('Error parsing data:', error)
}
}
}, [])
return (
<div>
{/* Your widget content */}
</div>
)
}
const container = document.getElementById('my-widget-root')
if (container) {
const root = createRoot(container)
root.render(<MyWidget />)
}
Styling Guidelines
- Use inline styles for simplicity
- Follow the existing design system
- Ensure responsive design
- Use consistent color palette
Data Handling
- Accept data via URL parameters
- Provide sensible defaults
- Handle errors gracefully
- Use TypeScript for type safety
4. Production Deployment
npm run build
Project Structure
my-ui-server/
βββ src/
β βββ server.ts # MCP server with UI endpoints
βββ resources/ # React components and HTML entry points
β βββ kanban-board.html
β βββ kanban-board.tsx
β βββ todo-list.html
β βββ todo-list.tsx
β βββ data-visualization.html
β βββ data-visualization.tsx
βββ dist/ # Built files
β βββ server.js
β βββ resources/
βββ package.json
βββ tsconfig.json
βββ vite.config.ts
βββ README.md
API Reference
MCP Resources
All UI widgets are available as MCP resources:
ui://status - Server status and available widgets
ui://widget/kanban-board - Kanban board widget
ui://widget/todo-list - Todo list widget
ui://widget/data-visualization - Data visualization widget
MCP Tools
show-kanban - Display Kanban board with tasks
show-todo-list - Display todo list with items
show-data-viz - Display data visualization
MCP Prompts
ui-development - Generate UI development guidance
Customization
Adding New Dependencies
npm install @types/react-router-dom react-router-dom
npm install @mui/material @emotion/react @emotion/styled
Environment Variables
Create a .env file:
PORT=3000
NODE_ENV=development
VITE_API_URL=http://localhost:3000
Custom Build Configuration
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
input: {
'my-custom-widget': resolve(__dirname, 'resources/my-custom-widget.html')
}
}
}
})
Troubleshooting
Common Issues
Development Tips
- Use browser dev tools to debug React components
- Check the Network tab for failed requests
- Use React DevTools browser extension
- Monitor console for errors
Learn More
Examples
Simple Counter Widget
import React, { useState } from 'react'
import { createRoot } from 'react-dom/client'
const Counter: React.FC = () => {
const [count, setCount] = useState(0)
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h1>Counter: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
</div>
)
}
const container = document.getElementById('counter-root')
if (container) {
const root = createRoot(container)
root.render(<Counter />)
}
Data Table Widget
import React, { useState, useEffect } from 'react'
import { createRoot } from 'react-dom/client'
interface TableData {
id: string
name: string
value: number
}
const DataTable: React.FC = () => {
const [data, setData] = useState<TableData[]>([])
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search)
const dataParam = urlParams.get('data')
if (dataParam) {
try {
setData(JSON.parse(decodeURIComponent(dataParam)))
} catch (error) {
console.error('Error parsing data:', error)
}
}
}, [])
return (
<div style={{ padding: '20px' }}>
<h1>Data Table</h1>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{data.map(row => (
<tr key={row.id}>
<td>{row.name}</td>
<td>{row.value}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
const container = document.getElementById('data-table-root')
if (container) {
const root = createRoot(container)
root.render(<DataTable />)
}
Happy coding! π