Skip to content
61 changes: 61 additions & 0 deletions docs/design-guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
title: Design Guidelines
group: Getting Started
description: UX guidance for MCP Apps, covering host-provided chrome, content sizing, and visual consistency with the surrounding chat.
---

# Design Guidelines

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.

## Host chrome

Hosts render a frame around your App that typically includes:

- A title bar showing the App name (from tool or server metadata)
- Display-mode controls (expand, collapse, close)
- Attribution indicating which connector or server provided the App

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 {@link app!App.requestTeardown `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.

## Scope

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.

- Inline content can be tall, but it must scroll with the surrounding conversation. Do not introduce nested scroll containers in inline mode; a scrollable region inside a scrollable chat is difficult to use on every input device. See [Supporting touch devices](./patterns.md#supporting-touch-devices).
- Design the inline layout to remain usable at narrow widths. Chat columns can be as narrow as a mobile message bubble, so dense toolbars and side-by-side panels should collapse or move to fullscreen mode rather than overflow.
- Avoid multi-page navigation (routes, wizards, tab stacks) in inline mode. The conversation already provides history and back-navigation. In-App search or filtering over the current data set is fine; navigating to a different document or view is better handled by a follow-up tool call, or reserved for fullscreen mode.

## Host UI imitation

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:

- Chat bubbles or message threads
- Anything that resembles the host's text input or send button
- System notifications or permission dialogs

A user must never mistake App-rendered surfaces for host controls. Most hosts prohibit these patterns in their submission guidelines.

## Host styling

Hosts provide CSS custom properties for colors, fonts, spacing, and border radius (see [Adapting to host context](./patterns.md#adapting-to-host-context-theme-styling-fonts-and-safe-areas)). 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.

## Display modes

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](./patterns.md#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 {@link app!App.addEventListener `app.addEventListener`} and read `containerDimensions` from the event payload, which reports either fixed `width`/`height` or `maxWidth`/`maxHeight` bounds depending on the host.

## Loading and empty states

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.
4 changes: 3 additions & 1 deletion docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ sequenceDiagram

1. **Discovery** — The Host learns about tools and their UI resources when connecting to the server.
2. **Initialization** — When a UI tool is called, the Host renders the iframe. The View sends `ui/initialize` and receives host context (theme, capabilities, container dimensions). This handshake ensures the View is ready before receiving data.
3. **Data delivery** — The Host sends tool arguments and, once available, tool results to the View. Results include both `content` (text for the model's context) and optionally `structuredContent` (data optimized for UI rendering). This separation lets servers provide rich data to the UI without bloating the model's context.
3. **Data delivery** — The Host sends tool arguments and, once available, tool results to the View. Results include both `content` (text for the model's context) and optionally `structuredContent` (data optimized for UI rendering, passed to the model only when `content` is empty). This separation lets servers provide rich data to the UI without bloating the model's context.
4. **Interactive phase** — The user interacts with the View. The View can call tools, send messages, or update context.
5. **Teardown** — Before unmounting, the Host notifies the View so it can save state or release resources.

Expand All @@ -110,6 +110,8 @@ Resources are declared upfront, during tool registration. This design enables:
- **Separation of concerns** — Templates (presentation) are separate from tool results (data)
- **Review** — Hosts can inspect UI templates during connection setup

**Versioning and caching.** Resource caching behavior is host-defined. A host may re-fetch the `ui://` resource on each render, cache it for the session, or persist it alongside the conversation. When a user revisits an old conversation, the host may run the current template code against the original tool result, or it may replay a snapshot of both from the time of the original tool call. Design the App to tolerate older `structuredContent` shapes: handle unknown fields gracefully and do not assume the template and the data were produced by the same code version.

See the [UI Resource Format](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx#ui-resource-format) section of the specification for the full schema.

## Tool-UI Linkage
Expand Down
Loading
Loading