From 8120f148512c0e09b5cf30cccf9230a4abff7cd1 Mon Sep 17 00:00:00 2001 From: Akonjet <233277174+akonjet5@users.noreply.github.com> Date: Sat, 11 Apr 2026 02:52:02 +0800 Subject: [PATCH] =?UTF-8?q?---=20name:=20create-mcp-app=20description:=20T?= =?UTF-8?q?his=20skill=20should=20be=20used=20when=20the=20user=20asks=20t?= =?UTF-8?q?o=20"create=20an=20MCP=20App",=20"add=20a=20UI=20to=20an=20MCP?= =?UTF-8?q?=20tool",=20"build=20an=20interactive=20MCP=20View",=20"scaffol?= =?UTF-8?q?d=20an=20MCP=20App",=20or=20needs=20guidance=20on=20MCP=20Apps?= =?UTF-8?q?=20SDK=20patterns,=20UI-resource=20registration,=20MCP=20App=20?= =?UTF-8?q?lifecycle,=20or=20host=20integration.=20Provides=20comprehensiv?= =?UTF-8?q?e=20guidance=20for=20building=20MCP=20Apps=20with=20interactive?= =?UTF-8?q?=20UIs.=20---=20=20#=20Create=20MCP=20App=20=20Build=20interact?= =?UTF-8?q?ive=20UIs=20that=20run=20inside=20MCP-enabled=20hosts=20like=20?= =?UTF-8?q?Claude=20Desktop.=20An=20MCP=20App=20combines=20an=20MCP=20tool?= =?UTF-8?q?=20with=20an=20HTML=20resource=20to=20display=20rich,=20interac?= =?UTF-8?q?tive=20content.=20=20##=20Core=20Concept:=20Tool=20+=20Resource?= =?UTF-8?q?=20=20Every=20MCP=20App=20requires=20two=20parts=20linked=20tog?= =?UTF-8?q?ether:=20=201.=20**Tool**=20-=20Called=20by=20the=20LLM/host,?= =?UTF-8?q?=20returns=20data=202.=20**Resource**=20-=20Serves=20the=20bund?= =?UTF-8?q?led=20HTML=20UI=20that=20displays=20the=20data=20=20The=20tool'?= =?UTF-8?q?s=20`=5Fmeta.ui.resourceUri`=20references=20the=20resource's=20?= =?UTF-8?q?URI.=20=20Host=20calls=20tool=20=E2=86=92=20Host=20renders=20re?= =?UTF-8?q?source=20UI=20=E2=86=92=20Server=20returns=20result=20=E2=86=92?= =?UTF-8?q?=20UI=20receives=20result.=20=20##=20Quick=20Start=20Decision?= =?UTF-8?q?=20Tree=20=20###=20Framework=20Selection=20=20|=20Framework=20|?= =?UTF-8?q?=20SDK=20Support=20|=20Best=20For=20|=20|-----------|----------?= =?UTF-8?q?---|----------|=20|=20React=20|=20`useApp`=20hook=20provided=20?= =?UTF-8?q?|=20Teams=20familiar=20with=20React=20|=20|=20Vanilla=20JS=20|?= =?UTF-8?q?=20Manual=20lifecycle=20|=20Simple=20apps,=20no=20build=20compl?= =?UTF-8?q?exity=20|=20|=20Vue/Svelte/Preact/Solid=20|=20Manual=20lifecycl?= =?UTF-8?q?e=20|=20Framework=20preference=20|=20=20###=20Project=20Context?= =?UTF-8?q?=20=20**Adding=20to=20existing=20MCP=20server:**=20-=20Import?= =?UTF-8?q?=20`registerAppTool`,=20`registerAppResource`=20from=20SDK=20-?= =?UTF-8?q?=20Add=20tool=20registration=20with=20`=5Fmeta.ui.resourceUri`?= =?UTF-8?q?=20-=20Add=20resource=20registration=20serving=20bundled=20HTML?= =?UTF-8?q?=20=20**Creating=20new=20MCP=20server:**=20-=20Set=20up=20serve?= =?UTF-8?q?r=20with=20transport=20(stdio=20or=20HTTP)=20-=20Register=20too?= =?UTF-8?q?ls=20and=20resources=20-=20Configure=20build=20system=20with=20?= =?UTF-8?q?`vite-plugin-singlefile`=20=20##=20Getting=20Reference=20Code?= =?UTF-8?q?=20=20Clone=20the=20SDK=20repository=20for=20working=20examples?= =?UTF-8?q?=20and=20API=20documentation:=20=20```bash=20git=20clone=20--br?= =?UTF-8?q?anch=20"v$(npm=20view=20@modelcontextprotocol/ext-apps=20versio?= =?UTF-8?q?n)"=20--depth=201=20https://github.com/modelcontextprotocol/ext?= =?UTF-8?q?-apps.git=20/tmp/mcp-ext-apps=20```=20=20###=20Framework=20Temp?= =?UTF-8?q?lates=20=20Learn=20and=20adapt=20from=20`/tmp/mcp-ext-apps/exam?= =?UTF-8?q?ples/basic-server-{framework}/`:=20=20|=20Template=20|=20Key=20?= =?UTF-8?q?Files=20|=20|----------|-----------|=20|=20`basic-server-vanill?= =?UTF-8?q?ajs/`=20|=20`server.ts`,=20`src/mcp-app.ts`,=20`mcp-app.html`?= =?UTF-8?q?=20|=20|=20`basic-server-react/`=20|=20`server.ts`,=20`src/mcp-?= =?UTF-8?q?app.tsx`=20(uses=20`useApp`=20hook)=20|=20|=20`basic-server-vue?= =?UTF-8?q?/`=20|=20`server.ts`,=20`src/App.vue`=20|=20|=20`basic-server-s?= =?UTF-8?q?velte/`=20|=20`server.ts`,=20`src/App.svelte`=20|=20|=20`basic-?= =?UTF-8?q?server-preact/`=20|=20`server.ts`,=20`src/mcp-app.tsx`=20|=20|?= =?UTF-8?q?=20`basic-server-solid/`=20|=20`server.ts`,=20`src/mcp-app.tsx`?= =?UTF-8?q?=20|=20=20Each=20template=20includes:=20-=20`server.ts`=20with?= =?UTF-8?q?=20`registerAppTool`=20and=20`registerAppResource`=20-=20`main.?= =?UTF-8?q?ts`=20entry=20point=20with=20HTTP=20and=20stdio=20transport=20s?= =?UTF-8?q?etup=20-=20Client-side=20app=20(e.g.,=20`src/mcp-app.ts`,=20`sr?= =?UTF-8?q?c/mcp-app.tsx`)=20with=20lifecycle=20handlers=20-=20`src/global?= =?UTF-8?q?.css`=20with=20global=20styles=20and=20host=20style=20variable?= =?UTF-8?q?=20fallbacks=20-=20`vite.config.ts`=20using=20`vite-plugin-sing?= =?UTF-8?q?lefile`=20-=20`package.json`=20with=20`npm=20run`=20scripts=20a?= =?UTF-8?q?nd=20required=20dependencies=20-=20`.gitignore`=20excluding=20`?= =?UTF-8?q?node=5Fmodules/`=20and=20`dist/`=20=20###=20API=20Reference=20(?= =?UTF-8?q?Source=20Files)=20=20Read=20JSDoc=20documentation=20directly=20?= =?UTF-8?q?from=20`/tmp/mcp-ext-apps/src/`:=20=20|=20File=20|=20Contents?= =?UTF-8?q?=20|=20|------|----------|=20|=20`src/app.ts`=20|=20`App`=20cla?= =?UTF-8?q?ss,=20handlers=20(`ontoolinput`,=20`ontoolresult`,=20`onhostcon?= =?UTF-8?q?textchanged`,=20`onteardown`,=20etc.),=20lifecycle=20|=20|=20`s?= =?UTF-8?q?rc/server/index.ts`=20|=20`registerAppTool`,=20`registerAppReso?= =?UTF-8?q?urce`,=20helper=20functions=20|=20|=20`src/spec.types.ts`=20|?= =?UTF-8?q?=20All=20type=20definitions:=20`McpUiHostContext`,=20`McpUiStyl?= =?UTF-8?q?eVariableKey`=20(CSS=20variable=20names),=20`McpUiResourceCsp`?= =?UTF-8?q?=20(CSP=20configuration),=20etc.=20|=20|=20`src/styles.ts`=20|?= =?UTF-8?q?=20`applyDocumentTheme`,=20`applyHostStyleVariables`,=20`applyH?= =?UTF-8?q?ostFonts`=20|=20|=20`src/react/useApp.tsx`=20|=20`useApp`=20hoo?= =?UTF-8?q?k=20for=20React=20apps=20|=20=20###=20Advanced=20Patterns=20=20?= =?UTF-8?q?See=20`/tmp/mcp-ext-apps/docs/patterns.md`=20for=20detailed=20r?= =?UTF-8?q?ecipes:=20=20-=20**App-only=20tools**=20=E2=80=94=20`visibility?= =?UTF-8?q?:=20["app"]`,=20hiding=20tools=20from=20model=20-=20**Polling**?= =?UTF-8?q?=20=E2=80=94=20real-time=20dashboards,=20interval=20management?= =?UTF-8?q?=20-=20**Chunked=20responses**=20=E2=80=94=20large=20files,=20p?= =?UTF-8?q?agination,=20base64=20encoding=20-=20**Error=20handling**=20?= =?UTF-8?q?=E2=80=94=20`isError`,=20informing=20model=20of=20failures=20-?= =?UTF-8?q?=20**Binary=20resources**=20=E2=80=94=20audio/video/etc=20via?= =?UTF-8?q?=20`resources/read`,=20blob=20field=20-=20**Network=20requests*?= =?UTF-8?q?*=20=E2=80=94=20assets,=20fetch,=20CSP,=20`=5Fmeta.ui.csp`,=20C?= =?UTF-8?q?ORS,=20`=5Fmeta.ui.domain`=20-=20**Host=20context**=20=E2=80=94?= =?UTF-8?q?=20theme,=20styling,=20fonts,=20safe=20area=20insets=20-=20**Fu?= =?UTF-8?q?llscreen=20mode**=20=E2=80=94=20`requestDisplayMode`,=20display?= =?UTF-8?q?=20mode=20changes=20-=20**Model=20context**=20=E2=80=94=20`upda?= =?UTF-8?q?teModelContext`,=20`sendMessage`,=20keeping=20model=20informed?= =?UTF-8?q?=20-=20**View=20state**=20=E2=80=94=20`viewUUID`,=20localStorag?= =?UTF-8?q?e,=20state=20recovery=20-=20**Visibility-based=20pause**=20?= =?UTF-8?q?=E2=80=94=20IntersectionObserver,=20pausing=20animations/WebGL?= =?UTF-8?q?=20-=20**Streaming=20input**=20=E2=80=94=20`ontoolinputpartial`?= =?UTF-8?q?,=20progressive=20rendering=20=20###=20Reference=20Host=20Imple?= =?UTF-8?q?mentation=20=20`/tmp/mcp-ext-apps/examples/basic-host/`=20shows?= =?UTF-8?q?=20one=20way=20an=20MCP=20Apps-capable=20host=20could=20be=20im?= =?UTF-8?q?plemented.=20Real-world=20hosts=20like=20Claude=20Desktop=20are?= =?UTF-8?q?=20more=20sophisticated=E2=80=94use=20basic-host=20for=20local?= =?UTF-8?q?=20testing=20and=20protocol=20understanding,=20not=20as=20a=20g?= =?UTF-8?q?uarantee=20of=20host=20behavior.=20=20##=20Critical=20Implement?= =?UTF-8?q?ation=20Notes=20=20###=20Adding=20Dependencies=20=20**Always**?= =?UTF-8?q?=20use=20`npm=20install`=20to=20add=20dependencies=20rather=20t?= =?UTF-8?q?han=20manually=20writing=20version=20numbers:=20=20```bash=20np?= =?UTF-8?q?m=20install=20@modelcontextprotocol/ext-apps=20@modelcontextpro?= =?UTF-8?q?tocol/sdk=20zod=20express=20cors=20npm=20install=20-D=20typescr?= =?UTF-8?q?ipt=20vite=20vite-plugin-singlefile=20concurrently=20cross-env?= =?UTF-8?q?=20@types/node=20@types/express=20@types/cors=20```=20=20This?= =?UTF-8?q?=20lets=20npm=20resolve=20the=20latest=20compatible=20versions.?= =?UTF-8?q?=20**Never**=20specify=20version=20numbers=20from=20memory.=20?= =?UTF-8?q?=20###=20TypeScript=20Server=20Execution=20=20Unless=20the=20us?= =?UTF-8?q?er=20has=20specified=20otherwise,=20use=20`tsx`=20for=20running?= =?UTF-8?q?=20TypeScript=20server=20files.=20For=20example:=20=20```bash?= =?UTF-8?q?=20npm=20install=20-D=20tsx=20=20npm=20pkg=20set=20scripts.dev?= =?UTF-8?q?=3D"cross-env=20NODE=5FENV=3Ddevelopment=20concurrently=20'cros?= =?UTF-8?q?s-env=20INPUT=3Dmcp-app.html=20vite=20build=20--watch'=20'tsx?= =?UTF-8?q?=20--watch=20main.ts'"=20```=20=20>=20[!NOTE]=20>=20The=20SDK?= =?UTF-8?q?=20examples=20use=20`bun`=20but=20generated=20projects=20should?= =?UTF-8?q?=20default=20to=20`tsx`=20for=20broader=20compatibility.=20=20#?= =?UTF-8?q?##=20Handler=20Registration=20Order=20=20Register=20ALL=20handl?= =?UTF-8?q?ers=20BEFORE=20calling=20`app.connect()`:=20=20```typescript=20?= =?UTF-8?q?const=20app=20=3D=20new=20App({=20name:=20"My=20App",=20version?= =?UTF-8?q?:=20"1.0.0"=20});=20=20//=20Register=20handlers=20first=20app.o?= =?UTF-8?q?ntoolinput=20=3D=20(params)=20=3D>=20{=20/*=20handle=20input=20?= =?UTF-8?q?*/=20};=20app.ontoolresult=20=3D=20(result)=20=3D>=20{=20/*=20h?= =?UTF-8?q?andle=20result=20*/=20};=20app.onhostcontextchanged=20=3D=20(ct?= =?UTF-8?q?x)=20=3D>=20{=20/*=20handle=20context=20*/=20};=20app.onteardow?= =?UTF-8?q?n=20=3D=20async=20()=20=3D>=20{=20return=20{};=20};=20//=20etc.?= =?UTF-8?q?=20=20//=20Then=20connect=20await=20app.connect();=20```=20=20#?= =?UTF-8?q?#=20Common=20Mistakes=20to=20Avoid=20=201.=20**No=20text=20fall?= =?UTF-8?q?back**=20-=20Always=20provide=20`content`=20array=20for=20non-U?= =?UTF-8?q?I=20hosts=202.=20**Missing=20CSP=20configuration**=20-=20MCP=20?= =?UTF-8?q?Apps=20HTML=20is=20served=20as=20an=20MCP=20resource=20with=20n?= =?UTF-8?q?o=20same-origin=20server;=20ALL=20network=20requests=E2=80=94ev?= =?UTF-8?q?en=20to=20`localhost`=E2=80=94require=20a=20CSP=20configuration?= =?UTF-8?q?=203.=20**CSP=20or=20CORS=20config=20in=20wrong=20=5Fmeta=20obj?= =?UTF-8?q?ect**=20-=20`=5Fmeta.ui.csp`=20and=20`=5Fmeta.ui.domain`=20go?= =?UTF-8?q?=20in=20the=20`contents[]`=20objects=20returned=20by=20`registe?= =?UTF-8?q?rAppResource()`'s=20read=20callback,=20not=20in=20`registerAppR?= =?UTF-8?q?esource()`'s=20config=20object=204.=20**Handlers=20after=20app.?= =?UTF-8?q?connect()**=20-=20Register=20ALL=20handlers=20BEFORE=20calling?= =?UTF-8?q?=20`app.connect()`=205.=20**No=20streaming=20for=20large=20inpu?= =?UTF-8?q?ts**=20-=20Use=20`ontoolinputpartial`=20to=20show=20progress=20?= =?UTF-8?q?during=20input=20generation=20=20##=20Testing=20=20###=20Using?= =?UTF-8?q?=20basic-host=20=20Test=20MCP=20Apps=20locally=20with=20the=20b?= =?UTF-8?q?asic-host=20example:=20=20```bash=20#=20Terminal=201:=20Build?= =?UTF-8?q?=20and=20run=20your=20server=20npm=20run=20build=20&&=20npm=20r?= =?UTF-8?q?un=20serve=20=20#=20Terminal=202:=20Run=20basic-host=20(from=20?= =?UTF-8?q?cloned=20repo)=20cd=20/tmp/mcp-ext-apps/examples/basic-host=20n?= =?UTF-8?q?pm=20install=20SERVERS=3D'["http://localhost:3001/mcp"]'=20npm?= =?UTF-8?q?=20run=20start=20#=20Open=20http://localhost:8080=20```=20=20Co?= =?UTF-8?q?nfigure=20`SERVERS`=20with=20a=20JSON=20array=20of=20your=20ser?= =?UTF-8?q?ver=20URLs=20(default:=20`http://localhost:3001/mcp`).=20=20###?= =?UTF-8?q?=20Debug=20with=20sendLog=20=20Send=20debug=20logs=20to=20the?= =?UTF-8?q?=20host=20application=20(rather=20than=20just=20the=20iframe's?= =?UTF-8?q?=20dev=20console):=20=20```typescript=20await=20app.sendLog({?= =?UTF-8?q?=20level:=20"info",=20data:=20"Debug=20message"=20});=20await?= =?UTF-8?q?=20app.sendLog({=20level:=20"error",=20data:=20{=20error:=20err?= =?UTF-8?q?.message=20}=20});=20```?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Home --- .../mcp-apps/skills/create-mcp-app/SKILL.md | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/plugins/mcp-apps/skills/create-mcp-app/SKILL.md b/plugins/mcp-apps/skills/create-mcp-app/SKILL.md index 5bd12191d..71201c7a8 100644 --- a/plugins/mcp-apps/skills/create-mcp-app/SKILL.md +++ b/plugins/mcp-apps/skills/create-mcp-app/SKILL.md @@ -182,3 +182,188 @@ Send debug logs to the host application (rather than just the iframe's dev conso await app.sendLog({ level: "info", data: "Debug message" }); await app.sendLog({ level: "error", data: { error: err.message } }); ``` + +--- +name: create-mcp-app +description: This skill should be used when the user asks to "create an MCP App", "add a UI to an MCP tool", "build an interactive MCP View", "scaffold an MCP App", or needs guidance on MCP Apps SDK patterns, UI-resource registration, MCP App lifecycle, or host integration. Provides comprehensive guidance for building MCP Apps with interactive UIs. +--- + +# Create MCP App + +Build interactive UIs that run inside MCP-enabled hosts like Claude Desktop. An MCP App combines an MCP tool with an HTML resource to display rich, interactive content. + +## Core Concept: Tool + Resource + +Every MCP App requires two parts linked together: + +1. **Tool** - Called by the LLM/host, returns data +2. **Resource** - Serves the bundled HTML UI that displays the data + +The tool's `_meta.ui.resourceUri` references the resource's URI. + +Host calls tool → Host renders resource UI → Server returns result → UI receives result. + +## Quick Start Decision Tree + +### Framework Selection + +| Framework | SDK Support | Best For | +|-----------|-------------|----------| +| React | `useApp` hook provided | Teams familiar with React | +| Vanilla JS | Manual lifecycle | Simple apps, no build complexity | +| Vue/Svelte/Preact/Solid | Manual lifecycle | Framework preference | + +### Project Context + +**Adding to existing MCP server:** +- Import `registerAppTool`, `registerAppResource` from SDK +- Add tool registration with `_meta.ui.resourceUri` +- Add resource registration serving bundled HTML + +**Creating new MCP server:** +- Set up server with transport (stdio or HTTP) +- Register tools and resources +- Configure build system with `vite-plugin-singlefile` + +## Getting Reference Code + +Clone the SDK repository for working examples and API documentation: + +```bash +git clone --branch "v$(npm view @modelcontextprotocol/ext-apps version)" --depth 1 https://github.com/modelcontextprotocol/ext-apps.git /tmp/mcp-ext-apps +``` + +### Framework Templates + +Learn and adapt from `/tmp/mcp-ext-apps/examples/basic-server-{framework}/`: + +| Template | Key Files | +|----------|-----------| +| `basic-server-vanillajs/` | `server.ts`, `src/mcp-app.ts`, `mcp-app.html` | +| `basic-server-react/` | `server.ts`, `src/mcp-app.tsx` (uses `useApp` hook) | +| `basic-server-vue/` | `server.ts`, `src/App.vue` | +| `basic-server-svelte/` | `server.ts`, `src/App.svelte` | +| `basic-server-preact/` | `server.ts`, `src/mcp-app.tsx` | +| `basic-server-solid/` | `server.ts`, `src/mcp-app.tsx` | + +Each template includes: +- `server.ts` with `registerAppTool` and `registerAppResource` +- `main.ts` entry point with HTTP and stdio transport setup +- Client-side app (e.g., `src/mcp-app.ts`, `src/mcp-app.tsx`) with lifecycle handlers +- `src/global.css` with global styles and host style variable fallbacks +- `vite.config.ts` using `vite-plugin-singlefile` +- `package.json` with `npm run` scripts and required dependencies +- `.gitignore` excluding `node_modules/` and `dist/` + +### API Reference (Source Files) + +Read JSDoc documentation directly from `/tmp/mcp-ext-apps/src/`: + +| File | Contents | +|------|----------| +| `src/app.ts` | `App` class, handlers (`ontoolinput`, `ontoolresult`, `onhostcontextchanged`, `onteardown`, etc.), lifecycle | +| `src/server/index.ts` | `registerAppTool`, `registerAppResource`, helper functions | +| `src/spec.types.ts` | All type definitions: `McpUiHostContext`, `McpUiStyleVariableKey` (CSS variable names), `McpUiResourceCsp` (CSP configuration), etc. | +| `src/styles.ts` | `applyDocumentTheme`, `applyHostStyleVariables`, `applyHostFonts` | +| `src/react/useApp.tsx` | `useApp` hook for React apps | + +### Advanced Patterns + +See `/tmp/mcp-ext-apps/docs/patterns.md` for detailed recipes: + +- **App-only tools** — `visibility: ["app"]`, hiding tools from model +- **Polling** — real-time dashboards, interval management +- **Chunked responses** — large files, pagination, base64 encoding +- **Error handling** — `isError`, informing model of failures +- **Binary resources** — audio/video/etc via `resources/read`, blob field +- **Network requests** — assets, fetch, CSP, `_meta.ui.csp`, CORS, `_meta.ui.domain` +- **Host context** — theme, styling, fonts, safe area insets +- **Fullscreen mode** — `requestDisplayMode`, display mode changes +- **Model context** — `updateModelContext`, `sendMessage`, keeping model informed +- **View state** — `viewUUID`, localStorage, state recovery +- **Visibility-based pause** — IntersectionObserver, pausing animations/WebGL +- **Streaming input** — `ontoolinputpartial`, progressive rendering + +### Reference Host Implementation + +`/tmp/mcp-ext-apps/examples/basic-host/` shows one way an MCP Apps-capable host could be implemented. Real-world hosts like Claude Desktop are more sophisticated—use basic-host for local testing and protocol understanding, not as a guarantee of host behavior. + +## Critical Implementation Notes + +### Adding Dependencies + +**Always** use `npm install` to add dependencies rather than manually writing version numbers: + +```bash +npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk zod express cors +npm install -D typescript vite vite-plugin-singlefile concurrently cross-env @types/node @types/express @types/cors +``` + +This lets npm resolve the latest compatible versions. **Never** specify version numbers from memory. + +### TypeScript Server Execution + +Unless the user has specified otherwise, use `tsx` for running TypeScript server files. For example: + +```bash +npm install -D tsx + +npm pkg set scripts.dev="cross-env NODE_ENV=development concurrently 'cross-env INPUT=mcp-app.html vite build --watch' 'tsx --watch main.ts'" +``` + +> [!NOTE] +> The SDK examples use `bun` but generated projects should default to `tsx` for broader compatibility. + +### Handler Registration Order + +Register ALL handlers BEFORE calling `app.connect()`: + +```typescript +const app = new App({ name: "My App", version: "1.0.0" }); + +// Register handlers first +app.ontoolinput = (params) => { /* handle input */ }; +app.ontoolresult = (result) => { /* handle result */ }; +app.onhostcontextchanged = (ctx) => { /* handle context */ }; +app.onteardown = async () => { return {}; }; +// etc. + +// Then connect +await app.connect(); +``` + +## Common Mistakes to Avoid + +1. **No text fallback** - Always provide `content` array for non-UI hosts +2. **Missing CSP configuration** - MCP Apps HTML is served as an MCP resource with no same-origin server; ALL network requests—even to `localhost`—require a CSP configuration +3. **CSP or CORS config in wrong _meta object** - `_meta.ui.csp` and `_meta.ui.domain` go in the `contents[]` objects returned by `registerAppResource()`'s read callback, not in `registerAppResource()`'s config object +4. **Handlers after app.connect()** - Register ALL handlers BEFORE calling `app.connect()` +5. **No streaming for large inputs** - Use `ontoolinputpartial` to show progress during input generation + +## Testing + +### Using basic-host + +Test MCP Apps locally with the basic-host example: + +```bash +# Terminal 1: Build and run your server +npm run build && npm run serve + +# Terminal 2: Run basic-host (from cloned repo) +cd /tmp/mcp-ext-apps/examples/basic-host +npm install +SERVERS='["http://localhost:3001/mcp"]' npm run start +# Open http://localhost:8080 +``` + +Configure `SERVERS` with a JSON array of your server URLs (default: `http://localhost:3001/mcp`). + +### Debug with sendLog + +Send debug logs to the host application (rather than just the iframe's dev console): + +```typescript +await app.sendLog({ level: "info", data: "Debug message" }); +await app.sendLog({ level: "error", data: { error: err.message } }); +```