Skip to content

Commit e205e26

Browse files
Update quickstart guide
- Reorganize prerequisites to emphasize MCP concepts first - Add tip linking to official MCP quickstart for newcomers - Update install command to use published npm package - Remove zod dependency and `structuredContent` usage - Use `RESOURCE_MIME_TYPE` constant for consistency - Add clearer comments explaining tool/resource registration - Simplify UI to extract time from text content 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent c7ef5f0 commit e205e26

File tree

1 file changed

+34
-26
lines changed

1 file changed

+34
-26
lines changed

docs/quickstart.md

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ A simple app that fetches the current server time and displays it in a clickable
1515
1616
## Prerequisites
1717

18-
- Node.js 18+
18+
- Familiarity with MCP concepts, especially [Tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools) and [Resources](https://modelcontextprotocol.io/docs/learn/server-concepts#resources)
1919
- Familiarity with the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
20+
- Node.js 18+
21+
22+
> [!TIP]
23+
> New to building MCP servers? Start with the [official MCP quickstart guide](https://modelcontextprotocol.io/docs/develop/build-server) to learn the core concepts first.
2024
2125
## 1. Project Setup
2226

@@ -30,7 +34,7 @@ npm init -y
3034
Install dependencies:
3135

3236
```bash
33-
npm install github:modelcontextprotocol/ext-apps @modelcontextprotocol/sdk zod
37+
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk
3438
npm install -D typescript vite vite-plugin-singlefile express cors @types/express @types/cors tsx
3539
```
3640

@@ -98,64 +102,68 @@ Create `server.ts`:
98102
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
99103
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
100104
import {
105+
registerAppTool,
106+
registerAppResource,
101107
RESOURCE_MIME_TYPE,
102-
type McpUiToolMeta,
103-
} from "@modelcontextprotocol/ext-apps";
108+
} from "@modelcontextprotocol/ext-apps/server";
104109
import cors from "cors";
105110
import express from "express";
106111
import fs from "node:fs/promises";
107112
import path from "node:path";
108-
import * as z from "zod";
109113

110114
const server = new McpServer({
111115
name: "My MCP App Server",
112116
version: "1.0.0",
113117
});
114118

115-
// Two-part registration: tool + resource
119+
// Two-part registration: tool + resource, tied together by the resource URI.
116120
const resourceUri = "ui://get-time/mcp-app.html";
117121

118-
server.registerTool(
122+
// Register a tool with UI metadata. When the host calls this tool, it reads
123+
// `_meta.ui.resourceUri` to know which resource to fetch and render as an
124+
// interactive UI.
125+
registerAppTool(
126+
server,
119127
"get-time",
120128
{
121129
title: "Get Time",
122130
description: "Returns the current server time.",
123131
inputSchema: {},
124-
outputSchema: { time: z.string() },
125-
_meta: { ui: { resourceUri } as McpUiToolMeta }, // Links tool to UI
132+
_meta: { ui: { resourceUri } },
126133
},
127134
async () => {
128135
const time = new Date().toISOString();
129136
return {
130137
content: [{ type: "text", text: time }],
131-
structuredContent: { time },
132138
};
133139
},
134140
);
135141

136-
server.registerResource(
142+
// Register the resource, which returns the bundled HTML/JavaScript for the UI.
143+
registerAppResource(
144+
server,
137145
resourceUri,
138146
resourceUri,
139-
{ mimeType: "text/html;profile=mcp-app" },
147+
{ mimeType: RESOURCE_MIME_TYPE },
140148
async () => {
141149
const html = await fs.readFile(
142150
path.join(import.meta.dirname, "dist", "mcp-app.html"),
143151
"utf-8",
144152
);
145153
return {
146154
contents: [
147-
{ uri: resourceUri, mimeType: "text/html;profile=mcp-app", text: html },
155+
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
148156
],
149157
};
150158
},
151159
);
152160

153-
// Express server for MCP endpoint
154-
const app = express();
155-
app.use(cors());
156-
app.use(express.json());
161+
// Start an Express server that exposes the MCP endpoint.
162+
const expressApp = express();
163+
expressApp.use(cors());
164+
expressApp.use(express.json());
157165

158-
app.post("/mcp", async (req, res) => {
166+
expressApp.post("/mcp", async (req, res) => {
159167
const transport = new StreamableHTTPServerTransport({
160168
sessionIdGenerator: undefined,
161169
enableJsonResponse: true,
@@ -165,7 +173,7 @@ app.post("/mcp", async (req, res) => {
165173
await transport.handleRequest(req, res, req.body);
166174
});
167175

168-
app.listen(3001, (err) => {
176+
expressApp.listen(3001, (err) => {
169177
if (err) {
170178
console.error("Error starting server:", err);
171179
process.exit(1);
@@ -209,7 +217,7 @@ Create `mcp-app.html`:
209217
Create `src/mcp-app.ts`:
210218

211219
```typescript
212-
import { App, PostMessageTransport } from "@modelcontextprotocol/ext-apps";
220+
import { App } from "@modelcontextprotocol/ext-apps";
213221

214222
// Get element references
215223
const serverTimeEl = document.getElementById("server-time")!;
@@ -220,30 +228,30 @@ const app = new App({ name: "Get Time App", version: "1.0.0" });
220228

221229
// Register handlers BEFORE connecting
222230
app.ontoolresult = (result) => {
223-
const { time } = (result.structuredContent as { time?: string }) ?? {};
231+
const time = result.content?.find((c) => c.type === "text")?.text;
224232
serverTimeEl.textContent = time ?? "[ERROR]";
225233
};
226234

227235
// Wire up button click
228236
getTimeBtn.addEventListener("click", async () => {
229237
const result = await app.callServerTool({ name: "get-time", arguments: {} });
230-
const { time } = (result.structuredContent as { time?: string }) ?? {};
238+
const time = result.content?.find((c) => c.type === "text")?.text;
231239
serverTimeEl.textContent = time ?? "[ERROR]";
232240
});
233241

234242
// Connect to host
235-
app.connect(new PostMessageTransport(window.parent));
243+
app.connect();
236244
```
237245

246+
> [!NOTE]
247+
> **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)
248+
238249
Build the UI:
239250

240251
```bash
241252
npm run build
242253
```
243254

244-
> [!NOTE]
245-
> **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)
246-
247255
This produces `dist/mcp-app.html` which contains your bundled app:
248256

249257
```console

0 commit comments

Comments
 (0)