create-mcp-use-app 0.3.3 → 0.3.5

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.
package/dist/index.js CHANGED
@@ -155,8 +155,15 @@ async function copyTemplate(projectPath, template, versions, isDevelopment = fal
155
155
  const templatePath = join(__dirname, "templates", template);
156
156
  if (!existsSync(templatePath)) {
157
157
  console.error(`\u274C Template "${template}" not found!`);
158
- console.log("Available templates: basic, filesystem, api, ui");
158
+ const templatesDir = join(__dirname, "templates");
159
+ if (existsSync(templatesDir)) {
160
+ const availableTemplates = readdirSync(templatesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name).sort();
161
+ console.log(`Available templates: ${availableTemplates.join(", ")}`);
162
+ } else {
163
+ console.log("No templates directory found");
164
+ }
159
165
  console.log('\u{1F4A1} Tip: Use "ui" template for React components and modern UI features');
166
+ console.log('\u{1F4A1} Tip: Use "uiresource" template for UI resources and advanced server examples');
160
167
  process.exit(1);
161
168
  }
162
169
  copyDirectoryWithProcessing(templatePath, projectPath, versions, isDevelopment);
@@ -0,0 +1,376 @@
1
+ # UIResource MCP Server
2
+
3
+ An MCP server with the new UIResource integration for simplified widget management and MCP-UI compatibility.
4
+
5
+ ## Features
6
+
7
+ - **🚀 UIResource Method**: Single method to register both tools and resources
8
+ - **🎨 React Widgets**: Interactive UI components built with React
9
+ - **🔄 Automatic Registration**: Tools and resources created automatically
10
+ - **📦 Props to Parameters**: Widget props automatically become tool parameters
11
+ - **🌐 MCP-UI Compatible**: Full compatibility with MCP-UI clients
12
+ - **🛠️ TypeScript Support**: Complete type safety and IntelliSense
13
+
14
+ ## What's New: UIResource
15
+
16
+ The `uiResource` method is a powerful new addition that simplifies widget registration:
17
+
18
+ ```typescript
19
+ // Old way: Manual registration of tool and resource
20
+ server.tool({ /* tool config */ })
21
+ server.resource({ /* resource config */ })
22
+
23
+ // New way: Single method does both!
24
+ server.uiResource({
25
+ name: 'kanban-board',
26
+ widget: 'kanban-board',
27
+ title: 'Kanban Board',
28
+ props: {
29
+ initialTasks: { type: 'array', required: false },
30
+ theme: { type: 'string', default: 'light' }
31
+ }
32
+ })
33
+ ```
34
+
35
+ This automatically creates:
36
+ - **Tool**: `ui_kanban-board` - Accepts parameters and returns UIResource
37
+ - **Resource**: `ui://widget/kanban-board` - Static access with defaults
38
+
39
+ ## Getting Started
40
+
41
+ ### Development
42
+
43
+ ```bash
44
+ # Install dependencies
45
+ npm install
46
+
47
+ # Start development server with hot reloading
48
+ npm run dev
49
+ ```
50
+
51
+ This will start:
52
+ - MCP server on port 3000
53
+ - Widget serving at `/mcp-use/widgets/*`
54
+ - Inspector UI at `/inspector`
55
+
56
+ ### Production
57
+
58
+ ```bash
59
+ # Build the server and widgets
60
+ npm run build
61
+
62
+ # Run the built server
63
+ npm start
64
+ ```
65
+
66
+ ## Basic Usage
67
+
68
+ ### Simple Widget Registration
69
+
70
+ ```typescript
71
+ import { createMCPServer } from 'mcp-use/server'
72
+
73
+ const server = createMCPServer('my-server', {
74
+ version: '1.0.0',
75
+ description: 'Server with UIResource widgets'
76
+ })
77
+
78
+ // Register a widget - creates both tool and resource
79
+ server.uiResource({
80
+ name: 'my-widget',
81
+ widget: 'my-widget',
82
+ title: 'My Widget',
83
+ description: 'An interactive widget'
84
+ })
85
+
86
+ server.listen(3000)
87
+ ```
88
+
89
+ ### Widget with Props
90
+
91
+ ```typescript
92
+ server.uiResource({
93
+ name: 'data-chart',
94
+ widget: 'chart',
95
+ title: 'Data Chart',
96
+ description: 'Interactive data visualization',
97
+ props: {
98
+ data: {
99
+ type: 'array',
100
+ description: 'Data points to display',
101
+ required: true
102
+ },
103
+ chartType: {
104
+ type: 'string',
105
+ description: 'Type of chart (line/bar/pie)',
106
+ default: 'line'
107
+ },
108
+ theme: {
109
+ type: 'string',
110
+ description: 'Visual theme',
111
+ default: 'light'
112
+ }
113
+ },
114
+ size: ['800px', '400px'], // Preferred iframe size
115
+ annotations: {
116
+ audience: ['user', 'assistant'],
117
+ priority: 0.8
118
+ }
119
+ })
120
+ ```
121
+
122
+ ## Widget Development
123
+
124
+ ### 1. Create Your Widget Component
125
+
126
+ ```typescript
127
+ // resources/my-widget.tsx
128
+ import React, { useState, useEffect } from 'react'
129
+ import { createRoot } from 'react-dom/client'
130
+
131
+ interface MyWidgetProps {
132
+ initialData?: any
133
+ theme?: 'light' | 'dark'
134
+ }
135
+
136
+ const MyWidget: React.FC<MyWidgetProps> = ({
137
+ initialData = [],
138
+ theme = 'light'
139
+ }) => {
140
+ const [data, setData] = useState(initialData)
141
+
142
+ // Load props from URL query parameters
143
+ useEffect(() => {
144
+ const params = new URLSearchParams(window.location.search)
145
+
146
+ const dataParam = params.get('initialData')
147
+ if (dataParam) {
148
+ try {
149
+ setData(JSON.parse(dataParam))
150
+ } catch (e) {
151
+ console.error('Error parsing data:', e)
152
+ }
153
+ }
154
+
155
+ const themeParam = params.get('theme')
156
+ if (themeParam) {
157
+ // Apply theme
158
+ }
159
+ }, [])
160
+
161
+ return (
162
+ <div className={`widget theme-${theme}`}>
163
+ {/* Your widget UI */}
164
+ </div>
165
+ )
166
+ }
167
+
168
+ // Mount the widget
169
+ const container = document.getElementById('widget-root')
170
+ if (container) {
171
+ createRoot(container).render(<MyWidget />)
172
+ }
173
+ ```
174
+
175
+ ### 2. Register with UIResource
176
+
177
+ ```typescript
178
+ // src/server.ts
179
+ server.uiResource({
180
+ name: 'my-widget',
181
+ widget: 'my-widget',
182
+ title: 'My Custom Widget',
183
+ description: 'A custom interactive widget',
184
+ props: {
185
+ initialData: {
186
+ type: 'array',
187
+ description: 'Initial data for the widget',
188
+ required: false
189
+ },
190
+ theme: {
191
+ type: 'string',
192
+ description: 'Widget theme',
193
+ default: 'light'
194
+ }
195
+ },
196
+ size: ['600px', '400px']
197
+ })
198
+ ```
199
+
200
+ ## How It Works
201
+
202
+ ### Tool Registration
203
+ When you call `uiResource`, it automatically creates a tool:
204
+ - Name: `ui_[widget-name]`
205
+ - Accepts all props as parameters
206
+ - Returns both text description and UIResource object
207
+
208
+ ### Resource Registration
209
+ Also creates a resource:
210
+ - URI: `ui://widget/[widget-name]`
211
+ - Returns UIResource with default prop values
212
+ - Discoverable by MCP clients
213
+
214
+ ### Parameter Passing
215
+ Tool parameters are automatically:
216
+ 1. Converted to URL query parameters
217
+ 2. Complex objects are JSON-stringified
218
+ 3. Passed to widget via iframe URL
219
+
220
+ ## Advanced Examples
221
+
222
+ ### Multiple Widgets
223
+
224
+ ```typescript
225
+ const widgets = [
226
+ {
227
+ name: 'todo-list',
228
+ widget: 'todo-list',
229
+ title: 'Todo List',
230
+ props: {
231
+ items: { type: 'array', default: [] }
232
+ }
233
+ },
234
+ {
235
+ name: 'calendar',
236
+ widget: 'calendar',
237
+ title: 'Calendar',
238
+ props: {
239
+ date: { type: 'string', required: false }
240
+ }
241
+ }
242
+ ]
243
+
244
+ // Register all widgets
245
+ widgets.forEach(widget => server.uiResource(widget))
246
+ ```
247
+
248
+ ### Mixed Registration
249
+
250
+ ```typescript
251
+ // UIResource for widgets
252
+ server.uiResource({
253
+ name: 'dashboard',
254
+ widget: 'dashboard',
255
+ title: 'Analytics Dashboard'
256
+ })
257
+
258
+ // Traditional tool for actions
259
+ server.tool({
260
+ name: 'calculate',
261
+ description: 'Perform calculations',
262
+ fn: async (params) => { /* ... */ }
263
+ })
264
+
265
+ // Traditional resource for data
266
+ server.resource({
267
+ name: 'config',
268
+ uri: 'config://app',
269
+ mimeType: 'application/json',
270
+ fn: async () => { /* ... */ }
271
+ })
272
+ ```
273
+
274
+ ## API Reference
275
+
276
+ ### `server.uiResource(definition)`
277
+
278
+ #### Parameters
279
+
280
+ - `definition: UIResourceDefinition`
281
+ - `name: string` - Resource identifier
282
+ - `widget: string` - Widget directory name
283
+ - `title?: string` - Human-readable title
284
+ - `description?: string` - Widget description
285
+ - `props?: WidgetProps` - Widget properties configuration
286
+ - `size?: [string, string]` - Preferred iframe size
287
+ - `annotations?: ResourceAnnotations` - Discovery hints
288
+
289
+ #### WidgetProps
290
+
291
+ Each prop can have:
292
+ - `type: 'string' | 'number' | 'boolean' | 'object' | 'array'`
293
+ - `required?: boolean` - Whether the prop is required
294
+ - `default?: any` - Default value if not provided
295
+ - `description?: string` - Prop description
296
+
297
+ ## Testing Your Widgets
298
+
299
+ ### Via Inspector UI
300
+ 1. Start the server: `npm run dev`
301
+ 2. Open: `http://localhost:3000/inspector`
302
+ 3. Test tools and resources
303
+
304
+ ### Direct Browser Access
305
+ Visit: `http://localhost:3000/mcp-use/widgets/[widget-name]`
306
+
307
+ ### Via MCP Client
308
+ ```typescript
309
+ // Call as tool
310
+ const result = await client.callTool('ui_kanban-board', {
311
+ initialTasks: [...],
312
+ theme: 'dark'
313
+ })
314
+
315
+ // Access as resource
316
+ const resource = await client.readResource('ui://widget/kanban-board')
317
+ ```
318
+
319
+ ## Benefits of UIResource
320
+
321
+ ✅ **Simplified API** - One method instead of two
322
+ ✅ **Automatic Wiring** - Props become tool inputs automatically
323
+ ✅ **Type Safety** - Full TypeScript support
324
+ ✅ **MCP-UI Compatible** - Works with all MCP-UI clients
325
+ ✅ **DRY Principle** - No duplicate UIResource creation
326
+ ✅ **Discoverable** - Both tools and resources are listed
327
+
328
+ ## Troubleshooting
329
+
330
+ ### Widget Not Loading
331
+ - Ensure widget exists in `dist/resources/mcp-use/widgets/`
332
+ - Check server console for errors
333
+ - Verify widget is registered with `uiResource()`
334
+
335
+ ### Props Not Passed
336
+ - Check URL parameters in browser DevTools
337
+ - Ensure prop names match exactly
338
+ - Complex objects must be JSON-stringified
339
+
340
+ ### Type Errors
341
+ - Import types: `import type { UIResourceDefinition } from 'mcp-use/server'`
342
+ - Ensure mcp-use is updated to latest version
343
+
344
+ ## Migration from Old Pattern
345
+
346
+ If you have existing code using separate tool/resource:
347
+
348
+ ```typescript
349
+ // Old pattern
350
+ server.tool({ name: 'show-widget', /* ... */ })
351
+ server.resource({ uri: 'ui://widget', /* ... */ })
352
+
353
+ // New pattern - replace both with:
354
+ server.uiResource({
355
+ name: 'widget',
356
+ widget: 'widget',
357
+ // ... consolidated configuration
358
+ })
359
+ ```
360
+
361
+ ## Future Enhancements
362
+
363
+ Coming soon:
364
+ - Automatic widget discovery from filesystem
365
+ - Widget manifests (widget.json)
366
+ - Prop extraction from TypeScript interfaces
367
+ - Build-time optimization
368
+
369
+ ## Learn More
370
+
371
+ - [MCP Documentation](https://modelcontextprotocol.io)
372
+ - [MCP-UI Documentation](https://github.com/idosal/mcp-ui)
373
+ - [mcp-use Documentation](https://github.com/pyroprompt/mcp-use)
374
+ - [React Documentation](https://react.dev/)
375
+
376
+ Happy widget building! 🚀
@@ -0,0 +1,12 @@
1
+ /**
2
+ * MCP Server Entry Point
3
+ *
4
+ * This file serves as the main entry point for the MCP server application.
5
+ * It re-exports all functionality from the server implementation, allowing
6
+ * the CLI and other tools to locate and start the server.
7
+ *
8
+ * The server is automatically started when this module is imported, making
9
+ * it suitable for both direct execution and programmatic usage.
10
+ */
11
+ export * from './src/server.js'
12
+
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "mcp-uiresource-server",
3
+ "type": "module",
4
+ "version": "1.0.0",
5
+ "description": "MCP server with UIResource widget integration",
6
+ "author": "",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "mcp",
10
+ "server",
11
+ "uiresource",
12
+ "ui",
13
+ "react",
14
+ "widgets",
15
+ "ai",
16
+ "tools",
17
+ "mcp-ui"
18
+ ],
19
+ "main": "dist/index.js",
20
+ "scripts": {
21
+ "build": "mcp-use build",
22
+ "dev": "mcp-use dev",
23
+ "start": "mcp-use start"
24
+ },
25
+ "dependencies": {
26
+ "@mcp-ui/server": "^5.11.0",
27
+ "cors": "^2.8.5",
28
+ "express": "^4.18.0",
29
+ "mcp-use": "workspace:*"
30
+ },
31
+ "devDependencies": {
32
+ "@mcp-use/cli": "workspace:*",
33
+ "@mcp-use/inspector": "workspace:*",
34
+ "@types/cors": "^2.8.0",
35
+ "@types/express": "^4.17.0",
36
+ "@types/node": "^20.0.0",
37
+ "@types/react": "^18.0.0",
38
+ "@types/react-dom": "^18.0.0",
39
+ "concurrently": "^8.0.0",
40
+ "esbuild": "^0.23.0",
41
+ "globby": "^14.0.2",
42
+ "react": "^18.0.0",
43
+ "react-dom": "^18.0.0",
44
+ "tsx": "^4.0.0",
45
+ "typescript": "^5.0.0"
46
+ }
47
+ }
@@ -0,0 +1,306 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+
4
+ interface Task {
5
+ id: string
6
+ title: string
7
+ description: string
8
+ status: 'todo' | 'in-progress' | 'done'
9
+ priority: 'low' | 'medium' | 'high'
10
+ assignee?: string
11
+ }
12
+
13
+ interface KanbanBoardProps {
14
+ initialTasks?: Task[]
15
+ }
16
+
17
+ const KanbanBoard: React.FC<KanbanBoardProps> = ({ initialTasks = [] }) => {
18
+ const [tasks, setTasks] = useState<Task[]>(initialTasks)
19
+ const [newTask, setNewTask] = useState({ title: '', description: '', priority: 'medium' as Task['priority'] })
20
+
21
+ // Load tasks from URL parameters or use defaults
22
+ useEffect(() => {
23
+ const urlParams = new URLSearchParams(window.location.search)
24
+ const tasksParam = urlParams.get('tasks')
25
+
26
+ if (tasksParam) {
27
+ try {
28
+ const parsedTasks = JSON.parse(decodeURIComponent(tasksParam))
29
+ setTasks(parsedTasks)
30
+ }
31
+ catch (error) {
32
+ console.error('Error parsing tasks from URL:', error)
33
+ }
34
+ }
35
+ else {
36
+ // Default tasks for demo
37
+ setTasks([
38
+ { id: '1', title: 'Design UI mockups', description: 'Create wireframes for the new dashboard', status: 'todo', priority: 'high', assignee: 'Alice' },
39
+ { id: '2', title: 'Implement authentication', description: 'Add login and registration functionality', status: 'in-progress', priority: 'high', assignee: 'Bob' },
40
+ { id: '3', title: 'Write documentation', description: 'Document the API endpoints', status: 'done', priority: 'medium', assignee: 'Charlie' },
41
+ { id: '4', title: 'Setup CI/CD', description: 'Configure automated testing and deployment', status: 'todo', priority: 'medium' },
42
+ { id: '5', title: 'Code review', description: 'Review pull requests from the team', status: 'in-progress', priority: 'low', assignee: 'David' },
43
+ ])
44
+ }
45
+ }, [])
46
+
47
+ const addTask = () => {
48
+ if (newTask.title.trim()) {
49
+ const task: Task = {
50
+ id: Date.now().toString(),
51
+ title: newTask.title,
52
+ description: newTask.description,
53
+ status: 'todo',
54
+ priority: newTask.priority,
55
+ }
56
+ setTasks([...tasks, task])
57
+ setNewTask({ title: '', description: '', priority: 'medium' })
58
+ }
59
+ }
60
+
61
+ const moveTask = (taskId: string, newStatus: Task['status']) => {
62
+ setTasks(tasks.map(task =>
63
+ task.id === taskId ? { ...task, status: newStatus } : task,
64
+ ))
65
+ }
66
+
67
+ const deleteTask = (taskId: string) => {
68
+ setTasks(tasks.filter(task => task.id !== taskId))
69
+ }
70
+
71
+ const getTasksByStatus = (status: Task['status']) => {
72
+ return tasks.filter(task => task.status === status)
73
+ }
74
+
75
+ const getPriorityColor = (priority: Task['priority']) => {
76
+ switch (priority) {
77
+ case 'high': return '#ff4757'
78
+ case 'medium': return '#ffa502'
79
+ case 'low': return '#2ed573'
80
+ default: return '#57606f'
81
+ }
82
+ }
83
+
84
+ const columns = [
85
+ { id: 'todo', title: 'To Do', color: '#57606f' },
86
+ { id: 'in-progress', title: 'In Progress', color: '#ffa502' },
87
+ { id: 'done', title: 'Done', color: '#2ed573' },
88
+ ] as const
89
+
90
+ return (
91
+ <div style={{ padding: '20px' }}>
92
+ <div style={{ marginBottom: '30px' }}>
93
+ <h1 style={{ margin: '0 0 20px 0', color: '#2c3e50' }}>Kanban Board</h1>
94
+
95
+ {/* Add new task form */}
96
+ <div style={{
97
+ background: 'white',
98
+ padding: '20px',
99
+ borderRadius: '8px',
100
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
101
+ marginBottom: '20px',
102
+ }}
103
+ >
104
+ <h3 style={{ margin: '0 0 15px 0' }}>Add New Task</h3>
105
+ <div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
106
+ <input
107
+ type="text"
108
+ placeholder="Task title"
109
+ value={newTask.title}
110
+ onChange={e => setNewTask({ ...newTask, title: e.target.value })}
111
+ style={{
112
+ padding: '8px 12px',
113
+ border: '1px solid #ddd',
114
+ borderRadius: '4px',
115
+ flex: '1',
116
+ minWidth: '200px',
117
+ }}
118
+ />
119
+ <input
120
+ type="text"
121
+ placeholder="Description"
122
+ value={newTask.description}
123
+ onChange={e => setNewTask({ ...newTask, description: e.target.value })}
124
+ style={{
125
+ padding: '8px 12px',
126
+ border: '1px solid #ddd',
127
+ borderRadius: '4px',
128
+ flex: '1',
129
+ minWidth: '200px',
130
+ }}
131
+ />
132
+ <select
133
+ title="Priority"
134
+ value={newTask.priority}
135
+ onChange={e => setNewTask({ ...newTask, priority: e.target.value as Task['priority'] })}
136
+ style={{
137
+ padding: '8px 12px',
138
+ border: '1px solid #ddd',
139
+ borderRadius: '4px',
140
+ }}
141
+ >
142
+ <option value="low">Low Priority</option>
143
+ <option value="medium">Medium Priority</option>
144
+ <option value="high">High Priority</option>
145
+ </select>
146
+ <button
147
+ onClick={addTask}
148
+ style={{
149
+ padding: '8px 16px',
150
+ background: '#3498db',
151
+ color: 'white',
152
+ border: 'none',
153
+ borderRadius: '4px',
154
+ cursor: 'pointer',
155
+ }}
156
+ >
157
+ Add Task
158
+ </button>
159
+ </div>
160
+ </div>
161
+ </div>
162
+
163
+ {/* Kanban columns */}
164
+ <div style={{ display: 'flex', gap: '20px', overflowX: 'auto' }}>
165
+ {columns.map(column => (
166
+ <div
167
+ key={column.id}
168
+ style={{
169
+ flex: '1',
170
+ minWidth: '300px',
171
+ background: 'white',
172
+ borderRadius: '8px',
173
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
174
+ overflow: 'hidden',
175
+ }}
176
+ >
177
+ <div
178
+ style={{
179
+ background: column.color,
180
+ color: 'white',
181
+ padding: '15px 20px',
182
+ fontWeight: 'bold',
183
+ display: 'flex',
184
+ justifyContent: 'space-between',
185
+ alignItems: 'center',
186
+ }}
187
+ >
188
+ <span>{column.title}</span>
189
+ <span style={{
190
+ background: 'rgba(255,255,255,0.2)',
191
+ padding: '4px 8px',
192
+ borderRadius: '12px',
193
+ fontSize: '12px',
194
+ }}
195
+ >
196
+ {getTasksByStatus(column.id).length}
197
+ </span>
198
+ </div>
199
+
200
+ <div style={{ padding: '20px', minHeight: '400px' }}>
201
+ {getTasksByStatus(column.id).map(task => (
202
+ <div
203
+ key={task.id}
204
+ style={{
205
+ background: '#f8f9fa',
206
+ border: '1px solid #e9ecef',
207
+ borderRadius: '6px',
208
+ padding: '15px',
209
+ marginBottom: '10px',
210
+ cursor: 'grab',
211
+ }}
212
+ draggable
213
+ onDragStart={(e) => {
214
+ e.dataTransfer.setData('text/plain', task.id)
215
+ }}
216
+ onDragOver={e => e.preventDefault()}
217
+ onDrop={(e) => {
218
+ e.preventDefault()
219
+ const taskId = e.dataTransfer.getData('text/plain')
220
+ if (taskId === task.id) {
221
+ // Move to next column
222
+ const currentIndex = columns.findIndex(col => col.id === column.id)
223
+ const nextColumn = columns[currentIndex + 1]
224
+ if (nextColumn) {
225
+ moveTask(taskId, nextColumn.id)
226
+ }
227
+ }
228
+ }}
229
+ >
230
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '8px' }}>
231
+ <h4 style={{ margin: '0', color: '#2c3e50' }}>{task.title}</h4>
232
+ <button
233
+ onClick={() => deleteTask(task.id)}
234
+ style={{
235
+ background: 'none',
236
+ border: 'none',
237
+ color: '#e74c3c',
238
+ cursor: 'pointer',
239
+ fontSize: '16px',
240
+ }}
241
+ >
242
+ ×
243
+ </button>
244
+ </div>
245
+
246
+ {task.description && (
247
+ <p style={{ margin: '0 0 10px 0', color: '#6c757d', fontSize: '14px' }}>
248
+ {task.description}
249
+ </p>
250
+ )}
251
+
252
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
253
+ <div
254
+ style={{
255
+ background: getPriorityColor(task.priority),
256
+ color: 'white',
257
+ padding: '2px 8px',
258
+ borderRadius: '12px',
259
+ fontSize: '12px',
260
+ fontWeight: 'bold',
261
+ }}
262
+ >
263
+ {task.priority.toUpperCase()}
264
+ </div>
265
+
266
+ {task.assignee && (
267
+ <span style={{
268
+ background: '#e9ecef',
269
+ padding: '2px 8px',
270
+ borderRadius: '12px',
271
+ fontSize: '12px',
272
+ color: '#495057',
273
+ }}
274
+ >
275
+ {task.assignee}
276
+ </span>
277
+ )}
278
+ </div>
279
+ </div>
280
+ ))}
281
+
282
+ {getTasksByStatus(column.id).length === 0 && (
283
+ <div style={{
284
+ textAlign: 'center',
285
+ color: '#6c757d',
286
+ padding: '40px 20px',
287
+ fontStyle: 'italic',
288
+ }}
289
+ >
290
+ No tasks in this column
291
+ </div>
292
+ )}
293
+ </div>
294
+ </div>
295
+ ))}
296
+ </div>
297
+ </div>
298
+ )
299
+ }
300
+
301
+ // Mount the component
302
+ const container = document.getElementById('widget-root')
303
+ if (container) {
304
+ const root = createRoot(container)
305
+ root.render(<KanbanBoard />)
306
+ }
@@ -0,0 +1,425 @@
1
+ import { createMCPServer } from 'mcp-use/server'
2
+ import type {
3
+ ExternalUrlUIResource,
4
+ RawHtmlUIResource,
5
+ RemoteDomUIResource
6
+ } from 'mcp-use/server'
7
+
8
+ // Create an MCP server with UIResource support
9
+ const server = createMCPServer('uiresource-mcp-server', {
10
+ version: '1.0.0',
11
+ description: 'MCP server demonstrating all UIResource types',
12
+ })
13
+
14
+ const PORT = process.env.PORT || 3000
15
+
16
+ /**
17
+ * ════════════════════════════════════════════════════════════════════
18
+ * Type 1: External URL (Iframe Widget)
19
+ * ════════════════════════════════════════════════════════════════════
20
+ *
21
+ * Serves a widget from your local filesystem via iframe.
22
+ * Best for: Complex interactive widgets with their own assets
23
+ *
24
+ * This automatically:
25
+ * 1. Creates a tool (ui_kanban-board) that accepts parameters
26
+ * 2. Creates a resource (ui://widget/kanban-board) for static access
27
+ * 3. Serves the widget from dist/resources/mcp-use/widgets/kanban-board/
28
+ */
29
+ server.uiResource({
30
+ type: 'externalUrl',
31
+ name: 'kanban-board',
32
+ widget: 'kanban-board',
33
+ title: 'Kanban Board',
34
+ description: 'Interactive task management board with drag-and-drop support',
35
+ props: {
36
+ initialTasks: {
37
+ type: 'array',
38
+ description: 'Initial tasks to display on the board',
39
+ required: false,
40
+ },
41
+ theme: {
42
+ type: 'string',
43
+ description: 'Visual theme for the board (light/dark)',
44
+ required: false,
45
+ default: 'light'
46
+ },
47
+ columns: {
48
+ type: 'array',
49
+ description: 'Column configuration for the board',
50
+ required: false,
51
+ }
52
+ }
53
+ } satisfies ExternalUrlUIResource)
54
+
55
+ /**
56
+ * ════════════════════════════════════════════════════════════════════
57
+ * Type 2: Raw HTML
58
+ * ════════════════════════════════════════════════════════════════════
59
+ *
60
+ * Renders HTML content directly without an iframe.
61
+ * Best for: Simple visualizations, status displays, formatted text
62
+ *
63
+ * This creates:
64
+ * - Tool: ui_welcome-card
65
+ * - Resource: ui://widget/welcome-card
66
+ */
67
+ server.uiResource({
68
+ type: 'rawHtml',
69
+ name: 'welcome-card',
70
+ title: 'Welcome Message',
71
+ description: 'A welcoming card with server information',
72
+ htmlContent: `
73
+ <!DOCTYPE html>
74
+ <html>
75
+ <head>
76
+ <meta charset="UTF-8">
77
+ <style>
78
+ body {
79
+ margin: 0;
80
+ padding: 20px;
81
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
82
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
83
+ color: white;
84
+ }
85
+ .card {
86
+ background: rgba(255, 255, 255, 0.1);
87
+ backdrop-filter: blur(10px);
88
+ border-radius: 16px;
89
+ padding: 30px;
90
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
91
+ border: 1px solid rgba(255, 255, 255, 0.18);
92
+ }
93
+ h1 {
94
+ margin: 0 0 10px 0;
95
+ font-size: 2em;
96
+ }
97
+ p {
98
+ margin: 10px 0;
99
+ opacity: 0.9;
100
+ }
101
+ .stats {
102
+ display: flex;
103
+ gap: 20px;
104
+ margin-top: 20px;
105
+ }
106
+ .stat {
107
+ background: rgba(255, 255, 255, 0.1);
108
+ padding: 15px;
109
+ border-radius: 8px;
110
+ flex: 1;
111
+ }
112
+ .stat-value {
113
+ font-size: 1.5em;
114
+ font-weight: bold;
115
+ }
116
+ .stat-label {
117
+ font-size: 0.9em;
118
+ opacity: 0.8;
119
+ }
120
+ </style>
121
+ </head>
122
+ <body>
123
+ <div class="card">
124
+ <h1>🎉 Welcome to MCP-UI</h1>
125
+ <p>Your server is running and ready to serve interactive widgets!</p>
126
+
127
+ <div class="stats">
128
+ <div class="stat">
129
+ <div class="stat-value">3</div>
130
+ <div class="stat-label">Widget Types</div>
131
+ </div>
132
+ <div class="stat">
133
+ <div class="stat-value">∞</div>
134
+ <div class="stat-label">Possibilities</div>
135
+ </div>
136
+ <div class="stat">
137
+ <div class="stat-value">⚡</div>
138
+ <div class="stat-label">Fast & Simple</div>
139
+ </div>
140
+ </div>
141
+
142
+ <p style="margin-top: 20px; font-size: 0.9em;">
143
+ Server: <strong>uiresource-mcp-server v1.0.0</strong><br>
144
+ Port: <strong>${PORT}</strong>
145
+ </p>
146
+ </div>
147
+ </body>
148
+ </html>
149
+ `,
150
+ encoding: 'text',
151
+ size: ['600px', '400px']
152
+ } satisfies RawHtmlUIResource)
153
+
154
+ /**
155
+ * ════════════════════════════════════════════════════════════════════
156
+ * Type 3: Remote DOM (React Components)
157
+ * ════════════════════════════════════════════════════════════════════
158
+ *
159
+ * Uses Remote DOM to render interactive components.
160
+ * Best for: Lightweight interactive UIs using MCP-UI React components
161
+ *
162
+ * This creates:
163
+ * - Tool: ui_quick-poll
164
+ * - Resource: ui://widget/quick-poll
165
+ */
166
+ server.uiResource({
167
+ type: 'remoteDom',
168
+ name: 'quick-poll',
169
+ title: 'Quick Poll',
170
+ description: 'Create instant polls with interactive voting',
171
+ script: `
172
+ // Remote DOM script for quick-poll widget
173
+ // Note: Remote DOM only supports registered MCP-UI components like ui-stack, ui-text, ui-button
174
+ // Standard HTML elements (div, h2, p, etc.) are NOT available
175
+
176
+ // Get props (passed from tool parameters)
177
+ const props = ${JSON.stringify({ question: 'What is your favorite framework?', options: ['React', 'Vue', 'Svelte', 'Angular'] })};
178
+
179
+ // Create main container stack (vertical layout)
180
+ const container = document.createElement('ui-stack');
181
+ container.setAttribute('direction', 'column');
182
+ container.setAttribute('spacing', 'medium');
183
+ container.setAttribute('padding', 'large');
184
+
185
+ // Title text
186
+ const title = document.createElement('ui-text');
187
+ title.setAttribute('size', 'xlarge');
188
+ title.setAttribute('weight', 'bold');
189
+ title.textContent = '📊 Quick Poll';
190
+ container.appendChild(title);
191
+
192
+ // Description text
193
+ const description = document.createElement('ui-text');
194
+ description.textContent = 'Cast your vote below!';
195
+ container.appendChild(description);
196
+
197
+ // Question text
198
+ const questionText = document.createElement('ui-text');
199
+ questionText.setAttribute('size', 'large');
200
+ questionText.setAttribute('weight', 'semibold');
201
+ questionText.textContent = props.question || 'What is your preference?';
202
+ container.appendChild(questionText);
203
+
204
+ // Button stack (horizontal layout)
205
+ const buttonStack = document.createElement('ui-stack');
206
+ buttonStack.setAttribute('direction', 'row');
207
+ buttonStack.setAttribute('spacing', 'small');
208
+ buttonStack.setAttribute('wrap', 'true');
209
+
210
+ // Create vote tracking
211
+ const votes = {};
212
+ let feedbackText = null;
213
+
214
+ // Create buttons for each option
215
+ const options = props.options || ['Option 1', 'Option 2', 'Option 3'];
216
+ options.forEach((option) => {
217
+ const button = document.createElement('ui-button');
218
+ button.setAttribute('label', option);
219
+ button.setAttribute('variant', 'secondary');
220
+
221
+ button.addEventListener('press', () => {
222
+ // Record vote
223
+ votes[option] = (votes[option] || 0) + 1;
224
+
225
+ // Send vote to parent (for tracking)
226
+ window.parent.postMessage({
227
+ type: 'tool',
228
+ payload: {
229
+ toolName: 'record_vote',
230
+ params: {
231
+ question: props.question,
232
+ selected: option,
233
+ votes: votes
234
+ }
235
+ }
236
+ }, '*');
237
+
238
+ // Update or create feedback text
239
+ if (feedbackText) {
240
+ feedbackText.textContent = \`✓ Voted for \${option}! (Total votes: \${votes[option]})\`;
241
+ } else {
242
+ feedbackText = document.createElement('ui-text');
243
+ feedbackText.setAttribute('emphasis', 'high');
244
+ feedbackText.textContent = \`✓ Voted for \${option}!\`;
245
+ container.appendChild(feedbackText);
246
+ }
247
+ });
248
+
249
+ buttonStack.appendChild(button);
250
+ });
251
+
252
+ container.appendChild(buttonStack);
253
+
254
+ // Results section
255
+ const resultsTitle = document.createElement('ui-text');
256
+ resultsTitle.setAttribute('size', 'medium');
257
+ resultsTitle.setAttribute('weight', 'semibold');
258
+ resultsTitle.textContent = 'Vote to see results!';
259
+ container.appendChild(resultsTitle);
260
+
261
+ // Append to root
262
+ root.appendChild(container);
263
+ `,
264
+ framework: 'react',
265
+ encoding: 'text',
266
+ size: ['500px', '450px'],
267
+ props: {
268
+ question: {
269
+ type: 'string',
270
+ description: 'The poll question',
271
+ default: 'What is your favorite framework?'
272
+ },
273
+ options: {
274
+ type: 'array',
275
+ description: 'Poll options',
276
+ default: ['React', 'Vue', 'Svelte']
277
+ }
278
+ }
279
+ } satisfies RemoteDomUIResource)
280
+
281
+ /**
282
+ * ════════════════════════════════════════════════════════════════════
283
+ * Traditional MCP Tools and Resources
284
+ * ════════════════════════════════════════════════════════════════════
285
+ *
286
+ * You can mix UIResources with traditional MCP tools and resources
287
+ */
288
+
289
+ server.tool({
290
+ name: 'get-widget-info',
291
+ description: 'Get information about available UI widgets',
292
+ fn: async () => {
293
+ const widgets = [
294
+ {
295
+ name: 'kanban-board',
296
+ type: 'externalUrl',
297
+ tool: 'ui_kanban-board',
298
+ resource: 'ui://widget/kanban-board',
299
+ url: `http://localhost:${PORT}/mcp-use/widgets/kanban-board`
300
+ },
301
+ {
302
+ name: 'welcome-card',
303
+ type: 'rawHtml',
304
+ tool: 'ui_welcome-card',
305
+ resource: 'ui://widget/welcome-card'
306
+ },
307
+ {
308
+ name: 'quick-poll',
309
+ type: 'remoteDom',
310
+ tool: 'ui_quick-poll',
311
+ resource: 'ui://widget/quick-poll'
312
+ }
313
+ ]
314
+
315
+ return {
316
+ content: [{
317
+ type: 'text',
318
+ text: `Available UI Widgets:\n\n${widgets.map(w =>
319
+ `📦 ${w.name} (${w.type})\n` +
320
+ ` Tool: ${w.tool}\n` +
321
+ ` Resource: ${w.resource}\n` +
322
+ (w.url ? ` Browser: ${w.url}\n` : '')
323
+ ).join('\n')}\n` +
324
+ `\nTypes Explained:\n` +
325
+ `• externalUrl: Iframe widget from filesystem\n` +
326
+ `• rawHtml: Direct HTML rendering\n` +
327
+ `• remoteDom: React/Web Components scripting`
328
+ }]
329
+ }
330
+ }
331
+ })
332
+
333
+ server.resource({
334
+ name: 'server-config',
335
+ uri: 'config://server',
336
+ title: 'Server Configuration',
337
+ description: 'Current server configuration and status',
338
+ mimeType: 'application/json',
339
+ fn: async () => ({
340
+ contents: [{
341
+ uri: 'config://server',
342
+ mimeType: 'application/json',
343
+ text: JSON.stringify({
344
+ port: PORT,
345
+ version: '1.0.0',
346
+ widgets: {
347
+ total: 3,
348
+ types: {
349
+ externalUrl: ['kanban-board'],
350
+ rawHtml: ['welcome-card'],
351
+ remoteDom: ['quick-poll']
352
+ },
353
+ baseUrl: `http://localhost:${PORT}/mcp-use/widgets/`
354
+ },
355
+ endpoints: {
356
+ mcp: `http://localhost:${PORT}/mcp`,
357
+ inspector: `http://localhost:${PORT}/inspector`,
358
+ widgets: `http://localhost:${PORT}/mcp-use/widgets/`
359
+ }
360
+ }, null, 2)
361
+ }]
362
+ })
363
+ })
364
+
365
+ // Start the server
366
+ server.listen(PORT)
367
+
368
+ // Display helpful startup message
369
+ console.log(`
370
+ ╔═══════════════════════════════════════════════════════════════╗
371
+ ║ 🎨 UIResource MCP Server (All Types) ║
372
+ ╚═══════════════════════════════════════════════════════════════╝
373
+
374
+ Server is running on port ${PORT}
375
+
376
+ 📍 Endpoints:
377
+ MCP Protocol: http://localhost:${PORT}/mcp
378
+ Inspector UI: http://localhost:${PORT}/inspector
379
+ Widgets Base: http://localhost:${PORT}/mcp-use/widgets/
380
+
381
+ 🎯 Available UIResources (3 types demonstrated):
382
+
383
+ 1️⃣ External URL Widget (Iframe)
384
+ • kanban-board
385
+ Tool: ui_kanban-board
386
+ Resource: ui://widget/kanban-board
387
+ Browser: http://localhost:${PORT}/mcp-use/widgets/kanban-board
388
+
389
+ 2️⃣ Raw HTML Widget (Direct Rendering)
390
+ • welcome-card
391
+ Tool: ui_welcome-card
392
+ Resource: ui://widget/welcome-card
393
+
394
+ 3️⃣ Remote DOM Widget (React Components)
395
+ • quick-poll
396
+ Tool: ui_quick-poll
397
+ Resource: ui://widget/quick-poll
398
+
399
+ 📝 Usage Examples:
400
+
401
+ // External URL - Call with dynamic parameters
402
+ await client.callTool('ui_kanban-board', {
403
+ initialTasks: [{id: 1, title: 'Task 1'}],
404
+ theme: 'dark'
405
+ })
406
+
407
+ // Raw HTML - Access as resource
408
+ await client.readResource('ui://widget/welcome-card')
409
+
410
+ // Remote DOM - Interactive component
411
+ await client.callTool('ui_quick-poll', {
412
+ question: 'Favorite color?',
413
+ options: ['Red', 'Blue', 'Green']
414
+ })
415
+
416
+ 💡 Tip: Open the Inspector UI to test all widget types interactively!
417
+ `)
418
+
419
+ // Handle graceful shutdown
420
+ process.on('SIGINT', () => {
421
+ console.log('\n\nShutting down server...')
422
+ process.exit(0)
423
+ })
424
+
425
+ export default server
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "jsx": "react-jsx",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowJs": true,
8
+ "strict": true,
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "outDir": "./dist",
12
+ "sourceMap": true,
13
+ "allowSyntheticDefaultImports": true,
14
+ "esModuleInterop": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "skipLibCheck": true
17
+ },
18
+ "include": ["index.ts", "src/**/*", "resources/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mcp-use-app",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "description": "Create MCP-Use apps with one command",
6
6
  "author": "mcp-use, Inc.",