create-mcp-use-app 2.0.10 → 2.1.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 CHANGED
@@ -2198,6 +2198,7 @@ async function main() {
2198
2198
  console.log(" \u251C\u2500\u2500 public/");
2199
2199
  console.log(" \u251C\u2500\u2500 mcp-env.d.ts");
2200
2200
  console.log(" \u251C\u2500\u2500 index.ts (server entry point)");
2201
+ console.log(" \u251C\u2500\u2500 landing.tsx (page shown at the MCP URL)");
2201
2202
  console.log(" \u251C\u2500\u2500 package.json");
2202
2203
  console.log(" \u251C\u2500\u2500 tsconfig.json");
2203
2204
  console.log(" \u2514\u2500\u2500 README.md");
@@ -2214,12 +2215,14 @@ async function main() {
2214
2215
  console.log(" \u2502 \u2514\u2500\u2500 MeshGradient.tsx");
2215
2216
  console.log(" \u251C\u2500\u2500 mcp-env.d.ts");
2216
2217
  console.log(" \u251C\u2500\u2500 index.ts (server entry point)");
2218
+ console.log(" \u251C\u2500\u2500 landing.tsx (page shown at the MCP URL)");
2217
2219
  console.log(" \u251C\u2500\u2500 package.json");
2218
2220
  console.log(" \u251C\u2500\u2500 tsconfig.json");
2219
2221
  console.log(" \u2514\u2500\u2500 README.md");
2220
2222
  } else if (validatedTemplate === "mcp-server") {
2221
2223
  console.log(" \u251C\u2500\u2500 mcp-env.d.ts");
2222
2224
  console.log(" \u251C\u2500\u2500 index.ts (server entry point)");
2225
+ console.log(" \u251C\u2500\u2500 landing.tsx (page shown at the MCP URL)");
2223
2226
  console.log(" \u251C\u2500\u2500 package.json");
2224
2227
  console.log(" \u251C\u2500\u2500 tsconfig.json");
2225
2228
  console.log(" \u2514\u2500\u2500 README.md");
@@ -15,6 +15,8 @@ Open [http://localhost:3000/mcp/inspector](http://localhost:3000/mcp/inspector)
15
15
 
16
16
  You can start building by editing the entry file. Add tools, resources, and prompts — the server auto-reloads as you edit.
17
17
 
18
+ Open [http://localhost:3000/mcp](http://localhost:3000/mcp) in a browser to see the landing page. Edit `landing.tsx` to change it (Tailwind classes work with no extra setup), or delete it to use the built-in page.
19
+
18
20
  Run `npm run typecheck` to refresh MCP view types and check the project with its local TypeScript compiler.
19
21
 
20
22
  ## Learn More
@@ -0,0 +1,253 @@
1
+ /**
2
+ * The page a browser sees at the MCP URL. Edit it freely, or delete it to
3
+ * fall back to the built-in page. Styled with Tailwind classes.
4
+ */
5
+ import { useState, type ReactNode } from "react";
6
+ import {
7
+ useInstallTargets,
8
+ useServerInfo,
9
+ useToolList,
10
+ type InstallTargets,
11
+ } from "mcp-use/landing";
12
+
13
+ // Optional: `export const metadata = { title: "...", description: "..." };`
14
+ // sets the page title and social preview tags.
15
+
16
+ type InstallTarget = InstallTargets[keyof InstallTargets];
17
+
18
+ // Layered radial gradients behind the header card.
19
+ const heroGradient = [
20
+ "radial-gradient(at 12% 18%, #c7d7fe 0, transparent 55%)",
21
+ "radial-gradient(at 88% 22%, #f4fca0 0, transparent 50%)",
22
+ "radial-gradient(at 70% 100%, #d4d4d8 0, transparent 55%)",
23
+ "radial-gradient(at 20% 95%, #e0eaff 0, transparent 50%)",
24
+ ].join(", ");
25
+
26
+ export default function Landing() {
27
+ const { name, title, version, description, url, iconUrl } = useServerInfo();
28
+ const install = useInstallTargets();
29
+
30
+ return (
31
+ <div className="min-h-screen bg-[#fdfcfc] text-slate-800">
32
+ <div className="mx-auto max-w-3xl border-x border-black/[0.08]">
33
+ <header className="p-6" style={{ backgroundImage: heroGradient }}>
34
+ <div className="border border-black/[0.06] bg-white/80 px-6 py-8 text-center backdrop-blur-md">
35
+ {iconUrl && (
36
+ <img
37
+ src={iconUrl}
38
+ alt=""
39
+ width={64}
40
+ height={64}
41
+ className="mx-auto mb-3 size-16 rounded-2xl object-contain"
42
+ />
43
+ )}
44
+ <h1 className="text-3xl font-bold text-[#172037]">
45
+ {title ?? name}{" "}
46
+ <span className="text-base font-medium text-slate-500">
47
+ (v{version})
48
+ </span>
49
+ </h1>
50
+ {description && (
51
+ <p className="mx-auto mt-2 max-w-xl text-[#172037]/80">
52
+ {description}
53
+ </p>
54
+ )}
55
+ <div className="mx-auto mt-6 flex w-fit max-w-full items-center gap-2 rounded-full border border-black/[0.12] bg-white py-2 pr-2 pl-4">
56
+ <code className="truncate font-mono text-sm text-slate-600">
57
+ {url}
58
+ </code>
59
+ <CopyButton text={url} label="Copy URL" />
60
+ </div>
61
+ <a
62
+ href={install.inspector.url}
63
+ target="_blank"
64
+ rel="noopener noreferrer"
65
+ className="mt-6 inline-block rounded-full bg-linear-to-b from-slate-800 to-slate-900 px-5 py-2.5 text-[15px] font-semibold text-white shadow-md hover:from-slate-700 hover:to-slate-800"
66
+ >
67
+ Open in Inspector
68
+ </a>
69
+ </div>
70
+ </header>
71
+
72
+ <Section
73
+ title="Installation Guide"
74
+ subtitle="Choose your preferred MCP client"
75
+ >
76
+ <InstallTabs targets={Object.values(install)} />
77
+ </Section>
78
+
79
+ <Tools />
80
+
81
+ <footer className="border-t border-black/[0.08] py-10 text-center text-sm text-slate-500">
82
+ Powered by{" "}
83
+ <a
84
+ href="https://github.com/mcp-use/mcp-use"
85
+ target="_blank"
86
+ rel="noopener noreferrer"
87
+ className="font-medium text-slate-900 hover:underline"
88
+ >
89
+ mcp-use
90
+ </a>
91
+ </footer>
92
+ </div>
93
+ </div>
94
+ );
95
+ }
96
+
97
+ function Section(props: {
98
+ title: string;
99
+ subtitle?: string;
100
+ children: ReactNode;
101
+ }) {
102
+ return (
103
+ <section className="border-t border-black/[0.08] bg-white px-6 py-8">
104
+ <h2 className="text-xl font-semibold text-[#172037]">{props.title}</h2>
105
+ {props.subtitle && (
106
+ <p className="mt-1 text-sm text-[#172037]/80">{props.subtitle}</p>
107
+ )}
108
+ <div className="mt-6">{props.children}</div>
109
+ </section>
110
+ );
111
+ }
112
+
113
+ /** One tab per client; the panel's shape depends on what the target holds. */
114
+ function InstallTabs({ targets }: { targets: InstallTarget[] }) {
115
+ const [active, setActive] = useState(targets[0].label);
116
+ const target = targets.find((t) => t.label === active) ?? targets[0];
117
+
118
+ return (
119
+ <div>
120
+ <div
121
+ role="tablist"
122
+ className="flex flex-wrap gap-0.5 rounded-lg bg-[#172037]/5 p-[3px]"
123
+ >
124
+ {targets.map(({ label }) => (
125
+ <button
126
+ key={label}
127
+ type="button"
128
+ role="tab"
129
+ aria-selected={label === active}
130
+ onClick={() => setActive(label)}
131
+ className={`min-w-20 flex-1 rounded-md border px-3 py-2 text-[13px] font-medium ${
132
+ label === active
133
+ ? "border-black/10 bg-white text-[#172037] shadow-sm"
134
+ : "border-transparent text-slate-500 hover:bg-white/60 hover:text-[#172037]"
135
+ }`}
136
+ >
137
+ {label}
138
+ </button>
139
+ ))}
140
+ </div>
141
+
142
+ <div role="tabpanel" className="mt-6 text-slate-600">
143
+ {"command" in target && (
144
+ <>
145
+ <p>Run this command in your terminal:</p>
146
+ <div className="mt-3 flex items-center gap-2 rounded-lg border border-black/[0.08] bg-slate-50 py-1.5 pr-1.5 pl-3">
147
+ <pre className="flex-1 overflow-x-auto font-mono text-sm">
148
+ {target.command}
149
+ </pre>
150
+ <CopyButton text={target.command} label="Copy command" />
151
+ </div>
152
+ </>
153
+ )}
154
+ {"deeplink" in target && (
155
+ <>
156
+ <p>Add this server to {target.label} in one click:</p>
157
+ <InstallLink href={target.deeplink}>
158
+ Open in {target.label}
159
+ </InstallLink>
160
+ <p className="mt-3 text-sm">
161
+ Or add it manually: Settings → MCP → Add server
162
+ </p>
163
+ </>
164
+ )}
165
+ {"steps" in target && (
166
+ <ol className="list-decimal space-y-2 pl-5">
167
+ {target.steps.map((step) => {
168
+ const [heading, ...rest] = step.split(": ");
169
+ return (
170
+ <li key={step}>
171
+ <strong className="text-[#172037]">{heading}:</strong>{" "}
172
+ {rest.join(": ")}
173
+ </li>
174
+ );
175
+ })}
176
+ </ol>
177
+ )}
178
+ {"url" in target && (
179
+ <>
180
+ <p>Chat with this server and try its tools in the browser:</p>
181
+ <InstallLink href={target.url} external>
182
+ Open {target.label}
183
+ </InstallLink>
184
+ </>
185
+ )}
186
+ </div>
187
+ </div>
188
+ );
189
+ }
190
+
191
+ function InstallLink(props: {
192
+ href: string;
193
+ external?: boolean;
194
+ children: ReactNode;
195
+ }) {
196
+ return (
197
+ <a
198
+ href={props.href}
199
+ {...(props.external && { target: "_blank", rel: "noopener noreferrer" })}
200
+ className="mt-3 inline-block rounded-full bg-slate-900 px-3.5 py-1.5 text-[13px] font-medium text-white hover:bg-slate-800"
201
+ >
202
+ {props.children}
203
+ </a>
204
+ );
205
+ }
206
+
207
+ /** Registered tools. Reading the list is what ships it to the page. */
208
+ function Tools() {
209
+ const tools = useToolList();
210
+ if (tools.length === 0) return null;
211
+
212
+ return (
213
+ <Section title={`Tools (${tools.length})`}>
214
+ <ul className="divide-y divide-slate-100">
215
+ {tools.map((tool) => (
216
+ <li key={tool.name} className="py-3 first:pt-0 last:pb-0">
217
+ <code className="rounded bg-slate-100 px-1.5 py-0.5 font-mono text-[13px]">
218
+ {tool.title ?? tool.name}
219
+ </code>
220
+ {tool.description && (
221
+ <p className="mt-2 text-sm text-slate-500">{tool.description}</p>
222
+ )}
223
+ </li>
224
+ ))}
225
+ </ul>
226
+ </Section>
227
+ );
228
+ }
229
+
230
+ function CopyButton({ text, label }: { text: string; label: string }) {
231
+ const [copied, setCopied] = useState(false);
232
+
233
+ async function copy() {
234
+ try {
235
+ await navigator.clipboard.writeText(text);
236
+ setCopied(true);
237
+ setTimeout(() => setCopied(false), 2000);
238
+ } catch {
239
+ // The clipboard needs a secure context (https or localhost).
240
+ }
241
+ }
242
+
243
+ return (
244
+ <button
245
+ type="button"
246
+ onClick={copy}
247
+ aria-label={label}
248
+ className="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-100"
249
+ >
250
+ {copied ? "Copied" : "Copy"}
251
+ </button>
252
+ );
253
+ }
@@ -26,10 +26,14 @@
26
26
  "typecheck": "mcp-use typecheck"
27
27
  },
28
28
  "dependencies": {
29
- "mcp-use": "workspace:*"
29
+ "mcp-use": "workspace:*",
30
+ "react": "^19.2.7",
31
+ "react-dom": "^19.2.7"
30
32
  },
31
33
  "devDependencies": {
32
34
  "@types/node": "^22.20.0",
35
+ "@types/react": "^19.2.17",
36
+ "@types/react-dom": "^19.2.3",
33
37
  "typescript": "^7.0.2"
34
38
  }
35
39
  }
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "include": [
17
17
  "index.ts",
18
+ "**/landing.tsx",
18
19
  "mcp-env.d.ts",
19
20
  "src/**/*",
20
21
  "views/**/*",
@@ -15,6 +15,8 @@ Open [http://localhost:3000/mcp/inspector](http://localhost:3000/mcp/inspector)
15
15
 
16
16
  You can start building by editing the entry file. Add tools, resources, and prompts — the server auto-reloads as you edit.
17
17
 
18
+ Open [http://localhost:3000/mcp](http://localhost:3000/mcp) in a browser to see the landing page. Edit `landing.tsx` to change it (Tailwind classes work with no extra setup), or delete it to use the built-in page.
19
+
18
20
  Run `npm run typecheck` to refresh MCP view types and check the project with its local TypeScript compiler. Statically declared tools used by views must be assigned to exported constants.
19
21
 
20
22
  ## Learn More
@@ -0,0 +1,253 @@
1
+ /**
2
+ * The page a browser sees at the MCP URL. Edit it freely, or delete it to
3
+ * fall back to the built-in page. Styled with Tailwind classes.
4
+ */
5
+ import { useState, type ReactNode } from "react";
6
+ import {
7
+ useInstallTargets,
8
+ useServerInfo,
9
+ useToolList,
10
+ type InstallTargets,
11
+ } from "mcp-use/landing";
12
+
13
+ // Optional: `export const metadata = { title: "...", description: "..." };`
14
+ // sets the page title and social preview tags.
15
+
16
+ type InstallTarget = InstallTargets[keyof InstallTargets];
17
+
18
+ // Layered radial gradients behind the header card.
19
+ const heroGradient = [
20
+ "radial-gradient(at 12% 18%, #c7d7fe 0, transparent 55%)",
21
+ "radial-gradient(at 88% 22%, #f4fca0 0, transparent 50%)",
22
+ "radial-gradient(at 70% 100%, #d4d4d8 0, transparent 55%)",
23
+ "radial-gradient(at 20% 95%, #e0eaff 0, transparent 50%)",
24
+ ].join(", ");
25
+
26
+ export default function Landing() {
27
+ const { name, title, version, description, url, iconUrl } = useServerInfo();
28
+ const install = useInstallTargets();
29
+
30
+ return (
31
+ <div className="min-h-screen bg-[#fdfcfc] text-slate-800">
32
+ <div className="mx-auto max-w-3xl border-x border-black/[0.08]">
33
+ <header className="p-6" style={{ backgroundImage: heroGradient }}>
34
+ <div className="border border-black/[0.06] bg-white/80 px-6 py-8 text-center backdrop-blur-md">
35
+ {iconUrl && (
36
+ <img
37
+ src={iconUrl}
38
+ alt=""
39
+ width={64}
40
+ height={64}
41
+ className="mx-auto mb-3 size-16 rounded-2xl object-contain"
42
+ />
43
+ )}
44
+ <h1 className="text-3xl font-bold text-[#172037]">
45
+ {title ?? name}{" "}
46
+ <span className="text-base font-medium text-slate-500">
47
+ (v{version})
48
+ </span>
49
+ </h1>
50
+ {description && (
51
+ <p className="mx-auto mt-2 max-w-xl text-[#172037]/80">
52
+ {description}
53
+ </p>
54
+ )}
55
+ <div className="mx-auto mt-6 flex w-fit max-w-full items-center gap-2 rounded-full border border-black/[0.12] bg-white py-2 pr-2 pl-4">
56
+ <code className="truncate font-mono text-sm text-slate-600">
57
+ {url}
58
+ </code>
59
+ <CopyButton text={url} label="Copy URL" />
60
+ </div>
61
+ <a
62
+ href={install.inspector.url}
63
+ target="_blank"
64
+ rel="noopener noreferrer"
65
+ className="mt-6 inline-block rounded-full bg-linear-to-b from-slate-800 to-slate-900 px-5 py-2.5 text-[15px] font-semibold text-white shadow-md hover:from-slate-700 hover:to-slate-800"
66
+ >
67
+ Open in Inspector
68
+ </a>
69
+ </div>
70
+ </header>
71
+
72
+ <Section
73
+ title="Installation Guide"
74
+ subtitle="Choose your preferred MCP client"
75
+ >
76
+ <InstallTabs targets={Object.values(install)} />
77
+ </Section>
78
+
79
+ <Tools />
80
+
81
+ <footer className="border-t border-black/[0.08] py-10 text-center text-sm text-slate-500">
82
+ Powered by{" "}
83
+ <a
84
+ href="https://github.com/mcp-use/mcp-use"
85
+ target="_blank"
86
+ rel="noopener noreferrer"
87
+ className="font-medium text-slate-900 hover:underline"
88
+ >
89
+ mcp-use
90
+ </a>
91
+ </footer>
92
+ </div>
93
+ </div>
94
+ );
95
+ }
96
+
97
+ function Section(props: {
98
+ title: string;
99
+ subtitle?: string;
100
+ children: ReactNode;
101
+ }) {
102
+ return (
103
+ <section className="border-t border-black/[0.08] bg-white px-6 py-8">
104
+ <h2 className="text-xl font-semibold text-[#172037]">{props.title}</h2>
105
+ {props.subtitle && (
106
+ <p className="mt-1 text-sm text-[#172037]/80">{props.subtitle}</p>
107
+ )}
108
+ <div className="mt-6">{props.children}</div>
109
+ </section>
110
+ );
111
+ }
112
+
113
+ /** One tab per client; the panel's shape depends on what the target holds. */
114
+ function InstallTabs({ targets }: { targets: InstallTarget[] }) {
115
+ const [active, setActive] = useState(targets[0].label);
116
+ const target = targets.find((t) => t.label === active) ?? targets[0];
117
+
118
+ return (
119
+ <div>
120
+ <div
121
+ role="tablist"
122
+ className="flex flex-wrap gap-0.5 rounded-lg bg-[#172037]/5 p-[3px]"
123
+ >
124
+ {targets.map(({ label }) => (
125
+ <button
126
+ key={label}
127
+ type="button"
128
+ role="tab"
129
+ aria-selected={label === active}
130
+ onClick={() => setActive(label)}
131
+ className={`min-w-20 flex-1 rounded-md border px-3 py-2 text-[13px] font-medium ${
132
+ label === active
133
+ ? "border-black/10 bg-white text-[#172037] shadow-sm"
134
+ : "border-transparent text-slate-500 hover:bg-white/60 hover:text-[#172037]"
135
+ }`}
136
+ >
137
+ {label}
138
+ </button>
139
+ ))}
140
+ </div>
141
+
142
+ <div role="tabpanel" className="mt-6 text-slate-600">
143
+ {"command" in target && (
144
+ <>
145
+ <p>Run this command in your terminal:</p>
146
+ <div className="mt-3 flex items-center gap-2 rounded-lg border border-black/[0.08] bg-slate-50 py-1.5 pr-1.5 pl-3">
147
+ <pre className="flex-1 overflow-x-auto font-mono text-sm">
148
+ {target.command}
149
+ </pre>
150
+ <CopyButton text={target.command} label="Copy command" />
151
+ </div>
152
+ </>
153
+ )}
154
+ {"deeplink" in target && (
155
+ <>
156
+ <p>Add this server to {target.label} in one click:</p>
157
+ <InstallLink href={target.deeplink}>
158
+ Open in {target.label}
159
+ </InstallLink>
160
+ <p className="mt-3 text-sm">
161
+ Or add it manually: Settings → MCP → Add server
162
+ </p>
163
+ </>
164
+ )}
165
+ {"steps" in target && (
166
+ <ol className="list-decimal space-y-2 pl-5">
167
+ {target.steps.map((step) => {
168
+ const [heading, ...rest] = step.split(": ");
169
+ return (
170
+ <li key={step}>
171
+ <strong className="text-[#172037]">{heading}:</strong>{" "}
172
+ {rest.join(": ")}
173
+ </li>
174
+ );
175
+ })}
176
+ </ol>
177
+ )}
178
+ {"url" in target && (
179
+ <>
180
+ <p>Chat with this server and try its tools in the browser:</p>
181
+ <InstallLink href={target.url} external>
182
+ Open {target.label}
183
+ </InstallLink>
184
+ </>
185
+ )}
186
+ </div>
187
+ </div>
188
+ );
189
+ }
190
+
191
+ function InstallLink(props: {
192
+ href: string;
193
+ external?: boolean;
194
+ children: ReactNode;
195
+ }) {
196
+ return (
197
+ <a
198
+ href={props.href}
199
+ {...(props.external && { target: "_blank", rel: "noopener noreferrer" })}
200
+ className="mt-3 inline-block rounded-full bg-slate-900 px-3.5 py-1.5 text-[13px] font-medium text-white hover:bg-slate-800"
201
+ >
202
+ {props.children}
203
+ </a>
204
+ );
205
+ }
206
+
207
+ /** Registered tools. Reading the list is what ships it to the page. */
208
+ function Tools() {
209
+ const tools = useToolList();
210
+ if (tools.length === 0) return null;
211
+
212
+ return (
213
+ <Section title={`Tools (${tools.length})`}>
214
+ <ul className="divide-y divide-slate-100">
215
+ {tools.map((tool) => (
216
+ <li key={tool.name} className="py-3 first:pt-0 last:pb-0">
217
+ <code className="rounded bg-slate-100 px-1.5 py-0.5 font-mono text-[13px]">
218
+ {tool.title ?? tool.name}
219
+ </code>
220
+ {tool.description && (
221
+ <p className="mt-2 text-sm text-slate-500">{tool.description}</p>
222
+ )}
223
+ </li>
224
+ ))}
225
+ </ul>
226
+ </Section>
227
+ );
228
+ }
229
+
230
+ function CopyButton({ text, label }: { text: string; label: string }) {
231
+ const [copied, setCopied] = useState(false);
232
+
233
+ async function copy() {
234
+ try {
235
+ await navigator.clipboard.writeText(text);
236
+ setCopied(true);
237
+ setTimeout(() => setCopied(false), 2000);
238
+ } catch {
239
+ // The clipboard needs a secure context (https or localhost).
240
+ }
241
+ }
242
+
243
+ return (
244
+ <button
245
+ type="button"
246
+ onClick={copy}
247
+ aria-label={label}
248
+ className="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-100"
249
+ >
250
+ {copied ? "Copied" : "Copy"}
251
+ </button>
252
+ );
253
+ }
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "include": [
17
17
  "index.ts",
18
+ "**/landing.tsx",
18
19
  "mcp-env.d.ts",
19
20
  "src/**/*",
20
21
  "views/**/*",
@@ -15,6 +15,8 @@ Open [http://localhost:3000/mcp/inspector](http://localhost:3000/mcp/inspector)
15
15
 
16
16
  You can start building by editing the entry file. Add tools and prompts — the server auto-reloads as you edit.
17
17
 
18
+ Open [http://localhost:3000/mcp](http://localhost:3000/mcp) in a browser to see the landing page. Edit `landing.tsx` to change it (Tailwind classes work with no extra setup), or delete it to use the built-in page.
19
+
18
20
  Run `npm run typecheck` to refresh MCP view types and check the project with its local TypeScript compiler.
19
21
 
20
22
  ## Learn More
@@ -0,0 +1,253 @@
1
+ /**
2
+ * The page a browser sees at the MCP URL. Edit it freely, or delete it to
3
+ * fall back to the built-in page. Styled with Tailwind classes.
4
+ */
5
+ import { useState, type ReactNode } from "react";
6
+ import {
7
+ useInstallTargets,
8
+ useServerInfo,
9
+ useToolList,
10
+ type InstallTargets,
11
+ } from "mcp-use/landing";
12
+
13
+ // Optional: `export const metadata = { title: "...", description: "..." };`
14
+ // sets the page title and social preview tags.
15
+
16
+ type InstallTarget = InstallTargets[keyof InstallTargets];
17
+
18
+ // Layered radial gradients behind the header card.
19
+ const heroGradient = [
20
+ "radial-gradient(at 12% 18%, #c7d7fe 0, transparent 55%)",
21
+ "radial-gradient(at 88% 22%, #f4fca0 0, transparent 50%)",
22
+ "radial-gradient(at 70% 100%, #d4d4d8 0, transparent 55%)",
23
+ "radial-gradient(at 20% 95%, #e0eaff 0, transparent 50%)",
24
+ ].join(", ");
25
+
26
+ export default function Landing() {
27
+ const { name, title, version, description, url, iconUrl } = useServerInfo();
28
+ const install = useInstallTargets();
29
+
30
+ return (
31
+ <div className="min-h-screen bg-[#fdfcfc] text-slate-800">
32
+ <div className="mx-auto max-w-3xl border-x border-black/[0.08]">
33
+ <header className="p-6" style={{ backgroundImage: heroGradient }}>
34
+ <div className="border border-black/[0.06] bg-white/80 px-6 py-8 text-center backdrop-blur-md">
35
+ {iconUrl && (
36
+ <img
37
+ src={iconUrl}
38
+ alt=""
39
+ width={64}
40
+ height={64}
41
+ className="mx-auto mb-3 size-16 rounded-2xl object-contain"
42
+ />
43
+ )}
44
+ <h1 className="text-3xl font-bold text-[#172037]">
45
+ {title ?? name}{" "}
46
+ <span className="text-base font-medium text-slate-500">
47
+ (v{version})
48
+ </span>
49
+ </h1>
50
+ {description && (
51
+ <p className="mx-auto mt-2 max-w-xl text-[#172037]/80">
52
+ {description}
53
+ </p>
54
+ )}
55
+ <div className="mx-auto mt-6 flex w-fit max-w-full items-center gap-2 rounded-full border border-black/[0.12] bg-white py-2 pr-2 pl-4">
56
+ <code className="truncate font-mono text-sm text-slate-600">
57
+ {url}
58
+ </code>
59
+ <CopyButton text={url} label="Copy URL" />
60
+ </div>
61
+ <a
62
+ href={install.inspector.url}
63
+ target="_blank"
64
+ rel="noopener noreferrer"
65
+ className="mt-6 inline-block rounded-full bg-linear-to-b from-slate-800 to-slate-900 px-5 py-2.5 text-[15px] font-semibold text-white shadow-md hover:from-slate-700 hover:to-slate-800"
66
+ >
67
+ Open in Inspector
68
+ </a>
69
+ </div>
70
+ </header>
71
+
72
+ <Section
73
+ title="Installation Guide"
74
+ subtitle="Choose your preferred MCP client"
75
+ >
76
+ <InstallTabs targets={Object.values(install)} />
77
+ </Section>
78
+
79
+ <Tools />
80
+
81
+ <footer className="border-t border-black/[0.08] py-10 text-center text-sm text-slate-500">
82
+ Powered by{" "}
83
+ <a
84
+ href="https://github.com/mcp-use/mcp-use"
85
+ target="_blank"
86
+ rel="noopener noreferrer"
87
+ className="font-medium text-slate-900 hover:underline"
88
+ >
89
+ mcp-use
90
+ </a>
91
+ </footer>
92
+ </div>
93
+ </div>
94
+ );
95
+ }
96
+
97
+ function Section(props: {
98
+ title: string;
99
+ subtitle?: string;
100
+ children: ReactNode;
101
+ }) {
102
+ return (
103
+ <section className="border-t border-black/[0.08] bg-white px-6 py-8">
104
+ <h2 className="text-xl font-semibold text-[#172037]">{props.title}</h2>
105
+ {props.subtitle && (
106
+ <p className="mt-1 text-sm text-[#172037]/80">{props.subtitle}</p>
107
+ )}
108
+ <div className="mt-6">{props.children}</div>
109
+ </section>
110
+ );
111
+ }
112
+
113
+ /** One tab per client; the panel's shape depends on what the target holds. */
114
+ function InstallTabs({ targets }: { targets: InstallTarget[] }) {
115
+ const [active, setActive] = useState(targets[0].label);
116
+ const target = targets.find((t) => t.label === active) ?? targets[0];
117
+
118
+ return (
119
+ <div>
120
+ <div
121
+ role="tablist"
122
+ className="flex flex-wrap gap-0.5 rounded-lg bg-[#172037]/5 p-[3px]"
123
+ >
124
+ {targets.map(({ label }) => (
125
+ <button
126
+ key={label}
127
+ type="button"
128
+ role="tab"
129
+ aria-selected={label === active}
130
+ onClick={() => setActive(label)}
131
+ className={`min-w-20 flex-1 rounded-md border px-3 py-2 text-[13px] font-medium ${
132
+ label === active
133
+ ? "border-black/10 bg-white text-[#172037] shadow-sm"
134
+ : "border-transparent text-slate-500 hover:bg-white/60 hover:text-[#172037]"
135
+ }`}
136
+ >
137
+ {label}
138
+ </button>
139
+ ))}
140
+ </div>
141
+
142
+ <div role="tabpanel" className="mt-6 text-slate-600">
143
+ {"command" in target && (
144
+ <>
145
+ <p>Run this command in your terminal:</p>
146
+ <div className="mt-3 flex items-center gap-2 rounded-lg border border-black/[0.08] bg-slate-50 py-1.5 pr-1.5 pl-3">
147
+ <pre className="flex-1 overflow-x-auto font-mono text-sm">
148
+ {target.command}
149
+ </pre>
150
+ <CopyButton text={target.command} label="Copy command" />
151
+ </div>
152
+ </>
153
+ )}
154
+ {"deeplink" in target && (
155
+ <>
156
+ <p>Add this server to {target.label} in one click:</p>
157
+ <InstallLink href={target.deeplink}>
158
+ Open in {target.label}
159
+ </InstallLink>
160
+ <p className="mt-3 text-sm">
161
+ Or add it manually: Settings → MCP → Add server
162
+ </p>
163
+ </>
164
+ )}
165
+ {"steps" in target && (
166
+ <ol className="list-decimal space-y-2 pl-5">
167
+ {target.steps.map((step) => {
168
+ const [heading, ...rest] = step.split(": ");
169
+ return (
170
+ <li key={step}>
171
+ <strong className="text-[#172037]">{heading}:</strong>{" "}
172
+ {rest.join(": ")}
173
+ </li>
174
+ );
175
+ })}
176
+ </ol>
177
+ )}
178
+ {"url" in target && (
179
+ <>
180
+ <p>Chat with this server and try its tools in the browser:</p>
181
+ <InstallLink href={target.url} external>
182
+ Open {target.label}
183
+ </InstallLink>
184
+ </>
185
+ )}
186
+ </div>
187
+ </div>
188
+ );
189
+ }
190
+
191
+ function InstallLink(props: {
192
+ href: string;
193
+ external?: boolean;
194
+ children: ReactNode;
195
+ }) {
196
+ return (
197
+ <a
198
+ href={props.href}
199
+ {...(props.external && { target: "_blank", rel: "noopener noreferrer" })}
200
+ className="mt-3 inline-block rounded-full bg-slate-900 px-3.5 py-1.5 text-[13px] font-medium text-white hover:bg-slate-800"
201
+ >
202
+ {props.children}
203
+ </a>
204
+ );
205
+ }
206
+
207
+ /** Registered tools. Reading the list is what ships it to the page. */
208
+ function Tools() {
209
+ const tools = useToolList();
210
+ if (tools.length === 0) return null;
211
+
212
+ return (
213
+ <Section title={`Tools (${tools.length})`}>
214
+ <ul className="divide-y divide-slate-100">
215
+ {tools.map((tool) => (
216
+ <li key={tool.name} className="py-3 first:pt-0 last:pb-0">
217
+ <code className="rounded bg-slate-100 px-1.5 py-0.5 font-mono text-[13px]">
218
+ {tool.title ?? tool.name}
219
+ </code>
220
+ {tool.description && (
221
+ <p className="mt-2 text-sm text-slate-500">{tool.description}</p>
222
+ )}
223
+ </li>
224
+ ))}
225
+ </ul>
226
+ </Section>
227
+ );
228
+ }
229
+
230
+ function CopyButton({ text, label }: { text: string; label: string }) {
231
+ const [copied, setCopied] = useState(false);
232
+
233
+ async function copy() {
234
+ try {
235
+ await navigator.clipboard.writeText(text);
236
+ setCopied(true);
237
+ setTimeout(() => setCopied(false), 2000);
238
+ } catch {
239
+ // The clipboard needs a secure context (https or localhost).
240
+ }
241
+ }
242
+
243
+ return (
244
+ <button
245
+ type="button"
246
+ onClick={copy}
247
+ aria-label={label}
248
+ className="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-100"
249
+ >
250
+ {copied ? "Copied" : "Copy"}
251
+ </button>
252
+ );
253
+ }
@@ -27,10 +27,14 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "mcp-use": "workspace:*",
30
+ "react": "^19.2.7",
31
+ "react-dom": "^19.2.7",
30
32
  "zod": "^4.4.3"
31
33
  },
32
34
  "devDependencies": {
33
35
  "@types/node": "^22.20.0",
36
+ "@types/react": "^19.2.17",
37
+ "@types/react-dom": "^19.2.3",
34
38
  "typescript": "^7.0.2"
35
39
  }
36
40
  }
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "include": [
17
17
  "index.ts",
18
+ "**/landing.tsx",
18
19
  "mcp-env.d.ts",
19
20
  "src/**/*",
20
21
  "views/**/*",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mcp-use-app",
3
- "version": "2.0.10",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "Create MCP-Use apps with one command",
6
6
  "author": "mcp-use, Inc.",