create-mcp-use-app 0.14.17 → 2.0.0-beta.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 (27) hide show
  1. package/README.md +2 -8
  2. package/dist/ansi.d.ts +9 -0
  3. package/dist/ansi.d.ts.map +1 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1768 -629
  6. package/dist/prompts.d.ts +16 -0
  7. package/dist/prompts.d.ts.map +1 -0
  8. package/dist/templates/blank/index.ts +4 -92
  9. package/dist/templates/blank/package.json +3 -12
  10. package/dist/templates/mcp-apps/index.ts +28 -122
  11. package/dist/templates/mcp-apps/package.json +7 -14
  12. package/dist/templates/mcp-apps/resources/product-search-result/view.tsx +26 -0
  13. package/dist/templates/mcp-apps/src/register.d.ts +7 -0
  14. package/dist/templates/mcp-apps/tsconfig.json +8 -14
  15. package/dist/templates/starter/index.ts +43 -80
  16. package/dist/templates/starter/package.json +5 -18
  17. package/package.json +4 -12
  18. package/dist/templates/mcp-apps/pnpm-workspace.yaml +0 -2
  19. package/dist/templates/mcp-apps/resources/product-search-result/components/Accordion.tsx +0 -35
  20. package/dist/templates/mcp-apps/resources/product-search-result/components/AccordionItem.tsx +0 -32
  21. package/dist/templates/mcp-apps/resources/product-search-result/components/Carousel.tsx +0 -51
  22. package/dist/templates/mcp-apps/resources/product-search-result/components/CarouselItem.tsx +0 -54
  23. package/dist/templates/mcp-apps/resources/product-search-result/components/CarouselSkeleton.tsx +0 -20
  24. package/dist/templates/mcp-apps/resources/product-search-result/hooks/useCarouselAnimation.ts +0 -78
  25. package/dist/templates/mcp-apps/resources/product-search-result/types.ts +0 -20
  26. package/dist/templates/mcp-apps/resources/product-search-result/widget.tsx +0 -288
  27. package/dist/templates/mcp-apps/resources/styles.css +0 -117
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mcp-use-app",
3
- "version": "0.14.17",
3
+ "version": "2.0.0-beta.0",
4
4
  "type": "module",
5
5
  "description": "Create MCP-Use apps with one command",
6
6
  "author": "mcp-use, Inc.",
@@ -35,24 +35,16 @@
35
35
  "engines": {
36
36
  "node": "^20.19.0 || >=22.12.0"
37
37
  },
38
- "dependencies": {
39
- "chalk": "^5.6.2",
40
- "commander": "^14.0.2",
41
- "ink": "^6.6.0",
42
- "ink-select-input": "^6.2.0",
43
- "ink-text-input": "^6.0.0",
44
- "ora": "^9.0.0",
45
- "react": "^19.2.3",
46
- "tar": "^7.5.16"
47
- },
38
+ "dependencies": {},
48
39
  "devDependencies": {
40
+ "@clack/prompts": "^1.7.0",
49
41
  "vitest": "^4.1.0"
50
42
  },
51
43
  "publishConfig": {
52
44
  "access": "public"
53
45
  },
