An MCP App is part of a conversation. It should read as a continuation of the chat, not as a separate application embedded inside it.
Hosts render a frame around your App that typically includes:
Do not duplicate these elements. Your App does not need its own close button, header bar, or "powered by" footer. Begin the layout with content. If the App should dismiss itself after a task completes, call app.requestTeardown(); the host decides whether to honor the request.
A title inside the content area (for example, "Q3 Revenue by Region" above a chart) is acceptable. The App's brand name is not.
An MCP App answers one question or supports one task. Avoid building an application shell around it: global navigation, sidebars, and settings panels belong to the host, not the App.
Use host-provided styles so your App matches the surrounding theme, but keep the boundary between App content and host UI unambiguous. Do not render:
A user must never mistake App-rendered surfaces for host controls. Most hosts prohibit these patterns in their submission guidelines.
Hosts provide CSS custom properties for colors, fonts, spacing, and border radius (see Adapting to host context). Using them keeps your App consistent across light mode, dark mode, and different host themes.
Brand colors are appropriate for content elements such as chart series or status badges. Backgrounds, text, and borders should use host variables. Always provide fallback values so the App renders correctly on hosts that omit some variables.
Design for inline mode first. It is the default, and it is narrow (often the width of a chat message). Most hosts let inline height grow with content up to a high safety cap, but a host may also pin the iframe to a fixed height via containerDimensions; see Controlling App height.
Treat fullscreen as a progressive enhancement for Apps that benefit from more space: editors, maps, large datasets. Check hostContext.availableDisplayModes before rendering a fullscreen toggle, since not every host supports it.
When the display mode changes, update your layout: remove edge border radius and expand to fill the viewport. To size the App to the space the host provides, listen for hostcontextchanged via app.addEventListener and read containerDimensions from the event payload, which reports either fixed width/height or maxWidth/maxHeight bounds depending on the host.
The App mounts before the tool result arrives, and even before the tool inputs are sent. Render a loading indicator such as a skeleton, spinner, or neutral background between ui/initialize and the first terminal event. A blank rectangle looks broken.
The terminal events are toolresult (success or isError) and toolcancelled (user stopped the request). Handle both: an App that clears its loading state only on toolresult will spin indefinitely if the user cancels.
If the tool result can be empty (no search results, empty cart), design an explicit empty state rather than rendering nothing.