create-mcp-use-app 0.5.2 → 0.6.0-canary.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.
Files changed (30) hide show
  1. package/dist/templates/apps-sdk/README.md +144 -4
  2. package/dist/templates/apps-sdk/index.ts +72 -6
  3. package/dist/templates/apps-sdk/package.json +3 -0
  4. package/dist/templates/apps-sdk/public/fruits/apple.png +0 -0
  5. package/dist/templates/apps-sdk/public/fruits/apricot.png +0 -0
  6. package/dist/templates/apps-sdk/public/fruits/avocado.png +0 -0
  7. package/dist/templates/apps-sdk/public/fruits/banana.png +0 -0
  8. package/dist/templates/apps-sdk/public/fruits/blueberry.png +0 -0
  9. package/dist/templates/apps-sdk/public/fruits/cherries.png +0 -0
  10. package/dist/templates/apps-sdk/public/fruits/coconut.png +0 -0
  11. package/dist/templates/apps-sdk/public/fruits/grapes.png +0 -0
  12. package/dist/templates/apps-sdk/public/fruits/lemon.png +0 -0
  13. package/dist/templates/apps-sdk/public/fruits/mango.png +0 -0
  14. package/dist/templates/apps-sdk/public/fruits/orange.png +0 -0
  15. package/dist/templates/apps-sdk/public/fruits/pear.png +0 -0
  16. package/dist/templates/apps-sdk/public/fruits/pineapple.png +0 -0
  17. package/dist/templates/apps-sdk/public/fruits/plum.png +0 -0
  18. package/dist/templates/apps-sdk/public/fruits/strawberry.png +0 -0
  19. package/dist/templates/apps-sdk/public/fruits/watermelon.png +0 -0
  20. package/dist/templates/apps-sdk/resources/product-search-result/components/Accordion.tsx +40 -0
  21. package/dist/templates/apps-sdk/resources/product-search-result/components/AccordionItem.tsx +34 -0
  22. package/dist/templates/apps-sdk/resources/product-search-result/components/Carousel.tsx +65 -0
  23. package/dist/templates/apps-sdk/resources/product-search-result/components/CarouselItem.tsx +26 -0
  24. package/dist/templates/apps-sdk/resources/product-search-result/constants.ts +4 -0
  25. package/dist/templates/apps-sdk/resources/product-search-result/hooks/useCarouselAnimation.ts +78 -0
  26. package/dist/templates/apps-sdk/resources/product-search-result/types.ts +14 -0
  27. package/dist/templates/apps-sdk/resources/product-search-result/widget.tsx +61 -0
  28. package/dist/templates/apps-sdk/styles.css +104 -0
  29. package/package.json +1 -1
  30. package/dist/templates/apps-sdk/resources/display-weather.tsx +0 -103
@@ -7,11 +7,13 @@ An MCP server template with OpenAI Apps SDK integration for ChatGPT-compatible w
7
7
  ## Features
8
8
 
9
9
  - **🤖 OpenAI Apps SDK**: Full compatibility with ChatGPT widgets
