create-mcp-use-app 0.2.2 → 0.3.2

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.
@@ -27,126 +27,40 @@ server.tool({
27
27
  uri: 'ui://widget/kanban-board',
28
28
  content: {
29
29
  type: 'externalUrl',
30
- iframeUrl: 'http://localhost:3000/mcp-use/widgets/kanban-board'
30
+ iframeUrl: `http://localhost:${PORT}/mcp-use/widgets/kanban-board`
31
31
  },
32
32
  encoding: 'text',
33
33
  })
34
- return uiResource
34
+ return {
35
+ content: [uiResource]
36
+ }
35
37
  },
36
38
  })
37
39
 
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
40
 
58
41
  // MCP Resource for Kanban Board widget
59
42
  server.resource({
60
- uri: 'ui://widget/kanban-board',
61
43
  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',
44
+ uri: 'ui://widget/kanban-board',
45
+ title: 'Kanban Board Widget',
78
46
  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()
47
+ description: 'Interactive Kanban board widget',
48
+ annotations: {
49
+ audience: ['user', 'assistant'],
50
+ priority: 0.7
85
51
  },
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
52
  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'}`
53
+ const widgetUrl = `http://localhost:${PORT}/mcp-use/widgets/kanban-board`
54
+ return {
55
+ contents: [{
56
+ uri: 'ui://widget/kanban-board',
57
+ mimeType: 'text/uri-list',
58
+ text: widgetUrl
59
+ }]
123
60
  }
124
61
  },
125
62
  })
126
63
 
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
64
 
151
65
 
152
66
  // Start the server (MCP endpoints auto-mounted at /mcp)
@@ -15,6 +15,6 @@
15
15
  "forceConsistentCasingInFileNames": true,
16
16
  "skipLibCheck": true
17
17
  },
18
- "include": ["src/**/*", "resources/**/*"],
18
+ "include": ["index.ts", "src/**/*", "resources/**/*"],
19
19
  "exclude": ["node_modules", "dist"]
20
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mcp-use-app",
3
- "version": "0.2.2",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "description": "Create MCP-Use apps with one command",
6
6
  "author": "mcp-use, Inc.",
@@ -53,9 +53,9 @@
53
53
  "scripts": {
54
54
  "build": "npm run clean && tsup src/index.ts --format esm && tsc --emitDeclarationOnly --declaration && npm run copy-templates",
55
55
  "clean": "rm -rf dist tsconfig.tsbuildinfo",
56
- "copy-templates": "mkdir -p dist/templates && cp -r src/templates/* dist/templates/",
56
+ "copy-templates": "node scripts/copy-templates.js",
57
57
  "dev": "tsc --build --watch",
58
- "test": "vitest",
58
+ "test": "vitest --run --passWithNoTests",
59
59
  "lint": "eslint ."
60
60
  }
61
61
  }
