|
1 | 1 | --- |
2 | 2 | title: Design Guidelines |
3 | 3 | group: Getting Started |
4 | | -description: UX guidance for MCP Apps — what the host already provides, how to size your content, and how to stay visually consistent with the surrounding chat. |
| 4 | +description: UX guidance for MCP Apps, covering host-provided chrome, content sizing, and visual consistency with the surrounding chat. |
5 | 5 | --- |
6 | 6 |
|
7 | 7 | # Design Guidelines |
8 | 8 |
|
9 | | -MCP Apps live inside a conversation. They should feel like a natural part of the chat, not a separate application wedged into it. |
| 9 | +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. |
10 | 10 |
|
11 | | -## The host provides the chrome |
| 11 | +## Host chrome |
12 | 12 |
|
13 | | -Hosts typically render a frame around your App that includes: |
| 13 | +Hosts render a frame around your App that typically includes: |
14 | 14 |
|
15 | | -- A **title bar** showing your App's name (from the tool or server metadata) |
16 | | -- **Display-mode controls** (expand to fullscreen, collapse, close) |
17 | | -- **Attribution** (which connector/server the App came from) |
| 15 | +- A title bar showing the App name (from tool or server metadata) |
| 16 | +- Display-mode controls (expand, collapse, close) |
| 17 | +- Attribution indicating which connector or server provided the App |
18 | 18 |
|
19 | | -**Don't duplicate these.** Your App doesn't need its own close button, title header, or "powered by" footer. Start your layout with the actual content. |
| 19 | +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. |
20 | 20 |
|
21 | | -If you need a title _inside_ your content (e.g., "Q3 Revenue by Region" above a chart), that's fine — just don't put your App's brand name there. |
| 21 | +A title inside the content area (for example, "Q3 Revenue by Region" above a chart) is acceptable. The App's brand name is not. |
22 | 22 |
|
23 | | -## Keep it focused |
| 23 | +## Scope |
24 | 24 |
|
25 | | -An MCP App answers one question or supports one task. Resist the urge to build a full dashboard with tabs, sidebars, and settings panels. |
| 25 | +An MCP App answers one question or supports one task. Avoid building a full dashboard with tabs, sidebars, and settings panels. |
26 | 26 |
|
27 | | -Good heuristics: |
| 27 | +- 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. |
| 28 | +- Limit inline mode to one primary action. A "Confirm" button is appropriate; a toolbar with eight icons is not. |
| 29 | +- 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. |
28 | 30 |
|
29 | | -- **Inline mode should fit in roughly one screen of scroll.** If your content is much taller than the chat viewport, consider whether it belongs in fullscreen mode — or whether you're showing too much. |
30 | | -- **One primary action at most.** A "Confirm" button is fine. A toolbar with eight icons is probably too much for inline mode. |
31 | | -- **Let the conversation drive navigation.** Instead of building a search box inside your App, let the user ask a follow-up question and re-invoke the tool with new arguments. |
| 31 | +## Host UI imitation |
32 | 32 |
|
33 | | -## Don't replicate the host's UI |
| 33 | +Your App must not resemble the surrounding chat client. Do not render: |
34 | 34 |
|
35 | | -Your App must not look like the surrounding chat client. Specifically, avoid: |
| 35 | +- Chat bubbles or message threads |
| 36 | +- Anything that resembles the host's text input or send button |
| 37 | +- System notifications or permission dialogs |
36 | 38 |
|
37 | | -- Rendering fake chat bubbles or message threads |
38 | | -- Mimicking the host's input box or send button |
39 | | -- Showing fake system notifications or permission dialogs |
| 39 | +These patterns blur the line between host UI and App content, and most hosts prohibit them in their submission guidelines. |
40 | 40 |
|
41 | | -These patterns confuse users about what's real host UI versus App content, and most hosts prohibit them in their submission guidelines. |
| 41 | +## Host styling |
42 | 42 |
|
43 | | -## Use host styling where possible |
| 43 | +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. |
44 | 44 |
|
45 | | -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 makes your App feel native across light mode, dark mode, and different host themes. |
| 45 | +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. |
46 | 46 |
|
47 | | -You can bring your own brand colors for content (chart series, status badges), but let the host's variables drive backgrounds, text, and borders. Always provide fallback values so your App still renders reasonably on hosts that don't supply every variable. |
| 47 | +## Display modes |
48 | 48 |
|
49 | | -## Inline vs fullscreen layout |
| 49 | +Design for inline mode first. It is the default, and it is narrow (often the width of a chat message) and height-constrained. |
50 | 50 |
|
51 | | -Design for **inline first** — that's where your App appears by default. Inline mode is narrow (often the width of a chat message) and height-constrained. |
| 51 | +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. |
52 | 52 |
|
53 | | -Treat **fullscreen** as a progressive enhancement for Apps that benefit from more space (editors, maps, large datasets). Check `hostContext.availableDisplayModes` before showing a fullscreen toggle — not every host supports it. |
| 53 | +When the display mode changes, update your layout: remove edge border radius, expand to fill the viewport, and re-read `containerDimensions` from the updated host context. |
54 | 54 |
|
55 | | -When switching modes, remember to adjust your layout: remove border radius at the edges, expand to fill the viewport, and re-read `containerDimensions` from the updated host context. |
| 55 | +## Loading and empty states |
56 | 56 |
|
57 | | -## Handle the empty and loading states |
| 57 | +The App mounts before the tool result arrives. Between `ui/initialize` and `ontoolresult`, render a loading indicator such as a skeleton, spinner, or neutral background. A blank rectangle looks broken. |
58 | 58 |
|
59 | | -Your App mounts before the tool result arrives. Between `ui/initialize` and `ontoolresult`, show something — a skeleton, a spinner, or at minimum a neutral background. A blank white rectangle looks broken. |
60 | | - |
61 | | -Similarly, if your tool result can be empty (no search results, no items in cart), design a clear empty state rather than rendering nothing. |
| 59 | +If the tool result can be empty (no search results, empty cart), design an explicit empty state rather than rendering nothing. |
0 commit comments