Skip to content

Commit 0571b56

Browse files
Add Quickstart guide for building MCP Apps
Introduces a step-by-step tutorial covering: - Project setup with Vite and TypeScript - Server creation with tool + resource registration - UI development using the App SDK - Testing with basic-host Also updates README to link to the guide and configures typedoc to include it in generated documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 308537d commit 0571b56

File tree

3 files changed

+253
-0
lines changed

3 files changed

+253
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Then open http://localhost:8080/
6969

7070
## Resources
7171

72+
- [Quickstart Guide](https://modelcontextprotocol.github.io/ext-apps/api/documents/Quickstart.html)
7273
- [API Documentation](https://modelcontextprotocol.github.io/ext-apps/api/)
7374
- [Draft Specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)
7475
- [SEP-1865 Discussion](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865)

docs/quickstart.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
---
2+
title: Quickstart
3+
---
4+
5+
# Build Your First MCP App
6+
7+
This tutorial walks you through building an MCP App—a tool with an interactive UI that renders inside MCP hosts like Claude Desktop.
8+
9+
## What You'll Build
10+
11+
A simple app that fetches the current server time and displays it in a clickable UI. You'll learn the core pattern: **MCP Apps = Tool + UI Resource**.
12+
13+
## Prerequisites
14+
15+
- Node.js 18+
16+
- Familiarity with the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
17+
18+
## 1. Project Setup
19+
20+
Create a new directory and initialize:
21+
22+
```bash
23+
mkdir my-mcp-app && cd my-mcp-app
24+
npm init -y
25+
```
26+
27+
Install dependencies:
28+
29+
```bash
30+
npm install github:modelcontextprotocol/ext-apps @modelcontextprotocol/sdk zod
31+
npm install -D typescript vite vite-plugin-singlefile express cors @types/express @types/cors tsx
32+
```
33+
34+
Create `tsconfig.json`:
35+
36+
```json
37+
{
38+
"compilerOptions": {
39+
"target": "ES2022",
40+
"module": "ESNext",
41+
"moduleResolution": "bundler",
42+
"strict": true,
43+
"esModuleInterop": true,
44+
"skipLibCheck": true,
45+
"outDir": "dist"
46+
},
47+
"include": ["*.ts", "src/**/*.ts"]
48+
}
49+
```
50+
51+
Create `vite.config.ts` — this bundles your UI into a single HTML file:
52+
53+
```typescript
54+
import { defineConfig } from "vite";
55+
import { viteSingleFile } from "vite-plugin-singlefile";
56+
57+
export default defineConfig({
58+
plugins: [viteSingleFile()],
59+
build: {
60+
outDir: "dist",
61+
rollupOptions: {
62+
input: process.env.INPUT,
63+
},
64+
},
65+
});
66+
```
67+
68+
Add to your `package.json`:
69+
70+
```json
71+
{
72+
"type": "module",
73+
"scripts": {
74+
"build": "INPUT=mcp-app.html vite build",
75+
"serve": "npx tsx server.ts"
76+
}
77+
}
78+
```
79+
80+
**Full files:** [`package.json`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/package.json), [`tsconfig.json`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/tsconfig.json), [`vite.config.ts`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/vite.config.ts)
81+
82+
## 2. Create the Server
83+
84+
MCP Apps use a **two-part registration**:
85+
86+
1. A **tool** that the LLM/host calls
87+
2. A **resource** that serves the UI HTML
88+
89+
The tool's `_meta` field links them together.
90+
91+
Create `server.ts`:
92+
93+
```typescript
94+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
95+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
96+
import { RESOURCE_URI_META_KEY } from "@modelcontextprotocol/ext-apps";
97+
import cors from "cors";
98+
import express from "express";
99+
import fs from "node:fs/promises";
100+
import path from "node:path";
101+
import { z } from "zod";
102+
103+
const server = new McpServer({
104+
name: "My MCP App Server",
105+
version: "1.0.0",
106+
});
107+
108+
// Two-part registration: tool + resource
109+
const resourceUri = "ui://get-time/mcp-app.html";
110+
111+
server.registerTool(
112+
"get-time",
113+
{
114+
title: "Get Time",
115+
description: "Returns the current server time.",
116+
inputSchema: {},
117+
outputSchema: { time: z.string() },
118+
_meta: { [RESOURCE_URI_META_KEY]: resourceUri }, // Links tool to UI
119+
},
120+
async () => {
121+
const time = new Date().toISOString();
122+
return {
123+
content: [{ type: "text", text: time }],
124+
structuredContent: { time },
125+
};
126+
},
127+
);
128+
129+
server.registerResource(resourceUri, resourceUri, {}, async () => {
130+
const html = await fs.readFile(
131+
path.join(import.meta.dirname, "dist", "mcp-app.html"),
132+
"utf-8",
133+
);
134+
return {
135+
contents: [{ uri: resourceUri, mimeType: "text/html+mcp", text: html }],
136+
};
137+
});
138+
139+
// Express server for MCP endpoint
140+
const app = express();
141+
app.use(cors());
142+
app.use(express.json());
143+
144+
app.post("/mcp", async (req, res) => {
145+
const transport = new StreamableHTTPServerTransport({
146+
sessionIdGenerator: undefined,
147+
enableJsonResponse: true,
148+
});
149+
res.on("close", () => transport.close());
150+
await server.connect(transport);
151+
await transport.handleRequest(req, res, req.body);
152+
});
153+
154+
app.listen(3001, () => {
155+
console.log("Server listening on http://localhost:3001/mcp");
156+
});
157+
```
158+
159+
**Full file:** [`server.ts`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/server.ts)
160+
161+
## 3. Build the UI
162+
163+
Create `mcp-app.html`:
164+
165+
```html
166+
<!DOCTYPE html>
167+
<html lang="en">
168+
<head>
169+
<meta charset="UTF-8" />
170+
<title>Get Time App</title>
171+
</head>
172+
<body>
173+
<p>
174+
<strong>Server Time:</strong> <code id="server-time">Loading...</code>
175+
</p>
176+
<button id="get-time-btn">Get Server Time</button>
177+
<script type="module" src="/src/mcp-app.ts"></script>
178+
</body>
179+
</html>
180+
```
181+
182+
Create `src/mcp-app.ts`:
183+
184+
```typescript
185+
import { App, PostMessageTransport } from "@modelcontextprotocol/ext-apps";
186+
187+
// Get element references
188+
const serverTimeEl = document.getElementById("server-time")!;
189+
const getTimeBtn = document.getElementById("get-time-btn")!;
190+
191+
// Create app instance
192+
const app = new App({ name: "Get Time App", version: "1.0.0" });
193+
194+
// Register handlers BEFORE connecting
195+
app.ontoolresult = (result) => {
196+
const { time } = (result.structuredContent as { time?: string }) ?? {};
197+
serverTimeEl.textContent = time ?? "[ERROR]";
198+
};
199+
200+
// Wire up button click
201+
getTimeBtn.addEventListener("click", async () => {
202+
const result = await app.callServerTool({ name: "get-time", arguments: {} });
203+
const { time } = (result.structuredContent as { time?: string }) ?? {};
204+
serverTimeEl.textContent = time ?? "[ERROR]";
205+
});
206+
207+
// Connect to host
208+
app.connect(new PostMessageTransport(window.parent));
209+
```
210+
211+
Build the UI:
212+
213+
```bash
214+
npm run build
215+
```
216+
217+
This produces `dist/mcp-app.html` containing your bundled app.
218+
219+
**Full files:** [`mcp-app.html`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/mcp-app.html), [`src/mcp-app.ts`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/src/mcp-app.ts)
220+
221+
## 4. Test It
222+
223+
You'll need two terminals.
224+
225+
**Terminal 1** — Build and start your server:
226+
227+
```bash
228+
npm run build && npm run serve
229+
```
230+
231+
**Terminal 2** — Run the test host (from the [ext-apps repo](https://github.com/modelcontextprotocol/ext-apps)):
232+
233+
```bash
234+
git clone https://github.com/modelcontextprotocol/ext-apps.git
235+
cd ext-apps/examples/basic-host
236+
npm install
237+
npm run start
238+
```
239+
240+
Open http://localhost:8080 in your browser:
241+
242+
1. Select **get-time** from the "Tool Name" dropdown
243+
2. Click **Call Tool**
244+
3. Your UI renders in the sandbox below
245+
4. Click **Get Server Time** — the current time appears!
246+
247+
## Next Steps
248+
249+
- **Host communication**: Add [`sendMessage()`](https://modelcontextprotocol.github.io/ext-apps/api/classes/app.App.html#sendmessage), [`sendLog()`](https://modelcontextprotocol.github.io/ext-apps/api/classes/app.App.html#sendlog), and [`sendOpenLink()`](https://modelcontextprotocol.github.io/ext-apps/api/classes/app.App.html#sendopenlink) to interact with the host — see [`src/mcp-app.ts`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/src/mcp-app.ts)
250+
- **React version**: Compare with [`basic-server-react`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) for a React-based UI
251+
- **API reference**: See the full [API documentation](https://modelcontextprotocol.github.io/ext-apps/api/)

typedoc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"$schema": "https://typedoc.org/schema.json",
3+
"projectDocuments": ["docs/quickstart.md"],
34
"entryPoints": [
45
"src/app.ts",
56
"src/app-bridge.ts",

0 commit comments

Comments
 (0)