create-mcp-use-app 0.4.8 → 0.4.9
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 +303 -168
- package/dist/templates/apps-sdk/index.ts +16 -18
- package/dist/templates/apps-sdk/package.json +1 -1
- package/dist/templates/apps-sdk/resources/display-weather.tsx +55 -41
- package/dist/templates/apps-sdk/styles.css +3 -4
- package/dist/templates/apps-sdk/tsconfig.json +0 -1
- package/dist/templates/mcp-ui/index.ts +111 -103
- package/dist/templates/mcp-ui/package.json +1 -1
- package/dist/templates/mcp-ui/resources/kanban-board.tsx +228 -150
- package/dist/templates/mcp-ui/tsconfig.json +0 -1
- package/dist/templates/starter/index.ts +65 -66
- package/dist/templates/starter/package.json +1 -1
- package/dist/templates/starter/resources/display-weather.tsx +56 -42
- package/dist/templates/starter/resources/kanban-board.tsx +228 -150
- package/dist/templates/starter/styles.css +3 -4
- package/dist/templates/starter/tsconfig.json +0 -1
- package/package.json +1 -1
|
@@ -1,82 +1,96 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { z } from
|
|
3
|
-
import { useWidget } from
|
|
4
|
-
import
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { useWidget } from "mcp-use/react";
|
|
4
|
+
import "../styles.css";
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
|
-
* Apps SDK widget
|
|
8
|
-
* Just export widgetMetadata with description and Zod schema, and mcp-use handles the rest!
|
|
9
|
-
* See docs: https://docs.mcp-use.com/typescript/server/ui-widgets
|
|
10
|
-
*/
|
|
7
|
+
* Apps SDK widget
|
|
8
|
+
* Just export widgetMetadata with description and Zod schema, and mcp-use handles the rest!
|
|
9
|
+
* See docs: https://docs.mcp-use.com/typescript/server/ui-widgets
|
|
10
|
+
*/
|
|
11
11
|
|
|
12
12
|
const propSchema = z.object({
|
|
13
|
-
city: z.string().describe(
|
|
14
|
-
weather: z
|
|
15
|
-
|
|
13
|
+
city: z.string().describe("The city to display weather for"),
|
|
14
|
+
weather: z
|
|
15
|
+
.enum(["sunny", "rain", "snow", "cloudy"])
|
|
16
|
+
.describe("The weather condition"),
|
|
17
|
+
temperature: z
|
|
18
|
+
.number()
|
|
19
|
+
.min(-20)
|
|
20
|
+
.max(50)
|
|
21
|
+
.describe("The temperature in Celsius"),
|
|
16
22
|
});
|
|
17
23
|
|
|
18
24
|
export const widgetMetadata = {
|
|
19
|
-
description:
|
|
25
|
+
description: "Display weather for a city",
|
|
20
26
|
inputs: propSchema,
|
|
21
|
-
}
|
|
27
|
+
};
|
|
22
28
|
|
|
23
29
|
type WeatherProps = z.infer<typeof propSchema>;
|
|
24
30
|
|
|
25
31
|
const WeatherWidget: React.FC = () => {
|
|
26
32
|
// Use the useWidget hook to get props from OpenAI Apps SDK
|
|
27
33
|
const { props, theme } = useWidget<WeatherProps>();
|
|
28
|
-
|
|
29
|
-
console.log(props)
|
|
34
|
+
|
|
35
|
+
console.log(props);
|
|
30
36
|
|
|
31
37
|
const { city, weather, temperature } = props;
|
|
32
38
|
const getWeatherIcon = (weatherType: string) => {
|
|
33
39
|
switch (weatherType?.toLowerCase()) {
|
|
34
|
-
case
|
|
35
|
-
return
|
|
36
|
-
case
|
|
37
|
-
return
|
|
38
|
-
case
|
|
39
|
-
return
|
|
40
|
-
case
|
|
41
|
-
return
|
|
40
|
+
case "sunny":
|
|
41
|
+
return "☀️";
|
|
42
|
+
case "rain":
|
|
43
|
+
return "🌧️";
|
|
44
|
+
case "snow":
|
|
45
|
+
return "❄️";
|
|
46
|
+
case "cloudy":
|
|
47
|
+
return "☁️";
|
|
42
48
|
default:
|
|
43
|
-
return
|
|
49
|
+
return "🌤️";
|
|
44
50
|
}
|
|
45
51
|
};
|
|
46
52
|
|
|
47
53
|
const getWeatherColor = (weatherType: string) => {
|
|
48
54
|
switch (weatherType?.toLowerCase()) {
|
|
49
|
-
case
|
|
50
|
-
return
|
|
51
|
-
case
|
|
52
|
-
return
|
|
53
|
-
case
|
|
54
|
-
return
|
|
55
|
-
case
|
|
56
|
-
return
|
|
55
|
+
case "sunny":
|
|
56
|
+
return "from-yellow-400 to-orange-500";
|
|
57
|
+
case "rain":
|
|
58
|
+
return "from-blue-400 to-blue-600";
|
|
59
|
+
case "snow":
|
|
60
|
+
return "from-blue-100 to-blue-300";
|
|
61
|
+
case "cloudy":
|
|
62
|
+
return "from-gray-400 to-gray-600";
|
|
57
63
|
default:
|
|
58
|
-
return
|
|
64
|
+
return "from-gray-300 to-gray-500";
|
|
59
65
|
}
|
|
60
66
|
};
|
|
61
67
|
|
|
62
68
|
// Theme-aware styling
|
|
63
|
-
const bgColor = theme ===
|
|
64
|
-
const textColor = theme ===
|
|
65
|
-
const subtextColor = theme ===
|
|
69
|
+
const bgColor = theme === "dark" ? "bg-gray-900" : "bg-white";
|
|
70
|
+
const textColor = theme === "dark" ? "text-gray-100" : "text-gray-800";
|
|
71
|
+
const subtextColor = theme === "dark" ? "text-gray-400" : "text-gray-600";
|
|
66
72
|
|
|
67
73
|
return (
|
|
68
|
-
<div
|
|
69
|
-
|
|
74
|
+
<div
|
|
75
|
+
className={`max-w-sm mx-auto ${bgColor} rounded-xl shadow-lg overflow-hidden`}
|
|
76
|
+
>
|
|
77
|
+
<div
|
|
78
|
+
className={`h-32 bg-gradient-to-br ${getWeatherColor(weather)} flex items-center justify-center`}
|
|
79
|
+
>
|
|
70
80
|
<div className="text-6xl">{getWeatherIcon(weather)}</div>
|
|
71
81
|
</div>
|
|
72
|
-
|
|
82
|
+
|
|
73
83
|
<div className="p-6">
|
|
74
84
|
<div className="text-center">
|
|
75
85
|
<h2 className={`text-2xl font-bold ${textColor} mb-2`}>{city}</h2>
|
|
76
86
|
<div className="flex items-center justify-center space-x-4">
|
|
77
|
-
<span className={`text-4xl font-light ${textColor}`}>
|
|
87
|
+
<span className={`text-4xl font-light ${textColor}`}>
|
|
88
|
+
{temperature}°
|
|
89
|
+
</span>
|
|
78
90
|
<div className="text-right">
|
|
79
|
-
<p className={`text-lg font-medium ${subtextColor} capitalize`}>
|
|
91
|
+
<p className={`text-lg font-medium ${subtextColor} capitalize`}>
|
|
92
|
+
{weather}
|
|
93
|
+
</p>
|
|
80
94
|
<p className={`text-sm ${subtextColor}`}>Current</p>
|
|
81
95
|
</div>
|
|
82
96
|
</div>
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
/* Custom styles */
|
|
4
4
|
body {
|
|
5
5
|
margin: 0;
|
|
6
|
-
font-family:
|
|
7
|
-
|
|
8
|
-
sans-serif;
|
|
6
|
+
font-family:
|
|
7
|
+
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
|
|
8
|
+
"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
|
9
9
|
-webkit-font-smoothing: antialiased;
|
|
10
10
|
-moz-osx-font-smoothing: grayscale;
|
|
11
11
|
}
|
|
12
|
-
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import { createMCPServer } from
|
|
2
|
-
import type {
|
|
3
|
-
RawHtmlUIResource,
|
|
4
|
-
RemoteDomUIResource
|
|
5
|
-
} from 'mcp-use/server'
|
|
1
|
+
import { createMCPServer } from "mcp-use/server";
|
|
2
|
+
import type { RawHtmlUIResource, RemoteDomUIResource } from "mcp-use/server";
|
|
6
3
|
|
|
7
4
|
// Create an MCP server with MCP-UI UIResource support
|
|
8
|
-
const server = createMCPServer(
|
|
9
|
-
version:
|
|
10
|
-
description:
|
|
5
|
+
const server = createMCPServer("uiresource-mcp-server", {
|
|
6
|
+
version: "1.0.0",
|
|
7
|
+
description: "MCP server demonstrating all UIResource types",
|
|
11
8
|
baseUrl: process.env.MCP_URL, // Full base URL (e.g., https://myserver.com)
|
|
12
|
-
})
|
|
9
|
+
});
|
|
13
10
|
|
|
14
|
-
const PORT = process.env.PORT || 3000
|
|
11
|
+
const PORT = process.env.PORT || 3000;
|
|
15
12
|
|
|
16
13
|
/**
|
|
17
14
|
* ════════════════════════════════════════════════════════════════════
|
|
@@ -27,7 +24,6 @@ const PORT = process.env.PORT || 3000
|
|
|
27
24
|
* 3. Serves the widget from dist/resources/mcp-use/widgets/kanban-board/
|
|
28
25
|
*/
|
|
29
26
|
|
|
30
|
-
|
|
31
27
|
/**
|
|
32
28
|
* ════════════════════════════════════════════════════════════════════
|
|
33
29
|
* Type 2: Raw HTML
|
|
@@ -40,10 +36,10 @@ const PORT = process.env.PORT || 3000
|
|
|
40
36
|
* - Resource: ui://widget/welcome-card
|
|
41
37
|
*/
|
|
42
38
|
server.uiResource({
|
|
43
|
-
type:
|
|
44
|
-
name:
|
|
45
|
-
title:
|
|
46
|
-
description:
|
|
39
|
+
type: "rawHtml",
|
|
40
|
+
name: "welcome-card",
|
|
41
|
+
title: "Welcome Message",
|
|
42
|
+
description: "A welcoming card with server information",
|
|
47
43
|
htmlContent: `
|
|
48
44
|
<!DOCTYPE html>
|
|
49
45
|
<html>
|
|
@@ -122,9 +118,9 @@ server.uiResource({
|
|
|
122
118
|
</body>
|
|
123
119
|
</html>
|
|
124
120
|
`,
|
|
125
|
-
encoding:
|
|
126
|
-
size: [
|
|
127
|
-
} satisfies RawHtmlUIResource)
|
|
121
|
+
encoding: "text",
|
|
122
|
+
size: ["600px", "400px"],
|
|
123
|
+
} satisfies RawHtmlUIResource);
|
|
128
124
|
|
|
129
125
|
/**
|
|
130
126
|
* ════════════════════════════════════════════════════════════════════
|
|
@@ -138,17 +134,17 @@ server.uiResource({
|
|
|
138
134
|
* - Resource: ui://widget/quick-poll
|
|
139
135
|
*/
|
|
140
136
|
server.uiResource({
|
|
141
|
-
type:
|
|
142
|
-
name:
|
|
143
|
-
title:
|
|
144
|
-
description:
|
|
137
|
+
type: "remoteDom",
|
|
138
|
+
name: "quick-poll",
|
|
139
|
+
title: "Quick Poll",
|
|
140
|
+
description: "Create instant polls with interactive voting",
|
|
145
141
|
script: `
|
|
146
142
|
// Remote DOM script for quick-poll widget
|
|
147
143
|
// Note: Remote DOM only supports registered MCP-UI components like ui-stack, ui-text, ui-button
|
|
148
144
|
// Standard HTML elements (div, h2, p, etc.) are NOT available
|
|
149
145
|
|
|
150
146
|
// Get props (passed from tool parameters)
|
|
151
|
-
const props = ${JSON.stringify({ question:
|
|
147
|
+
const props = ${JSON.stringify({ question: "What is your favorite framework?", options: ["React", "Vue", "Svelte", "Angular"] })};
|
|
152
148
|
|
|
153
149
|
// Create main container stack (vertical layout)
|
|
154
150
|
const container = document.createElement('ui-stack');
|
|
@@ -235,22 +231,22 @@ container.appendChild(resultsTitle);
|
|
|
235
231
|
// Append to root
|
|
236
232
|
root.appendChild(container);
|
|
237
233
|
`,
|
|
238
|
-
framework:
|
|
239
|
-
encoding:
|
|
240
|
-
size: [
|
|
234
|
+
framework: "react",
|
|
235
|
+
encoding: "text",
|
|
236
|
+
size: ["500px", "450px"],
|
|
241
237
|
props: {
|
|
242
238
|
question: {
|
|
243
|
-
type:
|
|
244
|
-
description:
|
|
245
|
-
default:
|
|
239
|
+
type: "string",
|
|
240
|
+
description: "The poll question",
|
|
241
|
+
default: "What is your favorite framework?",
|
|
246
242
|
},
|
|
247
243
|
options: {
|
|
248
|
-
type:
|
|
249
|
-
description:
|
|
250
|
-
default: [
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
} satisfies RemoteDomUIResource)
|
|
244
|
+
type: "array",
|
|
245
|
+
description: "Poll options",
|
|
246
|
+
default: ["React", "Vue", "Svelte"],
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
} satisfies RemoteDomUIResource);
|
|
254
250
|
|
|
255
251
|
/**
|
|
256
252
|
* ════════════════════════════════════════════════════════════════════
|
|
@@ -261,83 +257,95 @@ root.appendChild(container);
|
|
|
261
257
|
*/
|
|
262
258
|
|
|
263
259
|
server.tool({
|
|
264
|
-
name:
|
|
265
|
-
description:
|
|
260
|
+
name: "get-widget-info",
|
|
261
|
+
description: "Get information about available UI widgets",
|
|
266
262
|
cb: async () => {
|
|
267
263
|
const widgets = [
|
|
268
264
|
{
|
|
269
|
-
name:
|
|
270
|
-
type:
|
|
271
|
-
tool:
|
|
272
|
-
resource:
|
|
273
|
-
url: `http://localhost:${PORT}/mcp-use/widgets/kanban-board
|
|
265
|
+
name: "kanban-board",
|
|
266
|
+
type: "externalUrl",
|
|
267
|
+
tool: "kanban-board",
|
|
268
|
+
resource: "ui://widget/kanban-board",
|
|
269
|
+
url: `http://localhost:${PORT}/mcp-use/widgets/kanban-board`,
|
|
274
270
|
},
|
|
275
271
|
{
|
|
276
|
-
name:
|
|
277
|
-
type:
|
|
278
|
-
tool:
|
|
279
|
-
resource:
|
|
272
|
+
name: "welcome-card",
|
|
273
|
+
type: "rawHtml",
|
|
274
|
+
tool: "welcome-card",
|
|
275
|
+
resource: "ui://widget/welcome-card",
|
|
280
276
|
},
|
|
281
277
|
{
|
|
282
|
-
name:
|
|
283
|
-
type:
|
|
284
|
-
tool:
|
|
285
|
-
resource:
|
|
286
|
-
}
|
|
287
|
-
]
|
|
278
|
+
name: "quick-poll",
|
|
279
|
+
type: "remoteDom",
|
|
280
|
+
tool: "quick-poll",
|
|
281
|
+
resource: "ui://widget/quick-poll",
|
|
282
|
+
},
|
|
283
|
+
];
|
|
288
284
|
|
|
289
285
|
return {
|
|
290
|
-
content: [
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
286
|
+
content: [
|
|
287
|
+
{
|
|
288
|
+
type: "text",
|
|
289
|
+
text:
|
|
290
|
+
`Available UI Widgets:\n\n${widgets
|
|
291
|
+
.map(
|
|
292
|
+
(w) =>
|
|
293
|
+
`📦 ${w.name} (${w.type})\n` +
|
|
294
|
+
` Tool: ${w.tool}\n` +
|
|
295
|
+
` Resource: ${w.resource}\n` +
|
|
296
|
+
(w.url ? ` Browser: ${w.url}\n` : "")
|
|
297
|
+
)
|
|
298
|
+
.join("\n")}\n` +
|
|
299
|
+
`\nTypes Explained:\n` +
|
|
300
|
+
`• externalUrl: Iframe widget from filesystem\n` +
|
|
301
|
+
`• rawHtml: Direct HTML rendering\n` +
|
|
302
|
+
`• remoteDom: React/Web Components scripting`,
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
});
|
|
306
308
|
|
|
307
309
|
server.resource({
|
|
308
|
-
name:
|
|
309
|
-
uri:
|
|
310
|
-
title:
|
|
311
|
-
description:
|
|
312
|
-
mimeType:
|
|
310
|
+
name: "server-config",
|
|
311
|
+
uri: "config://server",
|
|
312
|
+
title: "Server Configuration",
|
|
313
|
+
description: "Current server configuration and status",
|
|
314
|
+
mimeType: "application/json",
|
|
313
315
|
readCallback: async () => ({
|
|
314
|
-
contents: [
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
316
|
+
contents: [
|
|
317
|
+
{
|
|
318
|
+
uri: "config://server",
|
|
319
|
+
mimeType: "application/json",
|
|
320
|
+
text: JSON.stringify(
|
|
321
|
+
{
|
|
322
|
+
port: PORT,
|
|
323
|
+
version: "1.0.0",
|
|
324
|
+
widgets: {
|
|
325
|
+
total: 3,
|
|
326
|
+
types: {
|
|
327
|
+
externalUrl: ["kanban-board"],
|
|
328
|
+
rawHtml: ["welcome-card"],
|
|
329
|
+
remoteDom: ["quick-poll"],
|
|
330
|
+
},
|
|
331
|
+
baseUrl: `http://localhost:${PORT}/mcp-use/widgets/`,
|
|
332
|
+
},
|
|
333
|
+
endpoints: {
|
|
334
|
+
mcp: `http://localhost:${PORT}/mcp`,
|
|
335
|
+
inspector: `http://localhost:${PORT}/inspector`,
|
|
336
|
+
widgets: `http://localhost:${PORT}/mcp-use/widgets/`,
|
|
337
|
+
},
|
|
326
338
|
},
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}, null, 2)
|
|
335
|
-
}]
|
|
336
|
-
})
|
|
337
|
-
})
|
|
339
|
+
null,
|
|
340
|
+
2
|
|
341
|
+
),
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
}),
|
|
345
|
+
});
|
|
338
346
|
|
|
339
347
|
// Start the server
|
|
340
|
-
server.listen(PORT)
|
|
348
|
+
server.listen(PORT);
|
|
341
349
|
|
|
342
350
|
// Display helpful startup message
|
|
343
351
|
console.log(`
|
|
@@ -388,12 +396,12 @@ Server is running on port ${PORT}
|
|
|
388
396
|
})
|
|
389
397
|
|
|
390
398
|
💡 Tip: Open the Inspector UI to test all widget types interactively!
|
|
391
|
-
`)
|
|
399
|
+
`);
|
|
392
400
|
|
|
393
401
|
// Handle graceful shutdown
|
|
394
|
-
process.on(
|
|
395
|
-
console.log(
|
|
396
|
-
process.exit(0)
|
|
397
|
-
})
|
|
402
|
+
process.on("SIGINT", () => {
|
|
403
|
+
console.log("\n\nShutting down server...");
|
|
404
|
+
process.exit(0);
|
|
405
|
+
});
|
|
398
406
|
|
|
399
|
-
export default server
|
|
407
|
+
export default server;
|