# Widget Templates Minimal HTML scaffolds for the common widget shapes. Copy, fill in, ship. All templates use the `App` class from `@modelcontextprotocol/ext-apps` via ESM CDN. They're intentionally framework-free — widgets are small enough that React/Vue hydration cost usually isn't worth it. --- ## Serving widget HTML Widgets are static HTML — data arrives at runtime via `ontoolresult`, not baked in. Store each widget as a string constant or read from disk: ```typescript import { readFileSync } from "node:fs"; import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; const pickerHtml = readFileSync("./widgets/picker.html", "utf8"); registerAppResource(server, "Picker", "ui://widgets/picker.html", {}, async () => ({ contents: [{ uri: "ui://widgets/picker.html", mimeType: RESOURCE_MIME_TYPE, text: pickerHtml }], }), ); ``` --- ## Picker (single-select list) ```html ``` **Tool returns:** `{ content: [{ type: "text", text: JSON.stringify({ items: [{ id, label, sub? }] }) }] }` --- ## Confirm dialog ```html

``` **Tool returns:** `{ content: [{ type: "text", text: JSON.stringify({ message, confirmLabel? }) }] }` **Note:** For simple confirmation, prefer **elicitation** over a widget — see `../build-mcp-server/references/elicitation.md`. Use this widget when you need custom styling or context beyond what a native form offers. --- ## Progress (long-running) ```html

Starting…

``` Server side, emit progress via `extra.sendNotification({ method: "notifications/progress", ... })` — see `apps-sdk-messages.md`. --- ## Display-only (chart / preview) Display widgets don't call `sendMessage` — they render and sit there. The tool should return a text summary **alongside** the widget so Claude can keep reasoning while the user sees the visual: ```typescript registerAppTool(server, "show_chart", { description: "Render a revenue chart", inputSchema: { range: z.enum(["week", "month", "year"]) }, _meta: { ui: { resourceUri: "ui://widgets/chart.html" } }, }, async ({ range }) => { const data = await fetchRevenue(range); return { content: [{ type: "text", text: `Revenue is up ${data.change}% over the ${range}. Chart rendered.\n\n` + JSON.stringify(data.points), }], }; }); ``` ```html ```