@@ -1,475 +0,0 @@
1
- import React, { useEffect, useState } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
-
4
- interface DataPoint {
5
- label: string
6
- value: number
7
- color?: string
8
- }
9
-
10
- interface DataVisualizationProps {
11
- initialData?: DataPoint[]
12
- chartType?: 'bar' | 'line' | 'pie'
13
- }
14
-
15
- const DataVisualization: React.FC<DataVisualizationProps> = ({
16
- initialData = [],
17
- chartType = 'bar',
18
- }) => {
19
- const [data, setData] = useState<DataPoint[]>(initialData)
20
- const [currentChartType, setCurrentChartType] = useState<'bar' | 'line' | 'pie'>(chartType)
21
- const [newDataPoint, setNewDataPoint] = useState({ label: '', value: 0 })
22
-
23
- // Load data from URL parameters or use defaults
24
- useEffect(() => {
25
- const urlParams = new URLSearchParams(window.location.search)
26
- const dataParam = urlParams.get('data')
27
- const typeParam = urlParams.get('chartType')
28
-
29
- if (dataParam) {
30
- try {
31
- const parsedData = JSON.parse(decodeURIComponent(dataParam))
32
- setData(parsedData)
33
- }
34
- catch (error) {
35
- console.error('Error parsing data from URL:', error)
36
- }
37
- }
38
- else {
39
- // Default data for demo
40
- setData([
41
- { label: 'January', value: 65, color: '#3498db' },
42
- { label: 'February', value: 59, color: '#e74c3c' },
43
- { label: 'March', value: 80, color: '#2ecc71' },
44
- { label: 'April', value: 81, color: '#f39c12' },
45
- { label: 'May', value: 56, color: '#9b59b6' },
46
- { label: 'June', value: 55, color: '#1abc9c' },
47
- ])
48
- }
49
-
50
- if (typeParam) {
51
- setCurrentChartType(typeParam as 'bar' | 'line' | 'pie')
52
- }
53
- }, [])
54
-
55
- const addDataPoint = () => {
56
- if (newDataPoint.label.trim() && newDataPoint.value > 0) {
57
- const colors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#34495e', '#e67e22']
58
- const newPoint: DataPoint = {
59
- ...newDataPoint,
60
- color: colors[data.length % colors.length],
61
- }
62
- setData([...data, newPoint])
63
- setNewDataPoint({ label: '', value: 0 })
64
- }
65
- }
66
-
67
- const removeDataPoint = (index: number) => {
68
- setData(data.filter((_, i) => i !== index))
69
- }
70
-
71
- const getMaxValue = () => {
72
- return Math.max(...data.map(d => d.value), 0)
73
- }
74
-
75
- const getTotalValue = () => {
76
- return data.reduce((sum, d) => sum + d.value, 0)
77
- }
78
-
79
- const renderBarChart = () => {
80
- const maxValue = getMaxValue()
81
-
82
- return (
83
- <div style={{ padding: '20px' }}>
84
- <h3 style={{ marginBottom: '20px', color: '#2c3e50' }}>Bar Chart</h3>
85
- <div style={{ display: 'flex', alignItems: 'end', gap: '10px', height: '300px' }}>
86
- {data.map((point, index) => (
87
- <div key={index} style={{ flex: '1', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
88
- <div
89
- style={{
90
- background: point.color || '#3498db',
91
- height: `${(point.value / maxValue) * 250}px`,
92
- width: '100%',
93
- borderRadius: '4px 4px 0 0',
94
- transition: 'all 0.3s ease',
95
- cursor: 'pointer',
96
- position: 'relative',
97
- }}
98
- title={`${point.label}: ${point.value}`}
99
- >
100
- <div style={{
101
- position: 'absolute',
102
- top: '-25px',
103
- left: '50%',
104
- transform: 'translateX(-50%)',
105
- background: 'rgba(0,0,0,0.8)',
106
- color: 'white',
107
- padding: '2px 6px',
108
- borderRadius: '4px',
109
- fontSize: '12px',
110
- whiteSpace: 'nowrap',
111
- }}
112
- >
113
- {point.value}
114
- </div>
115
- </div>
116
- <div style={{
117
- marginTop: '10px',
118
- fontSize: '12px',
119
- textAlign: 'center',
120
- color: '#7f8c8d',
121
- wordBreak: 'break-word',
122
- }}
123
- >
124
- {point.label}
125
- </div>
126
- </div>
127
- ))}
128
- </div>
129
- </div>
130
- )
131
- }
132
-
133
- const renderLineChart = () => {
134
- const maxValue = getMaxValue()
135
- const width = 600
136
- const height = 300
137
- const padding = 40
138
-
139
- const points = data.map((point, index) => ({
140
- x: padding + (index * (width - 2 * padding)) / (data.length - 1),
141
- y: padding + ((maxValue - point.value) / maxValue) * (height - 2 * padding),
142
- }))
143
-
144
- const pathData = points.map((point, index) =>
145
- `${index === 0 ? 'M' : 'L'} ${point.x} ${point.y}`,
146
- ).join(' ')
147
-
148
- return (
149
- <div style={{ padding: '20px' }}>
150
- <h3 style={{ marginBottom: '20px', color: '#2c3e50' }}>Line Chart</h3>
151
- <svg width={width} height={height} style={{ border: '1px solid #ecf0f1', borderRadius: '8px' }}>
152
- {/* Grid lines */}
153
- {[0, 0.25, 0.5, 0.75, 1].map(ratio => (
154
- <line
155
- key={ratio}
156
- x1={padding}
157
- y1={padding + ratio * (height - 2 * padding)}
158
- x2={width - padding}
159
- y2={padding + ratio * (height - 2 * padding)}
160
- stroke="#ecf0f1"
161
- strokeWidth="1"
162
- />
163
- ))}
164
-
165
- {/* Line */}
166
- <path
167
- d={pathData}
168
- fill="none"
169
- stroke="#3498db"
170
- strokeWidth="3"
171
- />
172
-
173
- {/* Data points */}
174
- {points.map((point, index) => (
175
- <circle
176
- key={index}
177
- cx={point.x}
178
- cy={point.y}
179
- r="6"
180
- fill={data[index].color || '#3498db'}
181
- stroke="white"
182
- strokeWidth="2"
183
- style={{ cursor: 'pointer' }}
184
- title={`${data[index].label}: ${data[index].value}`}
185
- />
186
- ))}
187
-
188
- {/* Labels */}
189
- {data.map((point, index) => (
190
- <text
191
- key={index}
192
- x={padding + (index * (width - 2 * padding)) / (data.length - 1)}
193
- y={height - 10}
194
- textAnchor="middle"
195
- fontSize="12"
196
- fill="#7f8c8d"
197
- >
198
- {point.label}
199
- </text>
200
- ))}
201
- </svg>
202
- </div>
203
- )
204
- }
205
-
206
- const renderPieChart = () => {
207
- const total = getTotalValue()
208
- let currentAngle = 0
209
-
210
- return (
211
- <div style={{ padding: '20px' }}>
212
- <h3 style={{ marginBottom: '20px', color: '#2c3e50' }}>Pie Chart</h3>
213
- <div style={{ display: 'flex', gap: '40px', alignItems: 'center' }}>
214
- <svg width="300" height="300" style={{ border: '1px solid #ecf0f1', borderRadius: '8px' }}>
215
- {data.map((point, index) => {
216
- const percentage = point.value / total
217
- const angle = percentage * 360
218
- const startAngle = currentAngle
219
- const endAngle = currentAngle + angle
220
- currentAngle += angle
221
-
222
- const centerX = 150
223
- const centerY = 150
224
- const radius = 120
225
-
226
- const startAngleRad = (startAngle - 90) * (Math.PI / 180)
227
- const endAngleRad = (endAngle - 90) * (Math.PI / 180)
228
-
229
- const x1 = centerX + radius * Math.cos(startAngleRad)
230
- const y1 = centerY + radius * Math.sin(startAngleRad)
231
- const x2 = centerX + radius * Math.cos(endAngleRad)
232
- const y2 = centerY + radius * Math.sin(endAngleRad)
233
-
234
- const largeArcFlag = angle > 180 ? 1 : 0
235
-
236
- const pathData = [
237
- `M ${centerX} ${centerY}`,
238
- `L ${x1} ${y1}`,
239
- `A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2}`,
240
- 'Z',
241
- ].join(' ')
242
-
243
- return (
244
- <path
245
- key={index}
246
- d={pathData}
247
- fill={point.color || '#3498db'}
248
- stroke="white"
249
- strokeWidth="2"
250
- style={{ cursor: 'pointer' }}
251
- title={`${point.label}: ${point.value} (${(percentage * 100).toFixed(1)}%)`}
252
- />
253
- )
254
- })}
255
- </svg>
256
-
257
- <div style={{ flex: '1' }}>
258
- <h4 style={{ marginBottom: '15px', color: '#2c3e50' }}>Legend</h4>
259
- {data.map((point, index) => (
260
- <div
261
- key={index}
262
- style={{
263
- display: 'flex',
264
- alignItems: 'center',
265
- marginBottom: '8px',
266
- padding: '8px',
267
- background: '#f8f9fa',
268
- borderRadius: '4px',
269
- }}
270
- >
271
- <div style={{
272
- width: '16px',
273
- height: '16px',
274
- background: point.color || '#3498db',
275
- borderRadius: '2px',
276
- marginRight: '10px',
277
- }}
278
- />
279
- <span style={{ flex: '1', fontSize: '14px' }}>{point.label}</span>
280
- <span style={{
281
- fontSize: '14px',
282
- fontWeight: 'bold',
283
- color: '#2c3e50',
284
- }}
285
- >
286
- {point.value}
287
- </span>
288
- </div>
289
- ))}
290
- </div>
291
- </div>
292
- </div>
293
- )
294
- }
295
-
296
- return (
297
- <div style={{ padding: '20px' }}>
298
- <div style={{ marginBottom: '30px' }}>
299
- <h1 style={{ margin: '0 0 20px 0', color: '#2c3e50' }}>Data Visualization</h1>
300
-
301
- {/* Controls */}
302
- <div style={{
303
- background: 'white',
304
- padding: '20px',
305
- borderRadius: '8px',
306
- boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
307
- marginBottom: '20px',
308
- }}
309
- >
310
- <div style={{ display: 'flex', gap: '20px', flexWrap: 'wrap', alignItems: 'center', marginBottom: '20px' }}>
311
- <div>
312
- <label style={{ marginRight: '10px', fontWeight: 'bold', color: '#2c3e50' }}>Chart Type:</label>
313
- <select
314
- value={currentChartType}
315
- onChange={e => setCurrentChartType(e.target.value as typeof currentChartType)}
316
- style={{
317
- padding: '8px 12px',
318
- border: '1px solid #ddd',
319
- borderRadius: '4px',
320
- }}
321
- >
322
- <option value="bar">Bar Chart</option>
323
- <option value="line">Line Chart</option>
324
- <option value="pie">Pie Chart</option>
325
- </select>
326
- </div>
327
- </div>
328
-
329
- <div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap', alignItems: 'center' }}>
330
- <input
331
- type="text"
332
- placeholder="Label"
333
- value={newDataPoint.label}
334
- onChange={e => setNewDataPoint({ ...newDataPoint, label: e.target.value })}
335
- style={{
336
- padding: '8px 12px',
337
- border: '1px solid #ddd',
338
- borderRadius: '4px',
339
- minWidth: '120px',
340
- }}
341
- />
342
- <input
343
- type="number"
344
- placeholder="Value"
345
- value={newDataPoint.value}
346
- onChange={e => setNewDataPoint({ ...newDataPoint, value: Number(e.target.value) })}
347
- style={{
348
- padding: '8px 12px',
349
- border: '1px solid #ddd',
350
- borderRadius: '4px',
351
- minWidth: '100px',
352
- }}
353
- />
354
- <button
355
- onClick={addDataPoint}
356
- style={{
357
- padding: '8px 16px',
358
- background: '#3498db',
359
- color: 'white',
360
- border: 'none',
361
- borderRadius: '4px',
362
- cursor: 'pointer',
363
- }}
364
- >
365
- Add Data Point
366
- </button>
367
- </div>
368
- </div>
369
-
370
- {/* Chart */}
371
- <div style={{
372
- background: 'white',
373
- borderRadius: '8px',
374
- boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
375
- overflow: 'hidden',
376
- }}
377
- >
378
- {data.length === 0
379
- ? (
380
- <div style={{
381
- textAlign: 'center',
382
- padding: '40px 20px',
383
- color: '#7f8c8d',
384
- fontStyle: 'italic',
385
- }}
386
- >
387
- No data to visualize. Add some data points above!
388
- </div>
389
- )
390
- : (
391
- <>
392
- {currentChartType === 'bar' && renderBarChart()}
393
- {currentChartType === 'line' && renderLineChart()}
394
- {currentChartType === 'pie' && renderPieChart()}
395
- </>
396
- )}
397
- </div>
398
-
399
- {/* Data table */}
400
- {data.length > 0 && (
401
- <div style={{
402
- background: 'white',
403
- borderRadius: '8px',
404
- boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
405
- marginTop: '20px',
406
- overflow: 'hidden',
407
- }}
408
- >
409
- <div style={{ padding: '20px', borderBottom: '1px solid #ecf0f1' }}>
410
- <h3 style={{ margin: '0', color: '#2c3e50' }}>Data Table</h3>
411
- </div>
412
- <div style={{ overflowX: 'auto' }}>
413
- <table style={{ width: '100%', borderCollapse: 'collapse' }}>
414
- <thead>
415
- <tr style={{ background: '#f8f9fa' }}>
416
- <th style={{ padding: '12px', textAlign: 'left', borderBottom: '1px solid #ecf0f1' }}>Label</th>
417
- <th style={{ padding: '12px', textAlign: 'left', borderBottom: '1px solid #ecf0f1' }}>Value</th>
418
- <th style={{ padding: '12px', textAlign: 'left', borderBottom: '1px solid #ecf0f1' }}>Percentage</th>
419
- <th style={{ padding: '12px', textAlign: 'left', borderBottom: '1px solid #ecf0f1' }}>Color</th>
420
- <th style={{ padding: '12px', textAlign: 'left', borderBottom: '1px solid #ecf0f1' }}>Actions</th>
421
- </tr>
422
- </thead>
423
- <tbody>
424
- {data.map((point, index) => (
425
- <tr key={index} style={{ borderBottom: '1px solid #ecf0f1' }}>
426
- <td style={{ padding: '12px' }}>{point.label}</td>
427
- <td style={{ padding: '12px', fontWeight: 'bold' }}>{point.value}</td>
428
- <td style={{ padding: '12px' }}>
429
- {((point.value / getTotalValue()) * 100).toFixed(1)}
430
- %
431
- </td>
432
- <td style={{ padding: '12px' }}>
433
- <div style={{
434
- width: '20px',
435
- height: '20px',
436
- background: point.color || '#3498db',
437
- borderRadius: '2px',
438
- display: 'inline-block',
439
- }}
440
- />
441
- </td>
442
- <td style={{ padding: '12px' }}>
443
- <button
444
- onClick={() => removeDataPoint(index)}
445
- style={{
446
- background: '#e74c3c',
447
- color: 'white',
448
- border: 'none',
449
- borderRadius: '4px',
450
- padding: '4px 8px',
451
- cursor: 'pointer',
452
- fontSize: '12px',
453
- }}
454
- >
455
- Remove
456
- </button>
457
- </td>
458
- </tr>
459
- ))}
460
- </tbody>
461
- </table>
462
- </div>
463
- </div>
464
- )}
465
- </div>
466
- </div>
467
- )
468
- }
469
-
470
- // Mount the component
471
- const container = document.getElementById('widget-root')
472
- if (container) {
473
- const root = createRoot(container)
474
- root.render(<DataVisualization />)
475
- }