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 +3 -0
- package/dist/templates/blank/README.md +2 -0
- package/dist/templates/blank/landing.tsx +253 -0
- package/dist/templates/blank/package.json +5 -1
- package/dist/templates/blank/tsconfig.json +1 -0
- package/dist/templates/mcp-apps/README.md +2 -0
- package/dist/templates/mcp-apps/landing.tsx +253 -0
- package/dist/templates/mcp-apps/tsconfig.json +1 -0
- package/dist/templates/mcp-server/README.md +2 -0
- package/dist/templates/mcp-server/landing.tsx +253 -0
- package/dist/templates/mcp-server/package.json +4 -0
- package/dist/templates/mcp-server/tsconfig.json +1 -0
- package/package.json +1 -1
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,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,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
|
}
|