From 78b24ae2ac7fa8a030934bc9b60929257fc1709c Mon Sep 17 00:00:00 2001 From: Jonathan Hefner Date: Mon, 19 Jan 2026 09:43:57 -0600 Subject: [PATCH] Add skeleton `docs/patterns.md` with type-checked companion file Introduces a patterns documentation page for common MCP Apps recipes. The skeleton includes TODO sections for: perceived latency, iframe/MCP Apps coexistence, authenticated calls, private tools, theme changes, fullscreen, host styling, data persistence, OpenAI migration, and error handling. - Add `docs/patterns.md` to `projectDocuments` in `typedoc.config.mjs` - Add `docs/patterns.ts` companion file with region-delimited code stubs - Include `docs/**/*.ts` in `tsconfig.json` for type-checking - Document the standalone docs pattern in `CLAUDE.md` Co-Authored-By: Claude Opus 4.5 --- AGENTS.md | 2 + docs/patterns.md | 69 +++++++++++++++++++++++ docs/patterns.ts | 133 +++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 2 +- typedoc.config.mjs | 2 +- 5 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 docs/patterns.md create mode 100644 docs/patterns.ts diff --git a/AGENTS.md b/AGENTS.md index 7701885..a7ad2f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,6 +82,8 @@ Guest UI (App) <--PostMessageTransport--> Host (AppBridge) <--MCP Client--> MCP JSDoc `@example` tags use `{@includeCode ./file.examples.ts#regionName}` to pull in type-checked code from companion `.examples.ts`/`.examples.tsx` files. Regions are marked with `//#region name` and `//#endregion name`, wrapped in functions (whose parameters provide types for external values). Region names follow `exportedName_variant` or `ClassName_methodName_variant` pattern (e.g., `useApp_basicUsage`, `App_hostCapabilities_checkAfterConnection`). +Standalone docs in `docs/` (listed in `typedoc.config.mjs` `projectDocuments`) can also have type-checked companion `.ts` files using the same `@includeCode` pattern. + ## Full Examples Uses npm workspaces. Full examples in `examples/` are separate packages: diff --git a/docs/patterns.md b/docs/patterns.md new file mode 100644 index 0000000..a3eeaf9 --- /dev/null +++ b/docs/patterns.md @@ -0,0 +1,69 @@ +--- +title: Patterns +--- + +# MCP Apps Patterns + +This document covers common patterns and recipes for building MCP Apps. + +## [TODO] Tools that are private to Apps + +Use `Tool._meta.ui.visibility: ["apps"]` to make tools only visible to Apps. + +{@includeCode ./patterns.ts#privateTools} + +## [TODO] Authenticated calls from App + +- Use tool calls / read resources + - See [PDF example](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/pdf-viewer) to read binaries by chunks to avoid call tool size limitations on platforms like claude.ai +- Pass auth token in `_meta` (will be loaded again in the future) + refresh token + store in local storage (see Persist data section below) + +{@includeCode ./patterns.ts#authenticatedCalls} + +## [TODO] Giving errors back to model + +- Before app runs: validate inputs in tool call +- After it runs: use `updateModelContext` + +{@includeCode ./patterns.ts#errorsToModel} + +## [TODO] Support native host styling / use CSS variables + +{@includeCode ./patterns.ts#hostStyling} + +## [TODO] Reacting to light/dark theme changes + +{@includeCode ./patterns.ts#lightDarkTheme} + +## [TODO] Support fullscreen / exit fullscreen + +{@includeCode ./patterns.ts#fullscreen} + +## [TODO] Persist data (incl. widget state) + +- Note: OAI's `window.openai.setWidgetState({modelContent, privateContent, imageIds})` has only a partial equivalent in the MCP Apps spec (for now!): `App.updateModelContext({content, structuredContent})` +- For data persistence / to reload when conversation is reloaded, you must use localStorage / IndexedDb with `hostInfo.toolInfo.id` as key returned `CallToolResult._meta.widgetUUID = randomUUID()` + +{@includeCode ./patterns.ts#persistData} + +## [TODO] Lower perceived latency / manage loading time + +Leverage partial inputs to show widgets as possible. + +Beware of partial JSON being partial (but healed), so some of your objects may not be complete (e.g. in a list you may need to skip the last item if your code validates input schemas strictly). + +{@includeCode ./patterns.ts#lowerPerceivedLatency} + +## [TODO] Supporting both iframe & MCP Apps in same binary + +See recipe: https://github.com/modelcontextprotocol/ext-apps/issues/34 + +{@includeCode ./patterns.ts#iframeAndMcpApps} + +## [TODO] Migrating from OpenAI to MCP Apps + +See [OpenAI -> MCP Apps](https://docs.google.com/document/d/13ROImOR9B8xc32yhqsFyC9Hh3_H63JFORDIyjyIPcU4/edit) migration guide. + +Also: [Managing State](https://platform.openai.com/docs/actions/managing-state) (OpenAI) + +{@includeCode ./patterns.ts#migrateFromOpenai} diff --git a/docs/patterns.ts b/docs/patterns.ts new file mode 100644 index 0000000..1321123 --- /dev/null +++ b/docs/patterns.ts @@ -0,0 +1,133 @@ +/** + * Type-checked code examples for the patterns documentation. + * + * These examples are included in {@link ./patterns.md} via `@includeCode` tags. + * Each function's region markers define the code snippet that appears in the docs. + * + * @module + */ + +import { App } from "../src/app.js"; + +// TODO: Add imports as patterns are implemented + +/** + * Example: Tools that are private to Apps + */ +function privateTools() { + //#region privateTools + // TODO: Tool._meta.ui.visibility: ["apps"] + //#endregion privateTools +} + +/** + * Example: Authenticated calls from App + */ +function authenticatedCalls(app: App) { + //#region authenticatedCalls + // TODO: Use tool calls / read resources + // See PDF example to read binaries by chunks + // Pass auth token in _meta + refresh token + store in local storage + //#endregion authenticatedCalls +} + +/** + * Example: Giving errors back to model + */ +function errorsToModel(app: App) { + //#region errorsToModel + // Before app runs: validate inputs in tool call + // After it runs: updateModelContext + // TODO: Complete implementation + //#endregion errorsToModel +} + +/** + * Example: Support native host styling / use CSS variables + */ +function hostStyling() { + //#region hostStyling + // TODO: Implement host styling support via CSS variables + //#endregion hostStyling +} + +/** + * Example: Reacting to light/dark theme changes + */ +function lightDarkTheme(app: App) { + //#region lightDarkTheme + const applyTheme = (theme: string) => { + document.documentElement.dataset.theme = theme; + }; + + applyTheme(app.getHostContext()?.theme ?? "light"); + app.onhostcontextchanged = (params) => { + if (params.theme) applyTheme(params.theme); + }; + //#endregion lightDarkTheme +} + +/** + * Example: Support fullscreen / exit fullscreen + */ +function fullscreen() { + //#region fullscreen + // TODO: Implement fullscreen support + //#endregion fullscreen +} + +/** + * Example: Persist data (incl. widget state) + */ +function persistData(app: App) { + //#region persistData + // Note: OAI's window.openai.setWidgetState({modelContent, privateContent, imageIds}) + // has only a partial equivalent in MCP Apps: App.updateModelContext({content, structuredContent}) + // For data persistence / to reload when conversation is reloaded, + // use localStorage / IndexedDb with hostInfo.toolInfo.id as key + // returned CallToolResult._meta.widgetUUID = randomUUID() + // TODO: Complete implementation + //#endregion persistData +} + +/** + * Example: Lower perceived latency / manage loading time + */ +function lowerPerceivedLatency(app: App) { + //#region lowerPerceivedLatency + // TODO: Leverage partial inputs to show widgets as possible. + // Beware of partial JSON being partial (but healed), + // so some objects may not be complete. + //#endregion lowerPerceivedLatency +} + +/** + * Example: Supporting both iframe & MCP Apps in same binary + */ +function iframeAndMcpApps() { + //#region iframeAndMcpApps + // TODO: See recipe: https://github.com/modelcontextprotocol/ext-apps/issues/34 + //#endregion iframeAndMcpApps +} + +/** + * Example: Migrating from OpenAI to MCP Apps + */ +function migrateFromOpenai() { + //#region migrateFromOpenai + // TODO: See OpenAI -> MCP Apps migration guide + // https://docs.google.com/document/d/13ROImOR9B8xc32yhqsFyC9Hh3_H63JFORDIyjyIPcU4/edit + //#endregion migrateFromOpenai +} + +// Suppress unused variable warnings +void privateTools; +void authenticatedCalls; +void errorsToModel; +void hostStyling; +void lightDarkTheme; +void fullscreen; +void persistData; +void lowerPerceivedLatency; +void iframeAndMcpApps; +void migrateFromOpenai; diff --git a/tsconfig.json b/tsconfig.json index 85ec789..f9cbb41 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,6 @@ "skipLibCheck": true, "resolveJsonModule": true }, - "include": ["src/**/*.ts", "src/**/*.tsx"], + "include": ["src/**/*.ts", "src/**/*.tsx", "docs/**/*.ts"], "exclude": ["node_modules", "dist", "examples/**/*.ts"] } diff --git a/typedoc.config.mjs b/typedoc.config.mjs index 2016361..3761585 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -4,7 +4,7 @@ import { OptionDefaults } from "typedoc"; const config = { blockTags: [...OptionDefaults.blockTags, "@description"], intentionallyNotExported: ["AppOptions"], - projectDocuments: ["docs/quickstart.md"], + projectDocuments: ["docs/quickstart.md", "docs/patterns.md"], entryPoints: [ "src/server/index.ts", "src/app.ts",