54
46
  "scripts": {
55
- "build": "npm run clean && tsup src/index.tsx --format esm && tsc --emitDeclarationOnly --declaration && npm run copy-templates",
47
+ "build": "npm run clean && tsup src/index.ts --format esm && tsc --emitDeclarationOnly --declaration && npm run copy-templates",
56
48
  "clean": "rimraf dist tsconfig.tsbuildinfo",
57
49
  "copy-templates": "node scripts/copy-templates.js",
58
50
  "dev": "tsc --build --watch",
@@ -1,2 +0,0 @@
1
- overrides:
2
- lodash: ">=4.18.0"
@@ -1,35 +0,0 @@
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
- }
12
-
13
- export const Accordion: React.FC<AccordionProps> = ({ items }) => {
14
- const [openAccordionIndex, setOpenAccordionIndex] = useState<number | null>(
15
- null
16
- );
17
-
18
- return (
19
- <div className="p-8 pt-4 border-t border-subtle mt-4">
20
- <div className="rounded-lg border border-default overflow-hidden">
21
- {items.map((item, index) => (
22
- <AccordionItem
23
- key={index}
24
- question={item.question}
25
- answer={item.answer}
26
- isOpen={openAccordionIndex === index}
27
- onToggle={() =>
28
- setOpenAccordionIndex(openAccordionIndex === index ? null : index)
29
- }
30
- />
31
- ))}
32
- </div>
33
- </div>
34
- );
35
- };
@@ -1,32 +0,0 @@
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-subtle 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-primary-soft-hover transition-colors"
17
- >
18
- <span className="font-medium text-default">{question}</span>
19
- <span className="text-xl text-tertiary transition-transform duration-200">
20
- {isOpen ? "−" : "+"}
21
- </span>
22
- </button>
23
- <Animate enter={{ y: 0, delay: 150, duration: 450 }} exit={{ y: -8 }}>
24
- {isOpen && (
25
- <div key="content" className="pb-4 text-secondary px-4">
26
- {answer}
27
- </div>
28
- )}
29
- </Animate>
30
- </div>
31
- );
32
- };
@@ -1,51 +0,0 @@
1
- import { Animate } from "@openai/apps-sdk-ui/components/Transition";
2
- import React, { useRef } from "react";
3
- import { CarouselItem } from "./CarouselItem";
4
- import { useCarouselAnimation } from "../hooks/useCarouselAnimation";
5
-
6
- interface CarouselProps {
7
- results: Array<{ fruit: string; color: string }>;
8
- favorites?: string[];
9
- onSelectFruit: (fruit: string) => void;
10
- onToggleFavorite?: (fruit: string) => void;
11
- }
12
-
13
- export const Carousel: React.FC<CarouselProps> = ({
14
- results,
15
- favorites = [],
16
- onSelectFruit,
17
- onToggleFavorite,
18
- }) => {
19
- const carouselContainerRef = useRef<HTMLDivElement>(null);
20
- const scrollContainerRef = useRef<HTMLDivElement>(null);
21
-
22
- useCarouselAnimation(carouselContainerRef, scrollContainerRef);
23
-
24
- return (
25
- <div
26
- ref={scrollContainerRef}
27
- className="carousel-scroll-container w-full overflow-x-auto overflow-y-visible pl-8"
28
- >
29
- <div ref={carouselContainerRef} className="overflow-visible">
30
- <Animate className="flex gap-4">
31
- {results.map((item, index) => {
32
- const fruit =
33
- item.fruit ?? (item as { name?: string }).name ?? "Item";
34
- return (
35
- <CarouselItem
36
- key={`${fruit}-${index}`}
37
- fruit={fruit}
38
- color={item.color ?? "bg-default/10"}
39
- isFavorite={favorites.includes(fruit)}
40
- onClick={() => onSelectFruit(fruit)}
41
- onToggleFavorite={
42
- onToggleFavorite ? () => onToggleFavorite(fruit) : undefined
43
- }
44
- />
45
- );
46
- })}
47
- </Animate>
48
- </div>
49
- </div>
50
- );
51
- };
@@ -1,54 +0,0 @@
1
- import { Button } from "@openai/apps-sdk-ui/components/Button";
2
- import { HeartFilled, HeartXs } from "@openai/apps-sdk-ui/components/Icon";
3
- import { Image } from "mcp-use/react";
4
- import React from "react";
5
-
6
- export interface CarouselItemProps {
7
- fruit: string;
8
- color: string;
9
- isFavorite?: boolean;
10
- onClick: () => void;
11
- onToggleFavorite?: () => void;
12
- }
13
-
14
- export const CarouselItem: React.FC<CarouselItemProps> = ({
15
- fruit,
16
- color,
17
- isFavorite,
18
- onClick,
19
- onToggleFavorite,
20
- }) => {
21
- return (
22
- <div
23
- className={`carousel-item size-52 rounded-xl border border-subtle ${color} cursor-pointer`}
24
- onClick={onClick}
25
- >
26
- {onToggleFavorite && (
27
- <Button
28
- color="secondary"
29
- pill
30
- size="md"
31
- uniform
32
- variant="ghost"
33
- onClick={(e) => {
34
- e.stopPropagation();
35
- onToggleFavorite();
36
- }}
37
- className={`absolute top-2 right-2 z-10 ${isFavorite ? "text-danger/80" : "text-secondary"}`}
38
- >
39
- {isFavorite ? <HeartFilled /> : <HeartXs />}
40
- </Button>
41
- )}
42
- <div className="carousel-item-bg">
43
- <Image src={"/fruits/" + fruit + ".png"} alt={fruit} />
44
- </div>
45
- <div className="carousel-item-content">
46
- <Image
47
- src={"/fruits/" + fruit + ".png"}
48
- alt={fruit}
49
- className="w-24 h-24 object-contain"
50
- />
51
- </div>
52
- </div>
53
- );
54
- };
@@ -1,20 +0,0 @@
1
- import React from "react";
2
-
3
- const SKELETON_COUNT = 6;
4
-
5
- export const CarouselSkeleton: React.FC = () => {
6
- return (
7
- <div className="carousel-scroll-container w-full overflow-x-auto overflow-y-visible pl-8">
8
- <div className="overflow-hidden">
9
- <div className="flex gap-4">
10
- {Array.from({ length: SKELETON_COUNT }).map((_, i) => (
11
- <div
12
- key={i}
13
- className="carousel-item shrink-0 size-52 rounded-xl border border-subtle animate-pulse bg-gray-100"
14
- ></div>
15
- ))}
16
- </div>
17
- </div>
18
- </div>
19
- );
20
- };
@@ -1,78 +0,0 @@
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
- }
@@ -1,20 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const propSchema = z.object({
4
- query: z.string().describe("The search query"),
5
- results: z.array(
6
- z.object({
7
- fruit: z.string().describe("Fruit name"),
8
- color: z.string().describe("Tailwind background color class"),
9
- })
10
- ),
11
- });
12
-
13
- export type ProductSearchResultProps = z.infer<typeof propSchema>;
14
-
15
- export type AccordionItemProps = {
16
- question: string;
17
- answer: string;
18
- isOpen: boolean;
19
- onToggle: () => void;
20
- };
@@ -1,288 +0,0 @@
1
- import { AppsSDKUIProvider } from "@openai/apps-sdk-ui/components/AppsSDKUIProvider";
2
- import {
3
- Image,
4
- McpUseProvider,
5
- useCallTool,
6
- useWidget,
7
- type WidgetMetadata,
8
- } from "mcp-use/react";
9
- import React, { useCallback } from "react";
10
- import { Link } from "react-router";
11
- import "../styles.css";
12
- import { Carousel } from "./components/Carousel";
13
- import { CarouselSkeleton } from "./components/CarouselSkeleton";
14
- import { Accordion } from "./components/Accordion";
15
- import type { ProductSearchResultProps } from "./types";
16
- import { propSchema } from "./types";
17
- import { Button } from "@openai/apps-sdk-ui/components/Button";
18
- import {
19
- Expand,
20
- HeartFilled,
21
- HeartXs,
22
- PictureInPicture,
23
- } from "@openai/apps-sdk-ui/components/Icon";
24
- export const widgetMetadata: WidgetMetadata = {
25
- description:
26
- "Display product search results with filtering, state management, and tool interactions",
27
- props: propSchema,
28
- exposeAsTool: false,
29
- metadata: {
30
- prefersBorder: false,
31
- invoking: "Loading product search results...",
32
- invoked: "Product search results loaded",
33
- csp: {
34
- // Widget-specific
35
- resourceDomains: ["https://cdn.openai.com"],
36
- },
37
- },
38
- };
39
- type FavoritesState = { favorites: string[] };
40
-
41
- const ProductSearchResult: React.FC = () => {
42
- const {
43
- props,
44
- isPending,
45
- displayMode,
46
- requestDisplayMode,
47
- sendFollowUpMessage,
48
- locale,
49
- state,
50
- setState,
51
- } = useWidget<ProductSearchResultProps, FavoritesState>();
52
-
53
- const {
54
- callTool: getFruitDetails,
55
- data: fruitDetails,
56
- isPending: isLoadingDetails,
57
- } = useCallTool("get-fruit-details");
58
-
59
- const selectedFruit = fruitDetails?.structuredContent as
60
- | { fruit: string; facts?: string[] }
61
- | undefined;
62
- const favorites = state?.favorites ?? [];
63
-
64
- const toggleFavorite = useCallback(
65
- (fruit: string) => {
66
- const current = state?.favorites ?? [];
67
- const next = current.includes(fruit)
68
- ? current.filter((f: string) => f !== fruit)
69
- : [...current, fruit];
70
- setState({ favorites: next });
71
- },
72
- [state, setState]
73
- );
74
-
75
- const accordionItems = [
76
- {
77
- question: "Demo of the autosize feature",
78
- answer:
79
- "This is a demo of the autosize feature. The widget will automatically resize to fit the content, as supported by the mcp-apps specification",
80
- },
81
- ];
82
-
83
- if (isPending) {
84
- return (
85
- <McpUseProvider>
86
- <div className="relative bg-surface-elevated border border-default rounded-3xl">
87
- <div className="p-8 pb-4">
88
- <h5 className="text-secondary mb-1">MCP-Apps Template</h5>
89
- <h2 className="heading-xl mb-3">Lovely Little Fruit Shop</h2>
90
- <div className="h-5 w-48 rounded-md bg-default/10 animate-pulse" />
91
- </div>
92
- <CarouselSkeleton />
93
- </div>
94
- </McpUseProvider>
95
- );
96
- }
97
-
98
- const { query, results } = props;
99
- const isFullscreen = displayMode === "fullscreen";
100
- const isPip = displayMode === "pip";
101
- const lang = locale?.split("-")[0] ?? "en";
102
-
103
- return (
104
- <McpUseProvider>
105
- <AppsSDKUIProvider linkComponent={Link}>
106
- <div className="relative bg-surface-elevated border border-default rounded-3xl">
107
- {/* Toolbar — top-right badges and controls */}
108
- <div className="absolute top-4 right-4 z-10 flex items-center gap-2">
109
- {/* Locale badge */}
110
- <span className="px-2.5 py-1 text-xs font-medium rounded-full text-secondary uppercase tracking-wide">
111
- {lang}
112
- </span>
113
-
114
- {/* Favorites count */}
115
- {favorites.length > 0 && (
116
- <Button
117
- color="secondary"
118
- pill
119
- size="lg"
120
- uniform
121
- variant="ghost"
122
- className="text-danger/80"
123
- >
124
- <HeartFilled />
125
- {favorites.length}
126
- </Button>
127
- )}
128
-
129
- {/* Display mode buttons */}
130
- {!isFullscreen && !isPip && (
131
- <>
132
- <Button
133
- color="secondary"
134
- pill
135
- size="lg"
136
- uniform
137
- variant="outline"
138
- onClick={() => requestDisplayMode("pip")}
139
- title="Picture-in-picture"
140
- >
141
- <PictureInPicture />
142
- </Button>
143
- <Button
144
- color="secondary"
145
- pill
146
- size="lg"
147
- uniform
148
- variant="outline"
149
- onClick={() => requestDisplayMode("fullscreen")}
150
- title="Fullscreen"
151
- >
152
- <Expand />
153
- </Button>
154
- </>
155
- )}
156
-
157
- {(isFullscreen || isPip) && (
158
- <Button
159
- color="secondary"
160
- pill
161
- size="lg"
162
- uniform
163
- variant="outline"
164
- onClick={() => requestDisplayMode("inline")}
165
- title="Exit"
166
- >
167
- <svg
168
- width="14"
169
- height="14"
170
- viewBox="0 0 24 24"
171
- fill="none"
172
- stroke="currentColor"
173
- strokeWidth="2"
174
- strokeLinecap="round"
175
- strokeLinejoin="round"
176
- >
177
- <line x1="18" y1="6" x2="6" y2="18" />
178
- <line x1="6" y1="6" x2="18" y2="18" />
179
- </svg>
180
- </Button>
181
- )}
182
- </div>
183
-
184
- {/* Header */}
185
- <div className="p-8 pb-4">
186
- <h5 className="text-secondary mb-1">MCP-Apps Template</h5>
187
- <h2 className="heading-xl mb-1">Lovely Little Fruit Shop</h2>
188
- <p className="text-md text-secondary">
189
- {query
190
- ? `Showing results for "${query}"`
191
- : "Tap a fruit to see details"}
192
- </p>
193
- </div>
194
-
195
- {/* Carousel */}
196
- <Carousel
197
- results={results}
198
- favorites={favorites}
199
- onSelectFruit={(fruit: string) => getFruitDetails({ fruit })}
200
- onToggleFavorite={toggleFavorite}
201
- />
202
-
203
- {/* Detail view */}
204
- {selectedFruit && (
205
- <div className="mx-8 my-6 rounded-2xl border border-default bg-surface p-5 flex items-center gap-6">
206
- <div
207
- className={`rounded-xl p-4 shrink-0 ${
208
- results.find(
209
- (r: { fruit: string }) => r.fruit === selectedFruit.fruit
210
- )?.color ?? ""
211
- }`}
212
- >
213
- <Image
214
- src={`/fruits/${selectedFruit.fruit}.png`}
215
- alt={selectedFruit.fruit}
216
- className="w-24 h-24 object-contain"
217
- />
218
- </div>
219
- <div className="flex-1">
220
- {isLoadingDetails ? (
221
- <div className="animate-pulse h-4 w-32 bg-surface-elevated rounded" />
222
- ) : (
223
- <>
224
- <div className="flex items-center gap-2 mb-2">
225
- <h3 className="font-semibold text-lg capitalize">
226
- {selectedFruit.fruit}
227
- </h3>
228
- <Button
229
- color="secondary"
230
- pill
231
- size="md"
232
- uniform
233
- variant="ghost"
234
- onClick={() => toggleFavorite(selectedFruit.fruit)}
235
- title={
236
- favorites.includes(selectedFruit.fruit)
237
- ? "Remove from favorites"
238
- : "Add to favorites"
239
- }
240
- className={
241
- favorites.includes(selectedFruit.fruit)
242
- ? "text-danger/80"
243
- : "text-secondary"
244
- }
245
- >
246
- {favorites.includes(selectedFruit.fruit) ? (
247
- <HeartFilled />
248
- ) : (
249
- <HeartXs />
250
- )}
251
- </Button>
252
- </div>
253
- <ul className="space-y-1">
254
- {(selectedFruit.facts ?? []).map((fact: string) => (
255
- <li
256
- key={fact}
257
- className="text-sm text-secondary flex items-start gap-2"
258
- >
259
- <span className="text-info mt-0.5">•</span>
260
- {fact}
261
- </li>
262
- ))}
263
- </ul>
264
- {/* Follow-up message demo — sends a message to the LLM from the widget */}
265
- <button
266
- onClick={() =>
267
- sendFollowUpMessage(
268
- `Tell me more interesting facts about ${selectedFruit.fruit}`
269
- )
270
- }
271
- className="mt-3 px-3 py-1.5 text-xs font-medium rounded-lg bg-info/10 text-info hover:bg-info/20 transition-colors cursor-pointer"
272
- >
273
- Ask the AI for more about {selectedFruit.fruit}
274
- </button>
275
- </>
276
- )}
277
- </div>
278
- </div>
279
- )}
280
-
281
- <Accordion items={accordionItems} />
282
- </div>
283
- </AppsSDKUIProvider>
284
- </McpUseProvider>
285
- );
286
- };
287
-
288
- export default ProductSearchResult;