diff --git a/AGENTS.md b/AGENTS.md index a7ad2f2..623d561 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,7 +82,7 @@ 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. +Standalone docs in `docs/` (listed in `typedoc.config.mjs` `projectDocuments`) can also have type-checked companion `.ts`/`.tsx` files using the same `@includeCode` pattern. ## Full Examples diff --git a/docs/patterns.md b/docs/patterns.md index 5c6b10d..e10210f 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -18,49 +18,41 @@ Set {@link types!McpUiToolMeta.visibility Tool.\_meta.ui.visibility} to `["app"] - 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} +{@includeCode ./patterns.tsx#authenticatedCalls} ## [TODO] Giving errors back to model - Before app runs: validate inputs in tool call - After it runs: use `updateModelContext` -{@includeCode ./patterns.ts#errorsToModel} +{@includeCode ./patterns.tsx#errorsToModel} -## Support native host styling using CSS variables +## Matching host styling (CSS variables, theme, and fonts) -Hosts provide CSS variables (e.g., `--color-background-primary`) that match their theme. Apply these to the document, then use them in your styles via `var()`. +Use the SDK's style helpers to apply host styling, then reference them in your CSS: + +- **CSS variables** — Use `var(--color-background-primary)`, etc. in your CSS +- **Theme** — Use `[data-theme="dark"]` selectors or `light-dark()` function for theme-aware styles +- **Fonts** — Use `var(--font-sans)` or `var(--font-mono)` with fallbacks (e.g., `font-family: var(--font-sans, system-ui, sans-serif)`) **Vanilla JS:** -{@includeCode ../src/styles.examples.ts#applyHostStyleVariables_fromHostContext} +{@includeCode ./patterns.tsx#hostStylingVanillaJs} **React:** -{@includeCode ../src/react/useHostStyles.examples.tsx#useHostStyles_basicUsage} - -## Reacting to light/dark theme changes - -The host provides a `theme` value (`"light"` or `"dark"`) in the context. Apply it to `` so you can use CSS selectors like `[data-theme="dark"]` for styling. - -**Vanilla JS:** - -{@includeCode ../src/styles.examples.ts#applyDocumentTheme_fromHostContext} - -**React:** `useHostStyles()` (shown above) applies theme automatically. To read the theme reactively for conditional rendering: - -{@includeCode ../src/react/useDocumentTheme.examples.tsx#useDocumentTheme_conditionalRender} +{@includeCode ./patterns.tsx#hostStylingReact} ## [TODO] Support fullscreen / exit fullscreen -{@includeCode ./patterns.ts#fullscreen} +{@includeCode ./patterns.tsx#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} +{@includeCode ./patterns.tsx#persistData} ## [TODO] Lower perceived latency / manage loading time @@ -68,13 +60,13 @@ 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} +{@includeCode ./patterns.tsx#lowerPerceivedLatency} ## [TODO] Supporting both iframe & MCP Apps in same binary See recipe: https://github.com/modelcontextprotocol/ext-apps/issues/34 -{@includeCode ./patterns.ts#iframeAndMcpApps} +{@includeCode ./patterns.tsx#iframeAndMcpApps} ## [TODO] Migrating from OpenAI to MCP Apps @@ -82,4 +74,4 @@ See [OpenAI -> MCP Apps](https://docs.google.com/document/d/13ROImOR9B8xc32yhqsF Also: [Managing State](https://platform.openai.com/docs/actions/managing-state) (OpenAI) -{@includeCode ./patterns.ts#migrateFromOpenai} +{@includeCode ./patterns.tsx#migrateFromOpenai} diff --git a/docs/patterns.ts b/docs/patterns.tsx similarity index 60% rename from docs/patterns.ts rename to docs/patterns.tsx index 757d654..6d6bfcc 100644 --- a/docs/patterns.ts +++ b/docs/patterns.tsx @@ -8,6 +8,13 @@ */ import { App } from "../src/app.js"; +import { + applyDocumentTheme, + applyHostFonts, + applyHostStyleVariables, +} from "../src/styles.js"; +import type { McpUiHostContext } from "../src/types.js"; +import { useApp, useHostStyles } from "../src/react/index.js"; /** * Example: Authenticated calls from App @@ -31,6 +38,65 @@ function errorsToModel(app: App) { //#endregion errorsToModel } +/** + * Example: Unified host styling (theme, CSS variables, fonts) + */ +function hostStylingVanillaJs(app: App) { + //#region hostStylingVanillaJs + function applyHostContext(ctx: McpUiHostContext) { + if (ctx.theme) { + applyDocumentTheme(ctx.theme); + } + if (ctx.styles?.variables) { + applyHostStyleVariables(ctx.styles.variables); + } + if (ctx.styles?.css?.fonts) { + applyHostFonts(ctx.styles.css.fonts); + } + } + + // Apply when host context changes + app.onhostcontextchanged = applyHostContext; + + // Apply initial styles after connecting + app.connect().then(() => { + const ctx = app.getHostContext(); + if (ctx) { + applyHostContext(ctx); + } + }); + //#endregion hostStylingVanillaJs +} + +/** + * Example: Host styling with React (CSS variables, theme, fonts) + */ +function hostStylingReact() { + //#region hostStylingReact + function MyApp() { + const { app } = useApp({ + appInfo: { name: "MyApp", version: "1.0.0" }, + capabilities: {}, + }); + + // Apply all host styles (variables, theme, fonts) + useHostStyles(app, app?.getHostContext()); + + return ( +
Styled with host CSS variables and fonts
+Uses [data-theme] selectors
+