Add explicit backtick-wrapped display text to all `{@link}` tags for
monospace rendering in TypeDoc output. This improves readability by:
- Using monospace font for code references (types, methods, functions)
- Stripping module prefixes from display text (e.g., `app!App.connect`
displays as `App.connect`)
Updated files across the SDK including `App`, `AppBridge`,
`PostMessageTransport`, React hooks, server helpers, types, and
documentation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.3 KiB
| title |
|---|
| Patterns |
MCP Apps Patterns
This document covers common patterns and recipes for building MCP Apps.
Tools that are private to Apps
Set {@link types!McpUiToolMeta.visibility Tool._meta.ui.visibility} to ["app"] to make tools only callable by Apps (hidden from the model). This is useful for UI-driven actions like updating quantities, toggling settings, or other interactions that shouldn't appear in the model's tool list.
{@includeCode ../src/server/index.examples.ts#registerAppTool_appOnlyVisibility}
See examples/system-monitor-server/ for a full implementation of this pattern.
Reading large amounts of data via chunked tool calls
Some host platforms have size limits on tool call responses, so large files (PDFs, images, etc.) cannot be sent in a single response. Use an app-only tool with chunked responses to bypass these limits while keeping the data out of model context.
Server-side: Register an app-only tool that returns data in chunks with pagination metadata:
{@includeCode ./patterns.tsx#chunkedDataServer}
Client-side: Loop calling the tool until all chunks are received:
{@includeCode ./patterns.tsx#chunkedDataClient}
See examples/pdf-server/ for a full implementation of this pattern.
Giving errors back to model
Server-side: Tool handler validates inputs and returns { isError: true, content: [...] }. The model receives this error through the normal tool call response.
Client-side: If a runtime error occurs (e.g., API failure, permission denied, resource unavailable), use {@link app!App.updateModelContext updateModelContext} to inform the model:
{@includeCode ../src/app.examples.ts#App_updateModelContext_reportError}
Matching host styling (CSS variables, theme, and fonts)
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 orlight-dark()function for theme-aware styles - Fonts — Use
var(--font-sans)orvar(--font-mono)with fallbacks (e.g.,font-family: var(--font-sans, system-ui, sans-serif))
Vanilla JS:
{@includeCode ./patterns.tsx#hostStylingVanillaJs}
React:
{@includeCode ./patterns.tsx#hostStylingReact}
See examples/basic-server-vanillajs/ and examples/basic-server-react/ for full implementations of this pattern.
Entering / Exiting fullscreen
Toggle fullscreen mode by calling {@link app!App.requestDisplayMode requestDisplayMode}:
{@includeCode ../src/app.examples.ts#App_requestDisplayMode_toggle}
Listen for display mode changes via {@link app!App.onhostcontextchanged onhostcontextchanged} to update your UI:
{@includeCode ../src/app.examples.ts#App_onhostcontextchanged_respondToDisplayMode}
See examples/shadertoy-server/ for a full implementation of this pattern.
Passing contextual information from the App to the Model
Use {@link app!App.updateModelContext updateModelContext} to keep the model informed about what the user is viewing or interacting with. Structure the content with YAML frontmatter for easy parsing:
{@includeCode ../src/app.examples.ts#App_updateModelContext_appState}
See examples/map-server/ for a full implementation of this pattern.
Sending large follow-up messages
When you need to send more data than fits in a message, use {@link app!App.updateModelContext updateModelContext} to set the context first, then {@link app!App.sendMessage sendMessage} with a brief prompt to trigger a response:
{@includeCode ../src/app.examples.ts#App_sendMessage_withLargeContext}
See examples/transcript-server/ for a full implementation of this pattern.
Persisting widget state
To persist widget state across conversation reloads (e.g., current page in a PDF viewer, camera position in a map), use localStorage with a stable identifier provided by the server.
Server-side: Tool handler generates a unique widgetUUID and returns it in CallToolResult._meta.widgetUUID:
{@includeCode ./patterns.tsx#persistDataServer}
Client-side: Receive the UUID in {@link app!App.ontoolresult ontoolresult} and use it as the storage key:
{@includeCode ./patterns.tsx#persistData}
See examples/map-server/ for a full implementation of this pattern.
Pausing computation-heavy widgets when out of view
Widgets with animations, WebGL rendering, or polling can consume significant CPU/GPU even when scrolled out of view. Use IntersectionObserver to pause expensive operations when the widget isn't visible:
{@includeCode ./patterns.tsx#visibilityBasedPause}
See examples/shadertoy-server/ for a full implementation of this pattern.
Lowering perceived latency
Use {@link app!App.ontoolinputpartial ontoolinputpartial} to receive streaming tool arguments as they arrive, allowing you to show a loading preview before the complete input is available.
{@includeCode ../src/app.examples.ts#App_ontoolinputpartial_progressiveRendering}
Important
Partial arguments are "healed" JSON — the host closes unclosed brackets/braces to produce valid JSON. This means objects may be incomplete (e.g., the last item in an array may be truncated). Don't rely on partial data for critical operations; use it only for preview UI.
See examples/threejs-server/ for a full implementation of this pattern.