docs: fill gaps surfaced by developer feedback#563
Conversation
Adds guidance for recurring questions and pitfalls that came up while building Apps against the SDK: patterns.md: - Model vs App data visibility (content/structuredContent/_meta split) - Controlling App height, including the autoResize + 100vh feedback loop - Touch device support (touch-action, horizontal overflow) - localStorage key namespacing across shared sandbox origin - Sharing a single ui:// resource across multiple tools - Conditionally showing UI (two-tool workaround) - Opening external links via app.openLink - color-scheme CSS gotcha that breaks iframe transparency overview.md: - Resource versioning/caching note — template and data may be from different code versions New pages: - design-guidelines.md - troubleshooting.md typedoc.config.mjs wired up to include the two new pages.
📖 Docs Preview Deployed
Includes drafts and future-dated posts. All pages served with |
@modelcontextprotocol/ext-apps
@modelcontextprotocol/server-basic-preact
@modelcontextprotocol/server-basic-react
@modelcontextprotocol/server-basic-solid
@modelcontextprotocol/server-basic-svelte
@modelcontextprotocol/server-basic-vanillajs
@modelcontextprotocol/server-basic-vue
@modelcontextprotocol/server-budget-allocator
@modelcontextprotocol/server-cohort-heatmap
@modelcontextprotocol/server-customer-segmentation
@modelcontextprotocol/server-debug
@modelcontextprotocol/server-map
@modelcontextprotocol/server-pdf
@modelcontextprotocol/server-scenario-modeler
@modelcontextprotocol/server-shadertoy
@modelcontextprotocol/server-sheet-music
@modelcontextprotocol/server-system-monitor
@modelcontextprotocol/server-threejs
@modelcontextprotocol/server-transcript
@modelcontextprotocol/server-video-resource
@modelcontextprotocol/server-wiki-explorer
commit: |
|
|
||
| An MCP App answers one question or supports one task. Avoid building a full dashboard with tabs, sidebars, and settings panels. | ||
|
|
||
| - Inline mode should fit within roughly one viewport of scroll. Content that is significantly taller than the chat viewport belongs in fullscreen mode, or should be trimmed. |
There was a problem hiding this comment.
Not sure about this one. I think it's fine to have longer apps, as long as they don't have nested scrolling in inline mode (big no-no).
| An MCP App answers one question or supports one task. Avoid building a full dashboard with tabs, sidebars, and settings panels. | ||
|
|
||
| - Inline mode should fit within roughly one viewport of scroll. Content that is significantly taller than the chat viewport belongs in fullscreen mode, or should be trimmed. | ||
| - Limit inline mode to one primary action. A "Confirm" button is appropriate; a toolbar with eight icons is not. |
There was a problem hiding this comment.
Maybe explain the intent behind this rather than the arbitrary limitation to achieve it? (if it's because inline chats may have limited space and a toolbar would be a bit cramped, maybe emphasize the fact the app layout should be progressive / cater to small widths, and be mobile friendly?
|
|
||
| - Inline mode should fit within roughly one viewport of scroll. Content that is significantly taller than the chat viewport belongs in fullscreen mode, or should be trimmed. | ||
| - Limit inline mode to one primary action. A "Confirm" button is appropriate; a toolbar with eight icons is not. | ||
| - Let the conversation handle navigation. Rather than adding a search box inside the App, let the user ask a follow-up question that re-invokes the tool with new arguments. |
There was a problem hiding this comment.
Counter example: Ctrl+F in PDF-viewer. And its interact pattern or upcoming #72 primitive to invoke search programmatically on an existing component.
I'd focus on discouraging multipage navigation in inline mode (might be okay in fullscreen).
|
|
||
| ## Host UI imitation | ||
|
|
||
| Your App must not resemble the surrounding chat client. Do not render: |
There was a problem hiding this comment.
Slightly at odds w/ encouraging apps to use native styles, font and colors. Maybe reformulate as avoid confusion between the app and the surrounding chat.
|
|
||
| ## Display modes | ||
|
|
||
| Design for inline mode first. It is the default, and it is narrow (often the width of a chat message) and height-constrained. |
There was a problem hiding this comment.
inline isn't height-constrained (or we constrain it to like 6000px, on purpose).
| > [!WARNING] | ||
| > Do not combine `autoResize: true` with `height: 100vh` or `100%` on the root element. The SDK reports the document height, the host grows the iframe to match, the document sees a taller viewport and grows again. This loops until the host's maximum height cap. | ||
|
|
||
| The React `useApp` hook always creates the App with `autoResize: true`. For fixed or host-driven height, construct the `App` manually or use the `useAutoResize` hook with a specific element. |
There was a problem hiding this comment.
The React
useApphook always creates the App with `autoResize: true
we should fix this btw 🙈
|
|
||
| ## Conditionally showing UI | ||
|
|
||
| The tool-to-resource binding is declared at registration time. A tool either has a `_meta.ui.resourceUri` or it does not; the server cannot decide per-call whether to render UI. |
There was a problem hiding this comment.
the server cannot decide
Stateful servers can decide to register different tools based on the initialize capabilities if the MCP Apps extension is declared (cf. getUiCapability helper in /server)
|
|
||
| If the decision must be made server-side (for example, showing UI only when the result set exceeds a threshold), the workaround is to always attach the UI resource and have the App render a minimal collapsed placeholder when there is nothing to show. Keep the placeholder small to avoid adding visual noise to the conversation. | ||
|
|
||
| ## Opening external links |
There was a problem hiding this comment.
maybe also mention downloadFile, and capability fencing for both
|
|
||
| # Troubleshooting | ||
|
|
||
| ## Blank iframe |
There was a problem hiding this comment.
failure to call connect is my own most frequent cause for this (esp. if iframe has zero size)
|
|
||
| MCP Apps are portable only if they use the SDK exclusively. Common portability mistakes: | ||
|
|
||
| - **Host-specific globals.** Do not reference `window.openai`, `window.claude`, or any other host-injected object. Use the `App` class from this SDK, which speaks the standard protocol to any compliant host. |
There was a problem hiding this comment.
not sure about mentioning hypothetical window.claude?
…shooting - design-guidelines: reframe scope bullets around no-nested-scroll / narrow-width layout / no multi-page nav; clarify host-UI imitation vs native styling tension; drop "height-constrained" inline claim; point to containerDimensions via onhostcontextchanged; note App mounts before tool inputs are sent - patterns: correct structuredContent model-visibility (shown iff content empty); rework touch-action guidance so inline never blocks vertical scroll; add sendSizeChanged to fixed-height recipe; document getUiCapability for per-client tool registration; add downloadFile + capability fencing alongside openLink - troubleshooting: lead blank-iframe list with missing connect(); drop hypothetical window.claude reference
Accuracy:
- patterns: drop incorrect useAutoResize-with-element advice; useApp simply
doesn't expose autoResize, so construct App manually
- patterns: add full host-driven height snippet using addEventListener and
the containerDimensions union (with in-guards), plus a strategy decision table
- patterns/overview: align structuredContent visibility wording (model sees it
only when content is empty) across both pages
- design-guidelines/patterns/troubleshooting: switch new content from deprecated
on* setters to addEventListener; retitle troubleshooting section to event names
- design-guidelines: describe containerDimensions as fixed-or-max bounds, not
plain {width,height}
Gaps:
- design-guidelines: cover toolcancelled as a terminal loading state; mention
requestTeardown alongside the no-close-button guidance
- troubleshooting: surface basic-host repro tip at top of Blank iframe
Clarity/examples:
- patterns: openLink/downloadFile snippet now gates control rendering, not the
call site; sharing-one-UI snippet guards isError before casting; fixed-height
snippet uses bare app.connect(); dedupe 100vh warning
- design-guidelines: reword Scope intro to target application shells (resolve
tabs contradiction); soften inline-height claim and cross-link to patterns
Summary
Adds documentation for recurring questions and pitfalls that surfaced while building real Apps:
patterns.md— new sections on model-vs-App data visibility, height control (and theautoResize+100vhloop), touch device support,localStoragenamespacing, sharing oneui://resource across tools, conditionally showing UI,openLink, and thecolor-schemeiframe transparency gotchaoverview.md— note on resource versioning/caching (template and tool result may come from different code versions)design-guidelines.mdandtroubleshooting.md, wired intotypedoc.config.mjsTest plan
npm exec typedoc -- --treatValidationWarningsAsErrors --emit nonepassesnpm run buildgenerates docs without broken{@link}referencespatterns.mdresolve