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.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/templates/api/README.md +224 -0
- package/dist/templates/api/package.json +24 -0
- package/dist/templates/api/src/server.ts +229 -0
- package/dist/templates/api/tsconfig.json +20 -0
- package/dist/templates/basic/README.md +64 -0
- package/dist/templates/basic/package.json +23 -0
- package/dist/templates/basic/src/server.ts +67 -0
- package/dist/templates/basic/tsconfig.json +20 -0
- package/dist/templates/filesystem/README.md +139 -0
- package/dist/templates/filesystem/package.json +23 -0
- package/dist/templates/filesystem/src/server.ts +155 -0
- package/dist/templates/filesystem/tsconfig.json +20 -0
- package/dist/templates/ui/README.md +454 -0
- package/dist/templates/ui/package-lock.json +2870 -0
- package/dist/templates/ui/package.json +45 -0
- package/dist/templates/ui/resources/data-visualization.tsx +475 -0
- package/dist/templates/ui/resources/kanban-board.tsx +306 -0
- package/dist/templates/ui/resources/todo-list.tsx +408 -0
- package/dist/templates/ui/src/server.ts +153 -0
- package/dist/templates/ui/tsconfig.json +20 -0
- package/package.json +63 -0
|
@@ -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
|
+
}
|