create-mcp-use-app 0.3.4 → 0.4.0
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 +153 -73
- package/dist/templates/apps_sdk/README.md +398 -0
- package/dist/templates/apps_sdk/index.ts +11 -0
- package/dist/templates/apps_sdk/package.json +42 -0
- package/dist/templates/apps_sdk/src/server.ts +239 -0
- package/dist/templates/apps_sdk/src/widgets.ts +180 -0
- package/dist/templates/apps_sdk/tsconfig.json +20 -0
- package/dist/templates/ui/README.md +21 -26
- package/dist/templates/ui/src/server.ts +2 -2
- package/dist/templates/uiresource/README.md +54 -34
- package/dist/templates/uiresource/src/server.ts +6 -6
- package/package.json +5 -2
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { AppsSdkUIResource } from 'mcp-use/server'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ════════════════════════════════════════════════════════════════════
|
|
5
|
+
* Pizzaz Widget Definitions
|
|
6
|
+
* ════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* OpenAI's pizzaz reference implementation demonstrating Apps SDK widgets.
|
|
9
|
+
* These widgets load external assets from OpenAI's CDN and use the
|
|
10
|
+
* text/html+skybridge MIME type for Apps SDK compatibility.
|
|
11
|
+
*
|
|
12
|
+
* Each widget demonstrates:
|
|
13
|
+
* - External resource loading (scripts and stylesheets)
|
|
14
|
+
* - Apps SDK metadata (CSP, tool invocation status, etc.)
|
|
15
|
+
* - Structured content injection via window.openai.toolOutput
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export interface PizzazWidgetDefinition {
|
|
19
|
+
id: string
|
|
20
|
+
title: string
|
|
21
|
+
description: string
|
|
22
|
+
templateUri: string
|
|
23
|
+
invoking: string
|
|
24
|
+
invoked: string
|
|
25
|
+
html: string
|
|
26
|
+
responseText: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const pizzazWidgets: PizzazWidgetDefinition[] = [
|
|
30
|
+
{
|
|
31
|
+
id: 'pizza-map',
|
|
32
|
+
title: 'Show Pizza Map',
|
|
33
|
+
description: 'Interactive map widget for displaying pizza locations',
|
|
34
|
+
templateUri: 'ui://widget/pizza-map.html',
|
|
35
|
+
invoking: 'Hand-tossing a map',
|
|
36
|
+
invoked: 'Served a fresh map',
|
|
37
|
+
html: `
|
|
38
|
+
<div id="pizzaz-root"></div>
|
|
39
|
+
<link rel="stylesheet" href="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-0038.css">
|
|
40
|
+
<script type="module" src="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-0038.js"></script>
|
|
41
|
+
`.trim(),
|
|
42
|
+
responseText: 'Rendered a pizza map!'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'pizza-carousel',
|
|
46
|
+
title: 'Show Pizza Carousel',
|
|
47
|
+
description: 'Carousel widget for browsing pizza options',
|
|
48
|
+
templateUri: 'ui://widget/pizza-carousel.html',
|
|
49
|
+
invoking: 'Carousel some spots',
|
|
50
|
+
invoked: 'Served a fresh carousel',
|
|
51
|
+
html: `
|
|
52
|
+
<div id="pizzaz-carousel-root"></div>
|
|
53
|
+
<link rel="stylesheet" href="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-carousel-0038.css">
|
|
54
|
+
<script type="module" src="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-carousel-0038.js"></script>
|
|
55
|
+
`.trim(),
|
|
56
|
+
responseText: 'Rendered a pizza carousel!'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'pizza-albums',
|
|
60
|
+
title: 'Show Pizza Album',
|
|
61
|
+
description: 'Album-style gallery widget for pizza collections',
|
|
62
|
+
templateUri: 'ui://widget/pizza-albums.html',
|
|
63
|
+
invoking: 'Hand-tossing an album',
|
|
64
|
+
invoked: 'Served a fresh album',
|
|
65
|
+
html: `
|
|
66
|
+
<div id="pizzaz-albums-root"></div>
|
|
67
|
+
<link rel="stylesheet" href="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-albums-0038.css">
|
|
68
|
+
<script type="module" src="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-albums-0038.js"></script>
|
|
69
|
+
`.trim(),
|
|
70
|
+
responseText: 'Rendered a pizza album!'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'pizza-list',
|
|
74
|
+
title: 'Show Pizza List',
|
|
75
|
+
description: 'List view widget for pizza items',
|
|
76
|
+
templateUri: 'ui://widget/pizza-list.html',
|
|
77
|
+
invoking: 'Hand-tossing a list',
|
|
78
|
+
invoked: 'Served a fresh list',
|
|
79
|
+
html: `
|
|
80
|
+
<div id="pizzaz-list-root"></div>
|
|
81
|
+
<link rel="stylesheet" href="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-list-0038.css">
|
|
82
|
+
<script type="module" src="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-list-0038.js"></script>
|
|
83
|
+
`.trim(),
|
|
84
|
+
responseText: 'Rendered a pizza list!'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'pizza-video',
|
|
88
|
+
title: 'Show Pizza Video',
|
|
89
|
+
description: 'Video player widget for pizza content',
|
|
90
|
+
templateUri: 'ui://widget/pizza-video.html',
|
|
91
|
+
invoking: 'Hand-tossing a video',
|
|
92
|
+
invoked: 'Served a fresh video',
|
|
93
|
+
html: `
|
|
94
|
+
<div id="pizzaz-video-root"></div>
|
|
95
|
+
<link rel="stylesheet" href="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-video-0038.css">
|
|
96
|
+
<script type="module" src="https://persistent.oaistatic.com/ecosystem-built-assets/pizzaz-video-0038.js"></script>
|
|
97
|
+
`.trim(),
|
|
98
|
+
responseText: 'Rendered a pizza video!'
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Convert pizzaz widget definitions to mcp-use AppsSdkUIResource format
|
|
104
|
+
*
|
|
105
|
+
* This demonstrates using mcp-use's AppsSdkUIResource type which:
|
|
106
|
+
* - Automatically sets MIME type to text/html+skybridge
|
|
107
|
+
* - Supports Apps SDK metadata (CSP, widget description, etc.)
|
|
108
|
+
* - Injects structuredContent as window.openai.toolOutput
|
|
109
|
+
* - Works seamlessly with ChatGPT and other Apps SDK clients
|
|
110
|
+
*/
|
|
111
|
+
export function getPizzazUIResources(): AppsSdkUIResource[] {
|
|
112
|
+
return pizzazWidgets.map(widget => ({
|
|
113
|
+
type: 'appsSdk',
|
|
114
|
+
name: widget.id,
|
|
115
|
+
title: widget.title,
|
|
116
|
+
description: widget.description,
|
|
117
|
+
htmlTemplate: widget.html,
|
|
118
|
+
size: ['800px', '600px'],
|
|
119
|
+
props: {
|
|
120
|
+
pizzaTopping: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
description: 'Topping to mention when rendering the widget',
|
|
123
|
+
required: true
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
appsSdkMetadata: {
|
|
127
|
+
'openai/widgetDescription': widget.description,
|
|
128
|
+
'openai/toolInvocation/invoking': widget.invoking,
|
|
129
|
+
'openai/toolInvocation/invoked': widget.invoked,
|
|
130
|
+
'openai/widgetAccessible': true,
|
|
131
|
+
'openai/resultCanProduceWidget': true,
|
|
132
|
+
'openai/widgetCSP': {
|
|
133
|
+
connect_domains: [],
|
|
134
|
+
resource_domains: ['https://persistent.oaistatic.com']
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} satisfies AppsSdkUIResource))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get a summary of all pizzaz widgets for documentation
|
|
142
|
+
*/
|
|
143
|
+
export function getPizzazWidgetsSummary() {
|
|
144
|
+
return pizzazWidgets.map(w => ({
|
|
145
|
+
id: w.id,
|
|
146
|
+
title: w.title,
|
|
147
|
+
description: w.description,
|
|
148
|
+
tool: `ui_${w.id}`,
|
|
149
|
+
resource: w.templateUri,
|
|
150
|
+
responseText: w.responseText
|
|
151
|
+
}))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Create widget metadata for a specific widget
|
|
156
|
+
*/
|
|
157
|
+
export function widgetMeta(widget: PizzazWidgetDefinition) {
|
|
158
|
+
return {
|
|
159
|
+
'openai/outputTemplate': widget.templateUri,
|
|
160
|
+
'openai/toolInvocation/invoking': widget.invoking,
|
|
161
|
+
'openai/toolInvocation/invoked': widget.invoked,
|
|
162
|
+
'openai/widgetAccessible': true,
|
|
163
|
+
'openai/resultCanProduceWidget': true
|
|
164
|
+
} as const
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get widget by ID
|
|
169
|
+
*/
|
|
170
|
+
export function getWidgetById(id: string): PizzazWidgetDefinition | undefined {
|
|
171
|
+
return pizzazWidgets.find(w => w.id === id)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get widget by template URI
|
|
176
|
+
*/
|
|
177
|
+
export function getWidgetByUri(uri: string): PizzazWidgetDefinition | undefined {
|
|
178
|
+
return pizzazWidgets.find(w => w.templateUri === uri)
|
|
179
|
+
}
|
|
180
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"allowJs": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"sourceMap": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"skipLibCheck": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["index.ts", "src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|
|
20
|
+
|
|
@@ -56,9 +56,9 @@ Interactive Kanban board for task management.
|
|
|
56
56
|
mcp.tool({
|
|
57
57
|
name: 'show-kanban',
|
|
58
58
|
inputs: [{ name: 'tasks', type: 'string', required: true }],
|
|
59
|
-
|
|
59
|
+
cb: async ({ tasks }) => {
|
|
60
60
|
// Display Kanban board with tasks
|
|
61
|
-
}
|
|
61
|
+
},
|
|
62
62
|
})
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -80,9 +80,9 @@ Interactive todo list with filtering and sorting.
|
|
|
80
80
|
mcp.tool({
|
|
81
81
|
name: 'show-todo-list',
|
|
82
82
|
inputs: [{ name: 'todos', type: 'string', required: true }],
|
|
83
|
-
|
|
83
|
+
cb: async ({ todos }) => {
|
|
84
84
|
// Display todo list with todos
|
|
85
|
-
}
|
|
85
|
+
},
|
|
86
86
|
})
|
|
87
87
|
```
|
|
88
88
|
|
|
@@ -105,11 +105,11 @@ mcp.tool({
|
|
|
105
105
|
name: 'show-data-viz',
|
|
106
106
|
inputs: [
|
|
107
107
|
{ name: 'data', type: 'string', required: true },
|
|
108
|
-
{ name: 'chartType', type: 'string', required: false }
|
|
108
|
+
{ name: 'chartType', type: 'string', required: false },
|
|
109
109
|
],
|
|
110
|
-
|
|
110
|
+
cb: async ({ data, chartType }) => {
|
|
111
111
|
// Display data visualization
|
|
112
|
-
}
|
|
112
|
+
},
|
|
113
113
|
})
|
|
114
114
|
```
|
|
115
115
|
|
|
@@ -149,10 +149,10 @@ mcp.tool({
|
|
|
149
149
|
uri: 'ui://widget/my-widget',
|
|
150
150
|
name: 'My Widget',
|
|
151
151
|
mimeType: 'text/html+skybridge',
|
|
152
|
-
|
|
152
|
+
readCallback: async () => {
|
|
153
153
|
const widgetUrl = `http://localhost:${PORT}/mcp-use/widgets/my-widget`
|
|
154
154
|
return `<div id="my-widget-root"></div><script type="module" src="${widgetUrl}"></script>`
|
|
155
|
-
}
|
|
155
|
+
},
|
|
156
156
|
})
|
|
157
157
|
```
|
|
158
158
|
|
|
@@ -198,11 +198,7 @@ const MyWidget: React.FC<MyWidgetProps> = ({ initialData = [] }) => {
|
|
|
198
198
|
}
|
|
199
199
|
}, [])
|
|
200
200
|
|
|
201
|
-
return
|
|
202
|
-
<div>
|
|
203
|
-
{/* Your widget content */}
|
|
204
|
-
</div>
|
|
205
|
-
)
|
|
201
|
+
return <div>{/* Your widget content */}</div>
|
|
206
202
|
}
|
|
207
203
|
|
|
208
204
|
// Render the component
|
|
@@ -235,7 +231,7 @@ npm run build
|
|
|
235
231
|
|
|
236
232
|
# The built files will be in dist/
|
|
237
233
|
# - dist/index.js (MCP server entry point)
|
|
238
|
-
# - dist/src/server.js (MCP server implementation)
|
|
234
|
+
# - dist/src/server.js (MCP server implementation)
|
|
239
235
|
# - dist/resources/ (Compiled TypeScript + UI widget bundles)
|
|
240
236
|
```
|
|
241
237
|
|
|
@@ -315,10 +311,13 @@ export default defineConfig({
|
|
|
315
311
|
build: {
|
|
316
312
|
rollupOptions: {
|
|
317
313
|
input: {
|
|
318
|
-
'my-custom-widget': resolve(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
314
|
+
'my-custom-widget': resolve(
|
|
315
|
+
__dirname,
|
|
316
|
+
'resources/my-custom-widget.html'
|
|
317
|
+
),
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
322
321
|
})
|
|
323
322
|
```
|
|
324
323
|
|
|
@@ -374,12 +373,8 @@ const Counter: React.FC = () => {
|
|
|
374
373
|
return (
|
|
375
374
|
<div style={{ padding: '20px', textAlign: 'center' }}>
|
|
376
375
|
<h1>Counter: {count}</h1>
|
|
377
|
-
<button onClick={() => setCount(count + 1)}>
|
|
378
|
-
|
|
379
|
-
</button>
|
|
380
|
-
<button onClick={() => setCount(count - 1)}>
|
|
381
|
-
Decrement
|
|
382
|
-
</button>
|
|
376
|
+
<button onClick={() => setCount(count + 1)}>Increment</button>
|
|
377
|
+
<button onClick={() => setCount(count - 1)}>Decrement</button>
|
|
383
378
|
</div>
|
|
384
379
|
)
|
|
385
380
|
}
|
|
@@ -432,7 +427,7 @@ const DataTable: React.FC = () => {
|
|
|
432
427
|
</tr>
|
|
433
428
|
</thead>
|
|
434
429
|
<tbody>
|
|
435
|
-
{data.map(row => (
|
|
430
|
+
{data.map((row) => (
|
|
436
431
|
<tr key={row.id}>
|
|
437
432
|
<td>{row.name}</td>
|
|
438
433
|
<td>{row.value}</td>
|
|
@@ -22,7 +22,7 @@ server.tool({
|
|
|
22
22
|
required: true,
|
|
23
23
|
},
|
|
24
24
|
],
|
|
25
|
-
|
|
25
|
+
cb: async () => {
|
|
26
26
|
const uiResource = createUIResource({
|
|
27
27
|
uri: 'ui://widget/kanban-board',
|
|
28
28
|
content: {
|
|
@@ -49,7 +49,7 @@ server.resource({
|
|
|
49
49
|
audience: ['user', 'assistant'],
|
|
50
50
|
priority: 0.7
|
|
51
51
|
},
|
|
52
|
-
|
|
52
|
+
readCallback: async () => {
|
|
53
53
|
const widgetUrl = `http://localhost:${PORT}/mcp-use/widgets/kanban-board`
|
|
54
54
|
return {
|
|
55
55
|
contents: [{
|
|
@@ -17,8 +17,12 @@ The `uiResource` method is a powerful new addition that simplifies widget regist
|
|
|
17
17
|
|
|
18
18
|
```typescript
|
|
19
19
|
// Old way: Manual registration of tool and resource
|
|
20
|
-
server.tool({
|
|
21
|
-
|
|
20
|
+
server.tool({
|
|
21
|
+
/* tool config */
|
|
22
|
+
})
|
|
23
|
+
server.resource({
|
|
24
|
+
/* resource config */
|
|
25
|
+
})
|
|
22
26
|
|
|
23
27
|
// New way: Single method does both!
|
|
24
28
|
server.uiResource({
|
|
@@ -27,12 +31,13 @@ server.uiResource({
|
|
|
27
31
|
title: 'Kanban Board',
|
|
28
32
|
props: {
|
|
29
33
|
initialTasks: { type: 'array', required: false },
|
|
30
|
-
theme: { type: 'string', default: 'light' }
|
|
31
|
-
}
|
|
34
|
+
theme: { type: 'string', default: 'light' },
|
|
35
|
+
},
|
|
32
36
|
})
|
|
33
37
|
```
|
|
34
38
|
|
|
35
39
|
This automatically creates:
|
|
40
|
+
|
|
36
41
|
- **Tool**: `ui_kanban-board` - Accepts parameters and returns UIResource
|
|
37
42
|
- **Resource**: `ui://widget/kanban-board` - Static access with defaults
|
|
38
43
|
|
|
@@ -49,6 +54,7 @@ npm run dev
|
|
|
49
54
|
```
|
|
50
55
|
|
|
51
56
|
This will start:
|
|
57
|
+
|
|
52
58
|
- MCP server on port 3000
|
|
53
59
|
- Widget serving at `/mcp-use/widgets/*`
|
|
54
60
|
- Inspector UI at `/inspector`
|
|
@@ -72,7 +78,7 @@ import { createMCPServer } from 'mcp-use/server'
|
|
|
72
78
|
|
|
73
79
|
const server = createMCPServer('my-server', {
|
|
74
80
|
version: '1.0.0',
|
|
75
|
-
description: 'Server with UIResource widgets'
|
|
81
|
+
description: 'Server with UIResource widgets',
|
|
76
82
|
})
|
|
77
83
|
|
|
78
84
|
// Register a widget - creates both tool and resource
|
|
@@ -80,7 +86,7 @@ server.uiResource({
|
|
|
80
86
|
name: 'my-widget',
|
|
81
87
|
widget: 'my-widget',
|
|
82
88
|
title: 'My Widget',
|
|
83
|
-
description: 'An interactive widget'
|
|
89
|
+
description: 'An interactive widget',
|
|
84
90
|
})
|
|
85
91
|
|
|
86
92
|
server.listen(3000)
|
|
@@ -98,24 +104,24 @@ server.uiResource({
|
|
|
98
104
|
data: {
|
|
99
105
|
type: 'array',
|
|
100
106
|
description: 'Data points to display',
|
|
101
|
-
required: true
|
|
107
|
+
required: true,
|
|
102
108
|
},
|
|
103
109
|
chartType: {
|
|
104
110
|
type: 'string',
|
|
105
111
|
description: 'Type of chart (line/bar/pie)',
|
|
106
|
-
default: 'line'
|
|
112
|
+
default: 'line',
|
|
107
113
|
},
|
|
108
114
|
theme: {
|
|
109
115
|
type: 'string',
|
|
110
116
|
description: 'Visual theme',
|
|
111
|
-
default: 'light'
|
|
112
|
-
}
|
|
117
|
+
default: 'light',
|
|
118
|
+
},
|
|
113
119
|
},
|
|
114
120
|
size: ['800px', '400px'], // Preferred iframe size
|
|
115
121
|
annotations: {
|
|
116
122
|
audience: ['user', 'assistant'],
|
|
117
|
-
priority: 0.8
|
|
118
|
-
}
|
|
123
|
+
priority: 0.8,
|
|
124
|
+
},
|
|
119
125
|
})
|
|
120
126
|
```
|
|
121
127
|
|
|
@@ -135,7 +141,7 @@ interface MyWidgetProps {
|
|
|
135
141
|
|
|
136
142
|
const MyWidget: React.FC<MyWidgetProps> = ({
|
|
137
143
|
initialData = [],
|
|
138
|
-
theme = 'light'
|
|
144
|
+
theme = 'light',
|
|
139
145
|
}) => {
|
|
140
146
|
const [data, setData] = useState(initialData)
|
|
141
147
|
|
|
@@ -158,11 +164,7 @@ const MyWidget: React.FC<MyWidgetProps> = ({
|
|
|
158
164
|
}
|
|
159
165
|
}, [])
|
|
160
166
|
|
|
161
|
-
return
|
|
162
|
-
<div className={`widget theme-${theme}`}>
|
|
163
|
-
{/* Your widget UI */}
|
|
164
|
-
</div>
|
|
165
|
-
)
|
|
167
|
+
return <div className={`widget theme-${theme}`}>{/* Your widget UI */}</div>
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
// Mount the widget
|
|
@@ -185,34 +187,40 @@ server.uiResource({
|
|
|
185
187
|
initialData: {
|
|
186
188
|
type: 'array',
|
|
187
189
|
description: 'Initial data for the widget',
|
|
188
|
-
required: false
|
|
190
|
+
required: false,
|
|
189
191
|
},
|
|
190
192
|
theme: {
|
|
191
193
|
type: 'string',
|
|
192
194
|
description: 'Widget theme',
|
|
193
|
-
default: 'light'
|
|
194
|
-
}
|
|
195
|
+
default: 'light',
|
|
196
|
+
},
|
|
195
197
|
},
|
|
196
|
-
size: ['600px', '400px']
|
|
198
|
+
size: ['600px', '400px'],
|
|
197
199
|
})
|
|
198
200
|
```
|
|
199
201
|
|
|
200
202
|
## How It Works
|
|
201
203
|
|
|
202
204
|
### Tool Registration
|
|
205
|
+
|
|
203
206
|
When you call `uiResource`, it automatically creates a tool:
|
|
207
|
+
|
|
204
208
|
- Name: `ui_[widget-name]`
|
|
205
209
|
- Accepts all props as parameters
|
|
206
210
|
- Returns both text description and UIResource object
|
|
207
211
|
|
|
208
212
|
### Resource Registration
|
|
213
|
+
|
|
209
214
|
Also creates a resource:
|
|
215
|
+
|
|
210
216
|
- URI: `ui://widget/[widget-name]`
|
|
211
217
|
- Returns UIResource with default prop values
|
|
212
218
|
- Discoverable by MCP clients
|
|
213
219
|
|
|
214
220
|
### Parameter Passing
|
|
221
|
+
|
|
215
222
|
Tool parameters are automatically:
|
|
223
|
+
|
|
216
224
|
1. Converted to URL query parameters
|
|
217
225
|
2. Complex objects are JSON-stringified
|
|
218
226
|
3. Passed to widget via iframe URL
|
|
@@ -228,21 +236,21 @@ const widgets = [
|
|
|
228
236
|
widget: 'todo-list',
|
|
229
237
|
title: 'Todo List',
|
|
230
238
|
props: {
|
|
231
|
-
items: { type: 'array', default: [] }
|
|
232
|
-
}
|
|
239
|
+
items: { type: 'array', default: [] },
|
|
240
|
+
},
|
|
233
241
|
},
|
|
234
242
|
{
|
|
235
243
|
name: 'calendar',
|
|
236
244
|
widget: 'calendar',
|
|
237
245
|
title: 'Calendar',
|
|
238
246
|
props: {
|
|
239
|
-
date: { type: 'string', required: false }
|
|
240
|
-
}
|
|
241
|
-
}
|
|
247
|
+
date: { type: 'string', required: false },
|
|
248
|
+
},
|
|
249
|
+
},
|
|
242
250
|
]
|
|
243
251
|
|
|
244
252
|
// Register all widgets
|
|
245
|
-
widgets.forEach(widget => server.uiResource(widget))
|
|
253
|
+
widgets.forEach((widget) => server.uiResource(widget))
|
|
246
254
|
```
|
|
247
255
|
|
|
248
256
|
### Mixed Registration
|
|
@@ -252,14 +260,16 @@ widgets.forEach(widget => server.uiResource(widget))
|
|
|
252
260
|
server.uiResource({
|
|
253
261
|
name: 'dashboard',
|
|
254
262
|
widget: 'dashboard',
|
|
255
|
-
title: 'Analytics Dashboard'
|
|
263
|
+
title: 'Analytics Dashboard',
|
|
256
264
|
})
|
|
257
265
|
|
|
258
266
|
// Traditional tool for actions
|
|
259
267
|
server.tool({
|
|
260
268
|
name: 'calculate',
|
|
261
269
|
description: 'Perform calculations',
|
|
262
|
-
|
|
270
|
+
cb: async (params) => {
|
|
271
|
+
/* ... */
|
|
272
|
+
},
|
|
263
273
|
})
|
|
264
274
|
|
|
265
275
|
// Traditional resource for data
|
|
@@ -267,7 +277,9 @@ server.resource({
|
|
|
267
277
|
name: 'config',
|
|
268
278
|
uri: 'config://app',
|
|
269
279
|
mimeType: 'application/json',
|
|
270
|
-
|
|
280
|
+
readCallback: async () => {
|
|
281
|
+
/* ... */
|
|
282
|
+
},
|
|
271
283
|
})
|
|
272
284
|
```
|
|
273
285
|
|
|
@@ -289,6 +301,7 @@ server.resource({
|
|
|
289
301
|
#### WidgetProps
|
|
290
302
|
|
|
291
303
|
Each prop can have:
|
|
304
|
+
|
|
292
305
|
- `type: 'string' | 'number' | 'boolean' | 'object' | 'array'`
|
|
293
306
|
- `required?: boolean` - Whether the prop is required
|
|
294
307
|
- `default?: any` - Default value if not provided
|
|
@@ -297,14 +310,17 @@ Each prop can have:
|
|
|
297
310
|
## Testing Your Widgets
|
|
298
311
|
|
|
299
312
|
### Via Inspector UI
|
|
313
|
+
|
|
300
314
|
1. Start the server: `npm run dev`
|
|
301
315
|
2. Open: `http://localhost:3000/inspector`
|
|
302
316
|
3. Test tools and resources
|
|
303
317
|
|
|
304
318
|
### Direct Browser Access
|
|
319
|
+
|
|
305
320
|
Visit: `http://localhost:3000/mcp-use/widgets/[widget-name]`
|
|
306
321
|
|
|
307
322
|
### Via MCP Client
|
|
323
|
+
|
|
308
324
|
```typescript
|
|
309
325
|
// Call as tool
|
|
310
326
|
const result = await client.callTool('ui_kanban-board', {
|
|
@@ -328,16 +344,19 @@ const resource = await client.readResource('ui://widget/kanban-board')
|
|
|
328
344
|
## Troubleshooting
|
|
329
345
|
|
|
330
346
|
### Widget Not Loading
|
|
347
|
+
|
|
331
348
|
- Ensure widget exists in `dist/resources/mcp-use/widgets/`
|
|
332
349
|
- Check server console for errors
|
|
333
350
|
- Verify widget is registered with `uiResource()`
|
|
334
351
|
|
|
335
352
|
### Props Not Passed
|
|
353
|
+
|
|
336
354
|
- Check URL parameters in browser DevTools
|
|
337
355
|
- Ensure prop names match exactly
|
|
338
356
|
- Complex objects must be JSON-stringified
|
|
339
357
|
|
|
340
358
|
### Type Errors
|
|
359
|
+
|
|
341
360
|
- Import types: `import type { UIResourceDefinition } from 'mcp-use/server'`
|
|
342
361
|
- Ensure mcp-use is updated to latest version
|
|
343
362
|
|
|
@@ -347,8 +366,8 @@ If you have existing code using separate tool/resource:
|
|
|
347
366
|
|
|
348
367
|
```typescript
|
|
349
368
|
// Old pattern
|
|
350
|
-
server.tool({ name: 'show-widget'
|
|
351
|
-
server.resource({ uri: 'ui://widget'
|
|
369
|
+
server.tool({ name: 'show-widget' /* ... */ })
|
|
370
|
+
server.resource({ uri: 'ui://widget' /* ... */ })
|
|
352
371
|
|
|
353
372
|
// New pattern - replace both with:
|
|
354
373
|
server.uiResource({
|
|
@@ -361,6 +380,7 @@ server.uiResource({
|
|
|
361
380
|
## Future Enhancements
|
|
362
381
|
|
|
363
382
|
Coming soon:
|
|
383
|
+
|
|
364
384
|
- Automatic widget discovery from filesystem
|
|
365
385
|
- Widget manifests (widget.json)
|
|
366
386
|
- Prop extraction from TypeScript interfaces
|
|
@@ -373,4 +393,4 @@ Coming soon:
|
|
|
373
393
|
- [mcp-use Documentation](https://github.com/pyroprompt/mcp-use)
|
|
374
394
|
- [React Documentation](https://react.dev/)
|
|
375
395
|
|
|
376
|
-
Happy widget building! 🚀
|
|
396
|
+
Happy widget building! 🚀
|
|
@@ -289,7 +289,7 @@ root.appendChild(container);
|
|
|
289
289
|
server.tool({
|
|
290
290
|
name: 'get-widget-info',
|
|
291
291
|
description: 'Get information about available UI widgets',
|
|
292
|
-
|
|
292
|
+
cb: async () => {
|
|
293
293
|
const widgets = [
|
|
294
294
|
{
|
|
295
295
|
name: 'kanban-board',
|
|
@@ -321,10 +321,10 @@ server.tool({
|
|
|
321
321
|
` Resource: ${w.resource}\n` +
|
|
322
322
|
(w.url ? ` Browser: ${w.url}\n` : '')
|
|
323
323
|
).join('\n')}\n` +
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
324
|
+
`\nTypes Explained:\n` +
|
|
325
|
+
`• externalUrl: Iframe widget from filesystem\n` +
|
|
326
|
+
`• rawHtml: Direct HTML rendering\n` +
|
|
327
|
+
`• remoteDom: React/Web Components scripting`
|
|
328
328
|
}]
|
|
329
329
|
}
|
|
330
330
|
}
|
|
@@ -336,7 +336,7 @@ server.resource({
|
|
|
336
336
|
title: 'Server Configuration',
|
|
337
337
|
description: 'Current server configuration and status',
|
|
338
338
|
mimeType: 'application/json',
|
|
339
|
-
|
|
339
|
+
readCallback: async () => ({
|
|
340
340
|
contents: [{
|
|
341
341
|
uri: 'config://server',
|
|
342
342
|
mimeType: 'application/json',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-mcp-use-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Create MCP-Use apps with one command",
|
|
6
6
|
"author": "mcp-use, Inc.",
|
|
@@ -39,11 +39,14 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"commander": "^11.0.0",
|
|
41
41
|
"chalk": "^5.3.0",
|
|
42
|
-
"fs-extra": "^11.2.0"
|
|
42
|
+
"fs-extra": "^11.2.0",
|
|
43
|
+
"inquirer": "^9.3.8",
|
|
44
|
+
"ora": "^8.2.0"
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
45
47
|
"@types/node": "^20.0.0",
|
|
46
48
|
"@types/fs-extra": "^11.0.4",
|
|
49
|
+
"@types/inquirer": "^9.0.9",
|
|
47
50
|
"typescript": "^5.0.0",
|
|
48
51
|
"vitest": "^1.0.0"
|
|
49
52
|
},
|