This server example uses the split MCP SDK 2.0 packages and
z.object() schemas used by current
sunpeak releases. See the SDK 2.0 migration guide when updating a
1.x server.- It detects whether the connecting host supports MCP Apps.
- It registers
ui://HTML resources with thetext/html;profile=mcp-appMIME type. - It links tools to those resources with
_meta.ui.resourceUri.
Server Checklist
Before you test in a host, confirm these items:- The server checks
getUiCapability(clientCapabilities)before adding UI metadata. - Every UI resource URI starts with
ui://. - Every UI resource returns
mimeType: RESOURCE_MIME_TYPE. - Each UI tool points at an existing resource with
_meta.ui.resourceUri. - Model-visible tools include MCP
annotationsfor read-only, destructive, idempotent, and external-system behavior. - Tool results include a short
contentsummary for the model and text-only clients. - Tool results put model-safe UI data in
structuredContent. - The resource declares every external origin it needs in
_meta.ui.csp. - UI-only actions use
visibility: ["app"]so they do not appear in the model tool list.
Complete Example
Why the Fallback Matters
MCP Apps is negotiated through the MCP extensions capability. A server should not assume every host can render Views. If the host does not advertise support forRESOURCE_MIME_TYPE, register a normal MCP tool that returns useful text.
The fallback tool can use the same name as the UI tool because only one branch is registered for a connection.
Resource and Tool Links
Theui:// resource is a template. The tool result is data. Keeping those separate lets hosts inspect, cache, and prefetch HTML before the tool runs.
_meta.ui.resourceUri, it cannot render the View.
content vs structuredContent
Use both fields in UI tool results:
contentis the readable summary. Keep it short and useful for the model and text-only clients.structuredContentis typed data the model may read and your View renders._metais for component-only data the model should not see.
content. It makes the model context harder to use and gives the View less predictable data.
For field-by-field guidance, see content vs structuredContent vs _meta.
Tool Annotations
Use MCPannotations alongside MCP Apps _meta.ui:
annotationstells the host whether the tool is read-only, destructive, idempotent, or reaches outside your backend._meta.uitells the host whether the tool renders a View and whether the model, the View, or both can call it.
search-orders tool above, readOnlyHint: true is correct because the tool only reads order data. If you add a cancel-order or submit-payment tool, set destructiveHint: true and require server-side authorization even if the host shows a confirmation.
See Tool Annotations for examples.
CSP and External Assets
MCP App HTML runs in a sandboxed iframe. By default, hosts block undeclared network and asset origins. Declare what the View needs:connectDomains for fetch, XHR, and WebSockets. Use resourceDomains for scripts, styles, images, fonts, and media. See Resource _meta for the full CSP reference.
App-only Tools
Use app-only tools for user actions that happen inside the View and do not need to appear in the model’s tool list:callServerTool(), but the host must hide it from the model. This is a good fit for refresh buttons, pagination, saving UI edits, and polling.