create-mcp-use-app 0.2.1

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.
@@ -0,0 +1,408 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+
4
+ interface Todo {
5
+ id: string
6
+ text: string
7
+ completed: boolean
8
+ priority: 'low' | 'medium' | 'high'
9
+ dueDate?: string
10
+ category?: string
11
+ }
12
+
13
+ interface TodoListProps {
14
+ initialTodos?: Todo[]
15
+ }
16
+
17
+ const TodoList: React.FC<TodoListProps> = ({ initialTodos = [] }) => {
18
+ const [todos, setTodos] = useState<Todo[]>(initialTodos)
19
+ const [newTodo, setNewTodo] = useState('')
20
+ const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all')
21
+ const [sortBy, setSortBy] = useState<'priority' | 'dueDate' | 'created'>('priority')
22
+
23
+ // Load todos from URL parameters or use defaults
24
+ useEffect(() => {
25
+ const urlParams = new URLSearchParams(window.location.search)
26
+ const todosParam = urlParams.get('todos')
27
+
28
+ if (todosParam) {
29
+ try {
30
+ const parsedTodos = JSON.parse(decodeURIComponent(todosParam))
31
+ setTodos(parsedTodos)
32
+ }
33
+ catch (error) {
34
+ console.error('Error parsing todos from URL:', error)
35
+ }
36
+ }
37
+ else {
38
+ // Default todos for demo
39
+ setTodos([
40
+ { id: '1', text: 'Complete project proposal', completed: false, priority: 'high', dueDate: '2024-01-15', category: 'Work' },
41
+ { id: '2', text: 'Buy groceries', completed: false, priority: 'medium', dueDate: '2024-01-12', category: 'Personal' },
42
+ { id: '3', text: 'Call dentist', completed: true, priority: 'low', category: 'Health' },
43
+ { id: '4', text: 'Read React documentation', completed: false, priority: 'medium', category: 'Learning' },
44
+ { id: '5', text: 'Plan weekend trip', completed: false, priority: 'low', dueDate: '2024-01-20', category: 'Personal' },
45
+ ])
46
+ }
47
+ }, [])
48
+
49
+ const addTodo = () => {
50
+ if (newTodo.trim()) {
51
+ const todo: Todo = {
52
+ id: Date.now().toString(),
53
+ text: newTodo,
54
+ completed: false,
55
+ priority: 'medium',
56
+ }
57
+ setTodos([...todos, todo])
58
+ setNewTodo('')
59
+ }
60
+ }
61
+
62
+ const toggleTodo = (id: string) => {
63
+ setTodos(todos.map(todo =>
64
+ todo.id === id ? { ...todo, completed: !todo.completed } : todo,
65
+ ))
66
+ }
67
+
68
+ const deleteTodo = (id: string) => {
69
+ setTodos(todos.filter(todo => todo.id !== id))
70
+ }
71
+
72
+ const updateTodoPriority = (id: string, priority: Todo['priority']) => {
73
+ setTodos(todos.map(todo =>
74
+ todo.id === id ? { ...todo, priority } : todo,
75
+ ))
76
+ }
77
+
78
+ const getFilteredTodos = () => {
79
+ let filtered = todos
80
+
81
+ // Filter by status
82
+ switch (filter) {
83
+ case 'active':
84
+ filtered = filtered.filter(todo => !todo.completed)
85
+ break
86
+ case 'completed':
87
+ filtered = filtered.filter(todo => todo.completed)
88
+ break
89
+ default:
90
+ break
91
+ }
92
+
93
+ // Sort todos
94
+ return filtered.sort((a, b) => {
95
+ switch (sortBy) {
96
+ case 'priority':
97
+ const priorityOrder = { high: 3, medium: 2, low: 1 }
98
+ return priorityOrder[b.priority] - priorityOrder[a.priority]
99
+ case 'dueDate':
100
+ if (!a.dueDate && !b.dueDate)
101
+ return 0
102
+ if (!a.dueDate)
103
+ return 1
104
+ if (!b.dueDate)
105
+ return -1
106
+ return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime()
107
+ case 'created':
108
+ default:
109
+ return Number.parseInt(b.id) - Number.parseInt(a.id)
110
+ }
111
+ })
112
+ }
113
+
114
+ // const getPriorityColor = (priority: Todo['priority']) => {
115
+ // switch (priority) {
116
+ // case 'high': return '#e74c3c'
117
+ // case 'medium': return '#f39c12'
118
+ // case 'low': return '#27ae60'
119
+ // default: return '#95a5a6'
120
+ // }
121
+ // }
122
+
123
+ const getPriorityIcon = (priority: Todo['priority']) => {
124
+ switch (priority) {
125
+ case 'high': return '🔴'
126
+ case 'medium': return '🟡'
127
+ case 'low': return '🟢'
128
+ default: return '⚪'
129
+ }
130
+ }
131
+
132
+ const completedCount = todos.filter(todo => todo.completed).length
133
+ const totalCount = todos.length
134
+ const progressPercentage = totalCount > 0 ? (completedCount / totalCount) * 100 : 0
135
+
136
+ return (
137
+ <div style={{ padding: '20px' }}>
138
+ <div style={{ marginBottom: '30px' }}>
139
+ <h1 style={{ margin: '0 0 20px 0', color: '#2c3e50' }}>Todo List</h1>
140
+
141
+ {/* Progress bar */}
142
+ <div style={{
143
+ background: 'white',
144
+ padding: '20px',
145
+ borderRadius: '8px',
146
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
147
+ marginBottom: '20px',
148
+ }}
149
+ >
150
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '10px' }}>
151
+ <span style={{ fontWeight: 'bold', color: '#2c3e50' }}>Progress</span>
152
+ <span style={{ color: '#7f8c8d' }}>
153
+ {completedCount}
154
+ {' '}
155
+ of
156
+ {' '}
157
+ {totalCount}
158
+ {' '}
159
+ completed
160
+ </span>
161
+ </div>
162
+ <div style={{
163
+ background: '#ecf0f1',
164
+ borderRadius: '10px',
165
+ height: '10px',
166
+ overflow: 'hidden',
167
+ }}
168
+ >
169
+ <div style={{
170
+ background: 'linear-gradient(90deg, #27ae60, #2ecc71)',
171
+ height: '100%',
172
+ width: `${progressPercentage}%`,
173
+ transition: 'width 0.3s ease',
174
+ }}
175
+ />
176
+ </div>
177
+ </div>
178
+
179
+ {/* Add new todo */}
180
+ <div style={{
181
+ background: 'white',
182
+ padding: '20px',
183
+ borderRadius: '8px',
184
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
185
+ marginBottom: '20px',
186
+ }}
187
+ >
188
+ <div style={{ display: 'flex', gap: '10px' }}>
189
+ <input
190
+ type="text"
191
+ placeholder="Add a new todo..."
192
+ value={newTodo}
193
+ onChange={e => setNewTodo(e.target.value)}
194
+ onKeyPress={e => e.key === 'Enter' && addTodo()}
195
+ style={{
196
+ flex: '1',
197
+ padding: '12px 16px',
198
+ border: '1px solid #ddd',
199
+ borderRadius: '6px',
200
+ fontSize: '16px',
201
+ }}
202
+ />
203
+ <button
204
+ onClick={addTodo}
205
+ style={{
206
+ padding: '12px 24px',
207
+ background: '#3498db',
208
+ color: 'white',
209
+ border: 'none',
210
+ borderRadius: '6px',
211
+ cursor: 'pointer',
212
+ fontSize: '16px',
213
+ fontWeight: 'bold',
214
+ }}
215
+ >
216
+ Add
217
+ </button>
218
+ </div>
219
+ </div>
220
+
221
+ {/* Filters and sorting */}
222
+ <div style={{
223
+ background: 'white',
224
+ padding: '20px',
225
+ borderRadius: '8px',
226
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
227
+ marginBottom: '20px',
228
+ }}
229
+ >
230
+ <div style={{ display: 'flex', gap: '20px', flexWrap: 'wrap', alignItems: 'center' }}>
231
+ <div>
232
+ <label style={{ marginRight: '10px', fontWeight: 'bold', color: '#2c3e50' }}>Filter:</label>
233
+ <select
234
+ value={filter}
235
+ onChange={e => setFilter(e.target.value as typeof filter)}
236
+ style={{
237
+ padding: '8px 12px',
238
+ border: '1px solid #ddd',
239
+ borderRadius: '4px',
240
+ }}
241
+ >
242
+ <option value="all">All</option>
243
+ <option value="active">Active</option>
244
+ <option value="completed">Completed</option>
245
+ </select>
246
+ </div>
247
+
248
+ <div>
249
+ <label style={{ marginRight: '10px', fontWeight: 'bold', color: '#2c3e50' }}>Sort by:</label>
250
+ <select
251
+ value={sortBy}
252
+ onChange={e => setSortBy(e.target.value as typeof sortBy)}
253
+ style={{
254
+ padding: '8px 12px',
255
+ border: '1px solid #ddd',
256
+ borderRadius: '4px',
257
+ }}
258
+ >
259
+ <option value="priority">Priority</option>
260
+ <option value="dueDate">Due Date</option>
261
+ <option value="created">Created</option>
262
+ </select>
263
+ </div>
264
+ </div>
265
+ </div>
266
+ </div>
267
+
268
+ {/* Todo list */}
269
+ <div style={{
270
+ background: 'white',
271
+ borderRadius: '8px',
272
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
273
+ overflow: 'hidden',
274
+ }}
275
+ >
276
+ {getFilteredTodos().map(todo => (
277
+ <div
278
+ key={todo.id}
279
+ style={{
280
+ padding: '20px',
281
+ borderBottom: '1px solid #ecf0f1',
282
+ display: 'flex',
283
+ alignItems: 'center',
284
+ gap: '15px',
285
+ background: todo.completed ? '#f8f9fa' : 'white',
286
+ }}
287
+ >
288
+ <input
289
+ type="checkbox"
290
+ checked={todo.completed}
291
+ onChange={() => toggleTodo(todo.id)}
292
+ style={{
293
+ width: '20px',
294
+ height: '20px',
295
+ cursor: 'pointer',
296
+ }}
297
+ />
298
+
299
+ <div style={{ flex: '1' }}>
300
+ <div style={{
301
+ display: 'flex',
302
+ alignItems: 'center',
303
+ gap: '10px',
304
+ marginBottom: '5px',
305
+ }}
306
+ >
307
+ <span style={{
308
+ fontSize: '18px',
309
+ textDecoration: todo.completed ? 'line-through' : 'none',
310
+ color: todo.completed ? '#7f8c8d' : '#2c3e50',
311
+ }}
312
+ >
313
+ {todo.text}
314
+ </span>
315
+
316
+ {todo.category && (
317
+ <span style={{
318
+ background: '#e9ecef',
319
+ color: '#495057',
320
+ padding: '2px 8px',
321
+ borderRadius: '12px',
322
+ fontSize: '12px',
323
+ }}
324
+ >
325
+ {todo.category}
326
+ </span>
327
+ )}
328
+ </div>
329
+
330
+ {todo.dueDate && (
331
+ <div style={{
332
+ fontSize: '14px',
333
+ color: '#7f8c8d',
334
+ display: 'flex',
335
+ alignItems: 'center',
336
+ gap: '5px',
337
+ }}
338
+ >
339
+ 📅 Due:
340
+ {' '}
341
+ {new Date(todo.dueDate).toLocaleDateString()}
342
+ </div>
343
+ )}
344
+ </div>
345
+
346
+ <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
347
+ <select
348
+ value={todo.priority}
349
+ onChange={e => updateTodoPriority(todo.id, e.target.value as Todo['priority'])}
350
+ style={{
351
+ padding: '4px 8px',
352
+ border: '1px solid #ddd',
353
+ borderRadius: '4px',
354
+ fontSize: '12px',
355
+ }}
356
+ >
357
+ <option value="low">Low</option>
358
+ <option value="medium">Medium</option>
359
+ <option value="high">High</option>
360
+ </select>
361
+
362
+ <span style={{ fontSize: '16px' }}>
363
+ {getPriorityIcon(todo.priority)}
364
+ </span>
365
+
366
+ <button
367
+ onClick={() => deleteTodo(todo.id)}
368
+ style={{
369
+ background: 'none',
370
+ border: 'none',
371
+ color: '#e74c3c',
372
+ cursor: 'pointer',
373
+ fontSize: '18px',
374
+ padding: '5px',
375
+ }}
376
+ >
377
+ 🗑️
378
+ </button>
379
+ </div>
380
+ </div>
381
+ ))}
382
+
383
+ {getFilteredTodos().length === 0 && (
384
+ <div style={{
385
+ textAlign: 'center',
386
+ padding: '40px 20px',
387
+ color: '#7f8c8d',
388
+ fontStyle: 'italic',
389
+ }}
390
+ >
391
+ {filter === 'all'
392
+ ? 'No todos yet. Add one above!'
393
+ : filter === 'active'
394
+ ? 'No active todos!'
395
+ : 'No completed todos!'}
396
+ </div>
397
+ )}
398
+ </div>
399
+ </div>
400
+ )
401
+ }
402
+
403
+ // Mount the component
404
+ const container = document.getElementById('widget-root')
405
+ if (container) {
406
+ const root = createRoot(container)
407
+ root.render(<TodoList />)
408
+ }
@@ -0,0 +1,153 @@
1
+ import { createMCPServer } from 'mcp-use'
2
+ import { createUIResource } from '@mcp-ui/server';
3
+
4
+ // Create an MCP server (which is also an Express app)
5
+ // The MCP Inspector is automatically mounted at /inspector
6
+ const server = createMCPServer('ui-mcp-server', {
7
+ version: '1.0.0',
8
+ description: 'An MCP server with React UI widgets',
9
+ })
10
+
11
+ const PORT = process.env.PORT || 3000
12
+
13
+
14
+ server.tool({
15
+ name: 'test-tool',
16
+ description: 'Test tool',
17
+ inputs: [
18
+ {
19
+ name: 'test',
20
+ type: 'string',
21
+ description: 'Test input',
22
+ required: true,
23
+ },
24
+ ],
25
+ fn: async () => {
26
+ const uiResource = createUIResource({
27
+ uri: 'ui://widget/kanban-board',
28
+ content: {
29
+ type: 'externalUrl',
30
+ iframeUrl: 'http://localhost:3000/mcp-use/widgets/kanban-board'
31
+ },
32
+ encoding: 'text',
33
+ })
34
+ return uiResource
35
+ },
36
+ })
37
+
38
+ // MCP Resource for server status
39
+ server.resource({
40
+ uri: 'ui://status',
41
+ name: 'UI Server Status',
42
+ description: 'Status of the UI MCP server',
43
+ mimeType: 'application/json',
44
+ fn: async () => {
45
+ return JSON.stringify({
46
+ name: 'ui-mcp-server',
47
+ version: '1.0.0',
48
+ status: 'running',
49
+ transport: process.env.MCP_TRANSPORT || 'http',
50
+ uiEndpoint: `http://localhost:${PORT}/mcp-use/widgets`,
51
+ mcpEndpoint: process.env.MCP_TRANSPORT === 'stdio' ? 'stdio' : `http://localhost:${PORT}/mcp`,
52
+ availableWidgets: ['kanban-board', 'todo-list', 'data-visualization'],
53
+ timestamp: new Date().toISOString(),
54
+ }, null, 2)
55
+ },
56
+ })
57
+
58
+ // MCP Resource for Kanban Board widget
59
+ server.resource({
60
+ uri: 'ui://widget/kanban-board',
61
+ name: 'Kanban Board Widget',
62
+ description: 'Interactive Kanban board widget',
63
+ mimeType: 'text/html+skybridge',
64
+ fn: async () => {
65
+ const widgetUrl = `http://localhost:${PORT}/mcp-use/widgets/kanban-board`
66
+ return `
67
+ <div id="kanban-root"></div>
68
+ <script type="module" src="${widgetUrl}"></script>
69
+ `.trim()
70
+ },
71
+ })
72
+
73
+ // MCP Resource for Todo List widget
74
+ server.resource({
75
+ uri: 'ui://widget/todo-list',
76
+ name: 'Todo List Widget',
77
+ description: 'Interactive todo list widget',
78
+ mimeType: 'text/html+skybridge',
79
+ fn: async () => {
80
+ const widgetUrl = `http://localhost:${PORT}/mcp-use/widgets/todo-list`
81
+ return `
82
+ <div id="todo-root"></div>
83
+ <script type="module" src="${widgetUrl}"></script>
84
+ `.trim()
85
+ },
86
+ })
87
+
88
+ // MCP Resource for Data Visualization widget
89
+ server.resource({
90
+ uri: 'ui://widget/data-visualization',
91
+ name: 'Data Visualization Widget',
92
+ description: 'Interactive data visualization widget',
93
+ mimeType: 'text/html+skybridge',
94
+ fn: async () => {
95
+ const widgetUrl = `http://localhost:${PORT}/mcp-use/widgets/data-visualization`
96
+ return `
97
+ <div id="data-viz-root"></div>
98
+ <script type="module" src="${widgetUrl}"></script>
99
+ `.trim()
100
+ },
101
+ })
102
+
103
+ // Tool for showing Kanban Board
104
+ server.tool({
105
+ name: 'show-kanban',
106
+ description: 'Display an interactive Kanban board',
107
+ inputs: [
108
+ {
109
+ name: 'tasks',
110
+ type: 'string',
111
+ description: 'JSON string of tasks to display',
112
+ required: true,
113
+ },
114
+ ],
115
+ fn: async (params: Record<string, any>) => {
116
+ const { tasks } = params
117
+ try {
118
+ const taskData = JSON.parse(tasks)
119
+ return `Displayed Kanban board with ${taskData.length || 0} tasks at http://localhost:${PORT}/mcp-use/widgets/kanban-board`
120
+ }
121
+ catch (error) {
122
+ return `Error parsing tasks: ${error instanceof Error ? error.message : 'Invalid JSON'}`
123
+ }
124
+ },
125
+ })
126
+
127
+ // Tool for showing Todo List
128
+ server.tool({
129
+ name: 'show-todo-list',
130
+ description: 'Display an interactive todo list',
131
+ inputs: [
132
+ {
133
+ name: 'todos',
134
+ type: 'string',
135
+ description: 'JSON string of todos to display',
136
+ required: true,
137
+ },
138
+ ],
139
+ fn: async (params: Record<string, any>) => {
140
+ const { todos } = params
141
+ try {
142
+ const todoData = JSON.parse(todos)
143
+ return `Displayed Todo list with ${todoData.length || 0} items at http://localhost:${PORT}/mcp-use/widgets/todo-list`
144
+ }
145
+ catch (error) {
146
+ return `Error parsing todos: ${error instanceof Error ? error.message : 'Invalid JSON'}`
147
+ }
148
+ },
149
+ })
150
+
151
+
152
+ // Start the server (MCP endpoints auto-mounted at /mcp)
153
+ server.listen(PORT)
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "jsx": "react-jsx",
5
+ "module": "ESNext",
6
+ "moduleResolution": "node",
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": ["src/**/*", "resources/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "create-mcp-use-app",
3
+ "version": "0.2.1",
4
+ "type": "module",
5
+ "description": "Create MCP-Use apps with one command",
6
+ "author": "mcp-use, Inc.",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/mcp-use/mcp-use-ts#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/mcp-use/mcp-use-ts.git",
12
+ "directory": "packages/create-mcp-use-app"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/mcp-use/mcp-use-ts/issues"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "create",
21
+ "scaffold",
22
+ "cli",
23
+ "init",
24
+ "starter",
25
+ "template",
26
+ "typescript",
27
+ "react"
28
+ ],
29
+ "bin": {
30
+ "create-mcp-use-app": "./dist/index.js"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md"
35
+ ],
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "dependencies": {
40
+ "commander": "^11.0.0",
41
+ "prompts": "^2.4.2",
42
+ "chalk": "^5.3.0",
43
+ "fs-extra": "^11.2.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^20.0.0",
47
+ "@types/prompts": "^2.4.9",
48
+ "@types/fs-extra": "^11.0.4",
49
+ "typescript": "^5.0.0",
50
+ "vitest": "^1.0.0"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "scripts": {
56
+ "build": "npm run clean && tsup src/index.ts --format esm && tsc --emitDeclarationOnly --declaration && npm run copy-templates",
57
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
58
+ "copy-templates": "mkdir -p dist/templates && cp -r src/templates/* dist/templates/",
59
+ "dev": "tsc --build --watch",
60
+ "test": "vitest",
61
+ "lint": "eslint ."
62
+ }
63
+ }