diff --git a/README.md b/README.md index d9e84b3..41b3955 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ Then open http://localhost:8080/ ## Resources +- [Quickstart Guide](https://modelcontextprotocol.github.io/ext-apps/api/documents/Quickstart.html) - [API Documentation](https://modelcontextprotocol.github.io/ext-apps/api/) - [Draft Specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx) - [SEP-1865 Discussion](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865) diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..79a1cfa --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,251 @@ +--- +title: Quickstart +--- + +# Build Your First MCP App + +This tutorial walks you through building an MCP App—a tool with an interactive UI that renders inside MCP hosts like Claude Desktop. + +## What You'll Build + +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**. + +## Prerequisites + +- Node.js 18+ +- Familiarity with the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) + +## 1. Project Setup + +Create a new directory and initialize: + +```bash +mkdir my-mcp-app && cd my-mcp-app +npm init -y +``` + +Install dependencies: + +```bash +npm install github:modelcontextprotocol/ext-apps @modelcontextprotocol/sdk zod +npm install -D typescript vite vite-plugin-singlefile express cors @types/express @types/cors tsx +``` + +Create `tsconfig.json`: + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist" + }, + "include": ["*.ts", "src/**/*.ts"] +} +``` + +Create `vite.config.ts` — this bundles your UI into a single HTML file: + +```typescript +import { defineConfig } from "vite"; +import { viteSingleFile } from "vite-plugin-singlefile"; + +export default defineConfig({ + plugins: [viteSingleFile()], + build: { + outDir: "dist", + rollupOptions: { + input: process.env.INPUT, + }, + }, +}); +``` + +Add to your `package.json`: + +```json +{ + "type": "module", + "scripts": { + "build": "INPUT=mcp-app.html vite build", + "serve": "npx tsx server.ts" + } +} +``` + +**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) + +## 2. Create the Server + +MCP Apps use a **two-part registration**: + +1. A **tool** that the LLM/host calls +2. A **resource** that serves the UI HTML + +The tool's `_meta` field links them together. + +Create `server.ts`: + +```typescript +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { RESOURCE_URI_META_KEY } from "@modelcontextprotocol/ext-apps"; +import cors from "cors"; +import express from "express"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { z } from "zod"; + +const server = new McpServer({ + name: "My MCP App Server", + version: "1.0.0", +}); + +// Two-part registration: tool + resource +const resourceUri = "ui://get-time/mcp-app.html"; + +server.registerTool( + "get-time", + { + title: "Get Time", + description: "Returns the current server time.", + inputSchema: {}, + outputSchema: { time: z.string() }, + _meta: { [RESOURCE_URI_META_KEY]: resourceUri }, // Links tool to UI + }, + async () => { + const time = new Date().toISOString(); + return { + content: [{ type: "text", text: time }], + structuredContent: { time }, + }; + }, +); + +server.registerResource(resourceUri, resourceUri, {}, async () => { + const html = await fs.readFile( + path.join(import.meta.dirname, "dist", "mcp-app.html"), + "utf-8", + ); + return { + contents: [{ uri: resourceUri, mimeType: "text/html+mcp", text: html }], + }; +}); + +// Express server for MCP endpoint +const app = express(); +app.use(cors()); +app.use(express.json()); + +app.post("/mcp", async (req, res) => { + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true, + }); + res.on("close", () => transport.close()); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); +}); + +app.listen(3001, () => { + console.log("Server listening on http://localhost:3001/mcp"); +}); +``` + +**Full file:** [`server.ts`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/server.ts) + +## 3. Build the UI + +Create `mcp-app.html`: + +```html + + +
+ +
+ Server Time: Loading...
+