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 <noreply@anthropic.com>
This commit is contained in:
Jonathan Hefner 2026-01-19 09:43:57 -06:00
commit 78b24ae2ac
5 changed files with 206 additions and 2 deletions

View file

@ -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:

69
docs/patterns.md Normal file
View file

@ -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}

133
docs/patterns.ts Normal file
View file

@ -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;

View file

@ -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"]
}

View file

@ -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",