10
- - **🎨 React Widgets**: Interactive UI components with theme support
10
+ - **🎨 Official UI Components**: Integrated [OpenAI Apps SDK UI components](https://openai.github.io/apps-sdk-ui/) for consistent, accessible widgets
11
+ - **🛒 Ecommerce Widgets**: Complete ecommerce example with carousel, search, map, and order confirmation
11
12
  - **🔄 Automatic Registration**: Widgets auto-register from `resources/` folder
12
13
  - **📦 Props Schema**: Zod schema validation for widget props
13
14
  - **🌙 Theme Support**: Dark/light theme detection via `useWidget` hook
14
15
  - **🛠️ TypeScript**: Complete type safety
16
+ - **🔧 Widget Capabilities**: Full support for `callTool`, `sendFollowUpMessage`, and persistent state
15
17
 
16
18
  ## What's New: Apps SDK Integration
17
19
 
@@ -59,9 +61,13 @@ npm start
59
61
 
60
62
  ```
61
63
  apps-sdk/
62
- ├── resources/ # React widget components
63
- └── display-weather.tsx # Weather widget example
64
- ├── index.ts # Server entry point
64
+ ├── resources/ # React widget components
65
+ ├── display-weather.tsx # Weather widget example
66
+ ├── ecommerce-carousel.tsx # Ecommerce product carousel
67
+ │ ├── product-search-result.tsx # Product search with filters
68
+ │ ├── stores-locations-map.tsx # Store locations map
69
+ │ └── order-confirmation.tsx # Order confirmation widget
70
+ ├── index.ts # Server entry point (includes brand info tool)
65
71
  ├── package.json
66
72
  ├── tsconfig.json
67
73
  └── README.md
@@ -153,6 +159,89 @@ const bgColor = theme === 'dark' ? 'bg-gray-900' : 'bg-white';
153
159
  const textColor = theme === 'dark' ? 'text-gray-100' : 'text-gray-800';
154
160
  ```
155
161
 
162
+ ## Official UI Components
163
+
164
+ This template uses the [OpenAI Apps SDK UI component library](https://openai.github.io/apps-sdk-ui/) for building consistent, accessible widgets. The library provides:
165
+
166
+ - **Button**: Primary, secondary, and outline button variants
167
+ - **Card**: Container component for content sections
168
+ - **Carousel**: Image and content carousel with transitions
169
+ - **Input**: Form input fields
170
+ - **Icon**: Consistent iconography
171
+ - **Transition**: Smooth animations and transitions
172
+
173
+ Import components like this:
174
+
175
+ ```typescript
176
+ import {
177
+ Button,
178
+ Card,
179
+ Carousel,
180
+ CarouselItem,
181
+ Transition,
182
+ Icon,
183
+ Input,
184
+ } from '@openai/apps-sdk-ui';
185
+ ```
186
+
187
+ ## Ecommerce Widgets
188
+
189
+ This template includes a complete ecommerce example with four widgets:
190
+
191
+ ### 1. Ecommerce Carousel (`ecommerce-carousel.tsx`)
192
+
193
+ A product carousel widget featuring:
194
+ - Title and description
195
+ - Carousel of product items with placeholder images
196
+ - Info button and Add to Cart button for each item
197
+ - Uses official Carousel, Card, Button, Icon, and Transition components
198
+ - Integrates with `callTool` for cart operations
199
+ - Persistent state management
200
+
201
+ ### 2. Product Search Result (`product-search-result.tsx`)
202
+
203
+ A search results widget with:
204
+ - Search input with real-time filtering
205
+ - Price range filters and stock status filter
206
+ - Grid layout of product cards
207
+ - Uses `callTool` to perform searches
208
+ - Uses `sendFollowUpMessage` to update conversation
209
+ - Persistent filter state
210
+
211
+ ### 3. Stores Locations Map (`stores-locations-map.tsx`)
212
+
213
+ A store locator widget featuring:
214
+ - Interactive map display (placeholder)
215
+ - List of store locations with details
216
+ - Distance calculation
217
+ - Get directions functionality
218
+ - Store details on click
219
+ - Uses `callTool` for directions and store info
220
+
221
+ ### 4. Order Confirmation (`order-confirmation.tsx`)
222
+
223
+ An order confirmation widget with:
224
+ - Order summary and items list
225
+ - Shipping information
226
+ - Order status tracking
227
+ - Track order and view receipt actions
228
+ - Uses `callTool` for order tracking
229
+
230
+ ## Brand Info Tool
231
+
232
+ The template includes a `get-brand-info` tool (normal MCP tool, not a widget) that returns brand information:
233
+
234
+ ```typescript
235
+ // Call the tool
236
+ await client.callTool('get-brand-info', {});
237
+
238
+ // Returns brand details including:
239
+ // - Company name, tagline, description
240
+ // - Mission and values
241
+ // - Contact information
242
+ // - Social media links
243
+ ```
244
+
156
245
  ## Example: Weather Widget
157
246
 
158
247
  The included `display-weather.tsx` widget demonstrates:
@@ -321,8 +410,59 @@ const { props } = useWidget();
321
410
  const city = props.city;
322
411
  ```
323
412
 
413
+ ## Using Widget Capabilities
414
+
415
+ The widgets in this template demonstrate the full capabilities of the Apps SDK:
416
+
417
+ ### Calling Tools (`callTool`)
418
+
419
+ Widgets can call other MCP tools:
420
+
421
+ ```typescript
422
+ const { callTool } = useWidget();
423
+
424
+ const handleAction = async () => {
425
+ const result = await callTool('add-to-cart', {
426
+ productId: '123',
427
+ productName: 'Product Name',
428
+ price: 29.99
429
+ });
430
+ };
431
+ ```
432
+
433
+ ### Sending Follow-up Messages (`sendFollowUpMessage`)
434
+
435
+ Widgets can send messages to the ChatGPT conversation:
436
+
437
+ ```typescript
438
+ const { sendFollowUpMessage } = useWidget();
439
+
440
+ await sendFollowUpMessage('Product added to cart successfully!');
441
+ ```
442
+
443
+ ### Persistent State (`setState`)
444
+
445
+ Widgets can maintain state across interactions:
446
+
447
+ ```typescript
448
+ const { setState, state } = useWidget();
449
+
450
+ // Save state
451
+ await setState({ cart: [...cart, newItem] });
452
+
453
+ // Read state
454
+ const savedCart = state?.cart || [];
455
+ ```
456
+
457
+ ## Component Library Note
458
+
459
+ This template uses the [OpenAI Apps SDK UI component library](https://openai.github.io/apps-sdk-ui/). The exact component API may vary based on the library version. If you encounter import errors, check the [official documentation](https://openai.github.io/apps-sdk-ui/) for the correct component names and props.
460
+
461
+ If the official library is not available, you can replace the imports with custom React components or other UI libraries while maintaining the same widget structure.
462
+
324
463
  ## Learn More
325
464
 
465
+ - [OpenAI Apps SDK UI Components](https://openai.github.io/apps-sdk-ui/) - Official component library
326
466
  - [MCP Documentation](https://modelcontextprotocol.io)
327
467
  - [OpenAI Apps SDK](https://platform.openai.com/docs/apps)
328
468
  - [mcp-use Documentation](https://docs.mcp-use.com)
@@ -11,20 +11,86 @@ const server = createMCPServer("test-app", {
11
11
  * Just export widgetMetadata with description and Zod schema, and mcp-use handles the rest!
12
12
  *
13
13
  * It will automatically add to your MCP server:
14
- * - server.tool('display-weather')
15
- * - server.resource('ui://widget/display-weather')
14
+ * - server.tool('get-brand-info')
15
+ * - server.resource('ui://widget/get-brand-info')
16
16
  *
17
17
  * See docs: https://docs.mcp-use.com/typescript/server/ui-widgets
18
18
  */
19
19
 
20
20
  /**
21
- * Add here yourtandard MCP tools, resources and prompts
21
+ * Add here your standard MCP tools, resources and prompts
22
22
  */
23
+
24
+ // Fruits data for the API
25
+ const fruits = [
26
+ { fruit: "mango", color: "bg-[#FBF1E1] dark:bg-[#FBF1E1]/10" },
27
+ { fruit: "pineapple", color: "bg-[#f8f0d9] dark:bg-[#f8f0d9]/10" },
28
+ { fruit: "cherries", color: "bg-[#E2EDDC] dark:bg-[#E2EDDC]/10" },
29
+ { fruit: "coconut", color: "bg-[#fbedd3] dark:bg-[#fbedd3]/10" },
30
+ { fruit: "apricot", color: "bg-[#fee6ca] dark:bg-[#fee6ca]/10" },
31
+ { fruit: "blueberry", color: "bg-[#e0e6e6] dark:bg-[#e0e6e6]/10" },
32
+ { fruit: "grapes", color: "bg-[#f4ebe2] dark:bg-[#f4ebe2]/10" },
33
+ { fruit: "watermelon", color: "bg-[#e6eddb] dark:bg-[#e6eddb]/10" },
34
+ { fruit: "orange", color: "bg-[#fdebdf] dark:bg-[#fdebdf]/10" },
35
+ { fruit: "avocado", color: "bg-[#ecefda] dark:bg-[#ecefda]/10" },
36
+ { fruit: "apple", color: "bg-[#F9E7E4] dark:bg-[#F9E7E4]/10" },
37
+ { fruit: "pear", color: "bg-[#f1f1cf] dark:bg-[#f1f1cf]/10" },
38
+ { fruit: "plum", color: "bg-[#ece5ec] dark:bg-[#ece5ec]/10" },
39
+ { fruit: "banana", color: "bg-[#fdf0dd] dark:bg-[#fdf0dd]/10" },
40
+ { fruit: "strawberry", color: "bg-[#f7e6df] dark:bg-[#f7e6df]/10" },
41
+ { fruit: "lemon", color: "bg-[#feeecd] dark:bg-[#feeecd]/10" },
42
+ ];
43
+
44
+ // API endpoint for fruits data
45
+ server.get("/api/fruits", (c) => {
46
+ return c.json(fruits);
47
+ });
48
+
49
+ // Brand Info Tool - Returns brand information
23
50
  server.tool({
24
- name: "get-my-city",
25
- description: "Get my city",
51
+ name: "get-brand-info",
52
+ description:
53
+ "Get information about the brand, including company details, mission, and values",
26
54
  cb: async () => {
27
- return { content: [{ type: "text", text: `My city is San Francisco` }] };
55
+ return {
56
+ content: [
57
+ {
58
+ type: "text",
59
+ text: JSON.stringify(
60
+ {
61
+ name: "mcp-use",
62
+ tagline: "Build MCP servers with UI widgets in minutes",
63
+ description:
64
+ "mcp-use is a modern framework for building Model Context Protocol (MCP) servers with automatic UI widget registration, making it easy to create interactive AI tools and resources.",
65
+ founded: "2024",
66
+ mission:
67
+ "To simplify the development of MCP servers and make AI integration accessible for developers",
68
+ values: [
69
+ "Developer Experience",
70
+ "Simplicity",
71
+ "Performance",
72
+ "Open Source",
73
+ "Innovation",
74
+ ],
75
+ contact: {
76
+ website: "https://mcp-use.com",
77
+ docs: "https://docs.mcp-use.com",
78
+ github: "https://github.com/mcp-use/mcp-use",
79
+ },
80
+ features: [
81
+ "Automatic UI widget registration",
82
+ "React component support",
83
+ "Full TypeScript support",
84
+ "Built-in HTTP server",
85
+ "MCP protocol compliance",
86
+ ],
87
+ },
88
+ null,
89
+ 2
90
+ ),
91
+ },
92
+ ],
93
+ };
28
94
  },
29
95
  });
30
96
 
@@ -27,11 +27,14 @@
27
27
  "deploy": "mcp-use deploy"
28
28
  },
29
29
  "dependencies": {
30
+ "@openai/apps-sdk-ui": "^0.1.0",
31
+ "@tanstack/react-query": "^5.0.0",
30
32
  "cors": "^2.8.5",
31
33
  "express": "^4.18.0",
32
34
  "mcp-use": "workspace:*",
33
35
  "react": "^19.2.0",
34
36
  "react-dom": "^19.2.0",
37
+ "react-router": "^7.9.6",
35
38
  "tailwindcss": "^4.0.0",
36
39
  "zod": "^4.1.12"
37
40
  },
@@ -0,0 +1,40 @@
1
+ import React, { useState } from "react";
2
+ import { AccordionItem } from "./AccordionItem";
3
+
4
+ export interface AccordionItemData {
5
+ question: string;
6
+ answer: string;
7
+ }
8
+
9
+ interface AccordionProps {
10
+ items: AccordionItemData[];
11
+ title?: string;
12
+ }
13
+
14
+ export const Accordion: React.FC<AccordionProps> = ({
15
+ items,
16
+ title = "Can fruit be cute?",
17
+ }) => {
18
+ const [openAccordionIndex, setOpenAccordionIndex] = useState<number | null>(
19
+ null
20
+ );
21
+
22
+ return (
23
+ <div className="p-8 pt-4 border-t border-gray-200 dark:border-gray-800 mt-4">
24
+ <h3 className="heading-lg mb-4">{title}</h3>
25
+ <div className="rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden">
26
+ {items.map((item, index) => (
27
+ <AccordionItem
28
+ key={index}
29
+ question={item.question}
30
+ answer={item.answer}
31
+ isOpen={openAccordionIndex === index}
32
+ onToggle={() =>
33
+ setOpenAccordionIndex(openAccordionIndex === index ? null : index)
34
+ }
35
+ />
36
+ ))}
37
+ </div>
38
+ </div>
39
+ );
40
+ };
@@ -0,0 +1,34 @@
1
+ import { Animate } from "@openai/apps-sdk-ui/components/Transition";
2
+ import React from "react";
3
+ import type { AccordionItemProps } from "../types";
4
+
5
+ export const AccordionItem: React.FC<AccordionItemProps> = ({
6
+ question,
7
+ answer,
8
+ isOpen,
9
+ onToggle,
10
+ }) => {
11
+ return (
12
+ <div className="border-b border-gray-200 dark:border-gray-800 last:border-b-0">
13
+ <button
14
+ type="button"
15
+ onClick={onToggle}
16
+ className="w-full flex items-center justify-between p-4 text-left hover:bg-gray-50 dark:hover:bg-gray-900/50 transition-colors"
17
+ >
18
+ <span className="font-medium text-gray-900 dark:text-gray-100">
19
+ {question}
20
+ </span>
21
+ <span className="text-xl text-gray-500 dark:text-gray-400 transition-transform duration-200">
22
+ {isOpen ? "−" : "+"}
23
+ </span>
24
+ </button>
25
+ <Animate enter={{ y: 0, delay: 150, duration: 450 }} exit={{ y: -8 }}>
26
+ {isOpen && (
27
+ <div key="content" className="pb-4 text-secondary px-4">
28
+ {answer}
29
+ </div>
30
+ )}
31
+ </Animate>
32
+ </div>
33
+ );
34
+ };
@@ -0,0 +1,65 @@
1
+ import { Animate } from "@openai/apps-sdk-ui/components/Transition";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import React, { useRef } from "react";
4
+ import { CarouselItem } from "./CarouselItem";
5
+ import { useCarouselAnimation } from "../hooks/useCarouselAnimation";
6
+
7
+ interface CarouselProps {
8
+ mcpUrl: string | undefined;
9
+ }
10
+
11
+ export const Carousel: React.FC<CarouselProps> = ({ mcpUrl }) => {
12
+ const carouselContainerRef = useRef<HTMLDivElement>(null);
13
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
14
+
15
+ // Fetch fruits from the API using React Query
16
+ const {
17
+ data: items,
18
+ isLoading,
19
+ error,
20
+ } = useQuery({
21
+ queryKey: ["fruits"],
22
+ queryFn: async () => {
23
+ const response = await fetch(`${mcpUrl}/api/fruits`);
24
+ if (!response.ok) {
25
+ throw new Error("Failed to fetch fruits");
26
+ }
27
+ return response.json() as Promise<
28
+ Array<{ fruit: string; color: string }>
29
+ >;
30
+ },
31
+ enabled: !!mcpUrl, // Only run query if mcpUrl is available
32
+ });
33
+
34
+ // Carousel animation with pointer tracking
35
+ useCarouselAnimation(carouselContainerRef, scrollContainerRef);
36
+
37
+ return (
38
+ <div
39
+ ref={scrollContainerRef}
40
+ className="carousel-scroll-container w-full overflow-x-auto overflow-y-visible pl-8"
41
+ >
42
+ <div ref={carouselContainerRef} className="overflow-visible">
43
+ {isLoading ? (
44
+ <div className="flex items-center justify-center p-8">
45
+ <p className="text-secondary">Loading fruits...</p>
46
+ </div>
47
+ ) : error ? (
48
+ <div className="flex items-center justify-center p-8">
49
+ <p className="text-red-500">Failed to load fruits</p>
50
+ </div>
51
+ ) : (
52
+ <Animate className="flex gap-4">
53
+ {items?.map((item: { fruit: string; color: string }) => (
54
+ <CarouselItem
55
+ key={item.fruit}
56
+ fruit={item.fruit}
57
+ color={item.color}
58
+ />
59
+ ))}
60
+ </Animate>
61
+ )}
62
+ </div>
63
+ </div>
64
+ );
65
+ };
@@ -0,0 +1,26 @@
1
+ import { Image } from "mcp-use/react";
2
+ import React from "react";
3
+
4
+ export interface CarouselItemProps {
5
+ fruit: string;
6
+ color: string;
7
+ }
8
+
9
+ export const CarouselItem: React.FC<CarouselItemProps> = ({ fruit, color }) => {
10
+ return (
11
+ <div
12
+ className={`carousel-item size-52 rounded-xl border border-gray-200 dark:border-gray-800 ${color}`}
13
+ >
14
+ <div className="carousel-item-bg">
15
+ <Image src={"/fruits/" + fruit + ".png"} alt={fruit} />
16
+ </div>
17
+ <div className="carousel-item-content">
18
+ <Image
19
+ src={"/fruits/" + fruit + ".png"}
20
+ alt={fruit}
21
+ className="w-24 h-24 object-contain"
22
+ />
23
+ </div>
24
+ </div>
25
+ );
26
+ };
@@ -0,0 +1,4 @@
1
+ import { QueryClient } from "@tanstack/react-query";
2
+
3
+ // Create a client
4
+ export const queryClient = new QueryClient();
@@ -0,0 +1,78 @@
1
+ import { useEffect, type RefObject } from "react";
2
+
3
+ export function useCarouselAnimation(
4
+ carouselContainerRef: RefObject<HTMLDivElement | null>,
5
+ scrollContainerRef: RefObject<HTMLDivElement | null>
6
+ ): void {
7
+ useEffect(() => {
8
+ let lastPointerX = 0;
9
+ let lastPointerY = 0;
10
+
11
+ const updateItems = () => {
12
+ const container = carouselContainerRef.current;
13
+ if (!container) return;
14
+
15
+ const articles =
16
+ container.querySelectorAll<HTMLElement>(".carousel-item");
17
+
18
+ articles.forEach((article) => {
19
+ const rect = article.getBoundingClientRect();
20
+ const centerX = rect.left + rect.width / 2;
21
+ const centerY = rect.top + rect.height / 2;
22
+ const relativeX = lastPointerX - centerX;
23
+ const relativeY = lastPointerY - centerY;
24
+ const x = relativeX / (rect.width / 2);
25
+ const y = relativeY / (rect.height / 2);
26
+
27
+ // Calculate distance from cursor to center of item
28
+ const distance = Math.sqrt(
29
+ relativeX * relativeX + relativeY * relativeY
30
+ );
31
+ // Use a larger max distance to make the effect work across gaps
32
+ const maxDistance = Math.max(rect.width, rect.height) * 2;
33
+ const normalizedDistance = Math.min(distance / maxDistance, 1);
34
+
35
+ // Closer items get higher opacity and scale
36
+ // Use exponential falloff for smoother transition
37
+ const proximity = Math.pow(1 - normalizedDistance, 2);
38
+ const opacity = 0.1 + proximity * 0.3; // Range from 0.1 to 0.4
39
+ const scale = 2.0 + proximity * 2.0; // Range from 2.0 to 4.0
40
+
41
+ article.style.setProperty("--pointer-x", x.toFixed(3));
42
+ article.style.setProperty("--pointer-y", y.toFixed(3));
43
+ article.style.setProperty("--icon-opacity", opacity.toFixed(3));
44
+ article.style.setProperty("--icon-scale", scale.toFixed(2));
45
+ });
46
+ };
47
+
48
+ const handlePointerMove = (event: { clientX: number; clientY: number }) => {
49
+ lastPointerX = event.clientX;
50
+ lastPointerY = event.clientY;
51
+ updateItems();
52
+ };
53
+
54
+ const handleScroll = () => {
55
+ updateItems();
56
+ };
57
+
58
+ document.addEventListener("pointermove", handlePointerMove);
59
+ window.addEventListener("scroll", handleScroll, true);
60
+
61
+ const scrollContainer = scrollContainerRef.current;
62
+ if (scrollContainer) {
63
+ scrollContainer.addEventListener("scroll", handleScroll);
64
+ }
65
+
66
+ // Initial update
67
+ updateItems();
68
+
69
+ return () => {
70
+ document.removeEventListener("pointermove", handlePointerMove);
71
+ window.removeEventListener("scroll", handleScroll, true);
72
+ const container = scrollContainerRef.current;
73
+ if (container) {
74
+ container.removeEventListener("scroll", handleScroll);
75
+ }
76
+ };
77
+ }, [carouselContainerRef, scrollContainerRef]);
78
+ }
@@ -0,0 +1,14 @@
1
+ import { z } from "zod";
2
+
3
+ export const propSchema = z.object({
4
+ query: z.string().describe("The search query"),
5
+ });
6
+
7
+ export type ProductSearchResultProps = z.infer<typeof propSchema>;
8
+
9
+ export type AccordionItemProps = {
10
+ question: string;
11
+ answer: string;
12
+ isOpen: boolean;
13
+ onToggle: () => void;
14
+ };
@@ -0,0 +1,61 @@
1
+ import { AppsSDKUIProvider } from "@openai/apps-sdk-ui/components/AppsSDKUIProvider";
2
+ import { QueryClientProvider } from "@tanstack/react-query";
3
+ import { McpUseProvider, useWidget } from "mcp-use/react";
4
+ import React from "react";
5
+ import { Link } from "react-router";
6
+ import { Accordion } from "./components/Accordion";
7
+ import { Carousel } from "./components/Carousel";
8
+ import { queryClient } from "./constants";
9
+ import type { ProductSearchResultProps } from "./types";
10
+ import { propSchema } from "./types";
11
+ import "../../styles.css";
12
+
13
+ export const widgetMetadata = {
14
+ description:
15
+ "Display product search results with filtering, state management, and tool interactions",
16
+ inputs: propSchema,
17
+ };
18
+
19
+ const ProductSearchResult: React.FC = () => {
20
+ const { props, mcp_url } = useWidget<ProductSearchResultProps>();
21
+
22
+ console.log(props);
23
+
24
+ const accordionItems = [
25
+ {
26
+ question: "Demo of the autosize feature",
27
+ answer:
28
+ "This is a demo of the autosize feature. The widget will automatically resize to fit the content, as supported by the OpenAI apps sdk https://developers.openai.com/apps-sdk/build/mcp-server/",
29
+ },
30
+ ];
31
+
32
+ return (
33
+ <McpUseProvider debugger viewControls autoSize>
34
+ <AppsSDKUIProvider linkComponent={Link}>
35
+ <div className="relative bg-white dark:bg-black border border-gray-200 dark:border-gray-800 rounded-3xl">
36
+ <div className="p-8">
37
+ <h5 className="text-secondary mb-1">Apps SDK Template</h5>
38
+ <h2 className="heading-xl mb-3">Lovely Little Fruit Shop</h2>
39
+ <p className="text-md">
40
+ Start building your ChatGPT widget this this mcp-use template. It
41
+ features the openai apps sdk ui components, dark/light theme
42
+ support, actions like callTool and sendFollowUpMessage, and more.
43
+ </p>
44
+ </div>
45
+ <Carousel mcpUrl={mcp_url} />
46
+ <Accordion items={accordionItems} />
47
+ </div>
48
+ </AppsSDKUIProvider>
49
+ </McpUseProvider>
50
+ );
51
+ };
52
+
53
+ const ProductSearchResultWithProvider: React.FC = () => {
54
+ return (
55
+ <QueryClientProvider client={queryClient}>
56
+ <ProductSearchResult />
57
+ </QueryClientProvider>
58
+ );
59
+ };
60
+
61
+ export default ProductSearchResultWithProvider;
@@ -1,4 +1,16 @@
1
1
  @import "tailwindcss";
2
+ @import "@openai/apps-sdk-ui/css";
3
+
4
+ /* Configure Tailwind v4 to scan for classes */
5
+ @source "../node_modules/@openai/apps-sdk-ui";
6
+ @source "./**/*.{ts,tsx,js,jsx,html}";
7
+
8
+ @theme {
9
+ /* Enable class-based dark mode */
10
+ --color-scheme: light dark;
11
+ }
12
+
13
+ @variant dark (&:where(.dark, .dark *));
2
14
 
3
15
  /* Custom styles */
4
16
  body {
@@ -9,3 +21,95 @@ body {
9
21
  -webkit-font-smoothing: antialiased;
10
22
  -moz-osx-font-smoothing: grayscale;
11
23
  }
24
+
25
+ h1,
26
+ h2,
27
+ h3,
28
+ h4,
29
+ h5,
30
+ h6 {
31
+ @apply text-gray-900 dark:text-white;
32
+ }
33
+
34
+ p {
35
+ @apply text-gray-600 dark:text-gray-400;
36
+ }
37
+
38
+ a {
39
+ @apply text-blue-600 dark:text-blue-400;
40
+ }
41
+
42
+ /* Carousel scroll container with padding for blur effect */
43
+ .carousel-scroll-container {
44
+ padding-bottom: 2rem;
45
+ }
46
+
47
+ /* Hover effect styles for carousel items */
48
+ .carousel-item {
49
+ position: relative;
50
+ container-type: size;
51
+ cursor: pointer;
52
+ transition-property: translate, scale;
53
+ transition-duration: 0.12s;
54
+ transition-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1);
55
+ -webkit-tap-highlight-color: transparent;
56
+ overflow: hidden;
57
+ border-radius: 12px;
58
+ }
59
+
60
+ .carousel-item:active {
61
+ translate: 0 1px;
62
+ scale: 0.99;
63
+ }
64
+
65
+ .carousel-item-content {
66
+ position: relative;
67
+ z-index: 2;
68
+ width: 100%;
69
+ height: 100%;
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ }
74
+
75
+ .carousel-item-bg {
76
+ position: absolute;
77
+ inset: 0;
78
+ display: grid;
79
+ place-items: center;
80
+ transform: translateZ(0);
81
+ filter: blur(28px) saturate(5) brightness(1.3);
82
+ translate: calc(var(--pointer-x, -10) * 50cqi)
83
+ calc(var(--pointer-y, -10) * 50cqh);
84
+ scale: var(--icon-scale, 3.4);
85
+ opacity: var(--icon-opacity, 0.25);
86
+ will-change: transform, filter, opacity;
87
+ pointer-events: none;
88
+ z-index: 1;
89
+ transition:
90
+ opacity 0.26s ease-out,
91
+ scale 0.26s ease-out;
92
+ border-radius: inherit;
93
+ }
94
+
95
+ .carousel-item-bg img {
96
+ width: 100px;
97
+ user-select: none;
98
+ -webkit-user-drag: none;
99
+ }
100
+
101
+ .carousel-item::after {
102
+ content: "";
103
+ position: absolute;
104
+ pointer-events: none;
105
+ inset: 0px;
106
+ border-radius: inherit;
107
+ border: 3px solid transparent;
108
+ backdrop-filter: blur(0px) saturate(4.2) brightness(2.5) contrast(2.5);
109
+ mask:
110
+ linear-gradient(#fff 0 100%) border-box,
111
+ linear-gradient(#fff 0 100%) padding-box;
112
+ mask-composite: exclude;
113
+ z-index: 3;
114
+ transform: translateZ(0);
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mcp-use-app",
3
- "version": "0.5.2",
3
+ "version": "0.6.0-canary.0",
4
4
  "type": "module",
5
5
  "description": "Create MCP-Use apps with one command",
6
6
  "author": "mcp-use, Inc.",
@@ -1,103 +0,0 @@
1
- import React from "react";
2
- import { z } from "zod";
3
- import { useWidget } from "mcp-use/react";
4
- import "../styles.css";
5
-
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
- */
11
-
12
- const propSchema = z.object({
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"),
22
- });
23
-
24
- export const widgetMetadata = {
25
- description: "Display weather for a city",
26
- inputs: propSchema,
27
- };
28
-
29
- type WeatherProps = z.infer<typeof propSchema>;
30
-
31
- const WeatherWidget: React.FC = () => {
32
- // Use the useWidget hook to get props from OpenAI Apps SDK
33
- const { props, theme } = useWidget<WeatherProps>();
34
-
35
- console.log(props);
36
-
37
- const { city, weather, temperature } = props;
38
- const getWeatherIcon = (weatherType: string) => {
39
- switch (weatherType?.toLowerCase()) {
40
- case "sunny":
41
- return "☀️";
42
- case "rain":
43
- return "🌧️";
44
- case "snow":
45
- return "❄️";
46
- case "cloudy":
47
- return "☁️";
48
- default:
49
- return "🌤️";
50
- }
51
- };
52
-
53
- const getWeatherColor = (weatherType: string) => {
54
- switch (weatherType?.toLowerCase()) {
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";
63
- default:
64
- return "from-gray-300 to-gray-500";
65
- }
66
- };
67
-
68
- // Theme-aware styling
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";
72
-
73
- return (
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
- >
80
- <div className="text-6xl">{getWeatherIcon(weather)}</div>
81
- </div>
82
-
83
- <div className="p-6">
84
- <div className="text-center">
85
- <h2 className={`text-2xl font-bold ${textColor} mb-2`}>{city}</h2>
86
- <div className="flex items-center justify-center space-x-4">
87
- <span className={`text-4xl font-light ${textColor}`}>
88
- {temperature}°
89
- </span>
90
- <div className="text-right">
91
- <p className={`text-lg font-medium ${subtextColor} capitalize`}>
92
- {weather}
93
- </p>
94
- <p className={`text-sm ${subtextColor}`}>Current</p>
95
- </div>
96
- </div>
97
- </div>
98
- </div>
99
- </div>
100
- );
101
- };
102
-
103
- export default WeatherWidget;