Skip to main content
MCP Apps SDK If you already have an MCP tool that returns useful data, you do not need to rewrite it. Add a UI resource, link the tool to that resource, and keep the tool result readable for normal MCP clients. This guide shows the smallest useful conversion path.

Before You Start

Start with a working MCP tool:
The tool already has a name, description, input schema, annotations, and a model-readable result. The MCP App version keeps those pieces.

1. Choose the UI Boundary

Add a View only when the user benefits from seeing or changing data inside the conversation. Keep the first tool call model-visible. Add app-only helper tools later for UI-only interactions.

2. Define the Data Contract

The View should render structuredContent, not scrape text from content. Add an outputSchema that matches the object your UI consumes:
Use content for a short readable summary and structuredContent for model-safe UI data:
Do not move all data into _meta. Hosts and Views can read _meta, but the model cannot rely on it for facts. Also do not treat structuredContent as private; the model may read it.

3. Register an HTML Resource

MCP Apps render HTML resources with the text/html;profile=mcp-app MIME type. The resource URI should use ui:// and should be stable.
Declare every origin the View needs. Use connectDomains for fetch, XHR, and WebSockets. Use resourceDomains for scripts, styles, images, fonts, and media. If the View embeds another iframe, add that origin to frameDomains. Switch the UI-capable branch from server.registerTool() to registerAppTool() and add _meta.ui.resourceUri.
The resourceUri string must match the registered resource URI exactly. Hosts use that value to read the HTML, render the iframe, and deliver the tool input and result to the View.

5. Keep a Text Fallback

MCP Apps are optional. Check host capabilities before registering UI metadata, then register a normal text tool when the host does not support MCP Apps.
Use the same tool name in both branches. Only one branch is registered for the connection, so callers do not need separate tool names.

6. Add App-only Helper Tools

If the View needs refresh, pagination, autosave, or form submit, add helper tools with visibility: ['app']. These tools are callable from the View through callServerTool(), but hosts must hide them from the model.
Omit resourceUri for helper tools that only return data to an already-rendered View. Add resourceUri only when the helper itself should be associated with a View.

7. Test the Contract

Before testing in a real host, verify the protocol shape locally:
Check these items:
  • tools/list includes _meta.ui.resourceUri on the UI-launching tool.
  • resources/read returns mimeType: "text/html;profile=mcp-app".
  • The resource URI and _meta.ui.resourceUri match exactly.
  • The tool returns both content and structuredContent.
  • The outputSchema matches structuredContent.
  • structuredContent contains only data the model may see.
  • App-only helpers use visibility: ["app"].
  • The iframe can load every external origin declared in _meta.ui.csp.
For automated checks, use the MCP testing fixtures to call the tool and render the View in the inspector.

Build an MCP App Server

See the complete server-side example with capability detection.

Tool and Resource Contract

Check each field that connects tools, resources, and results.

Tool _meta

Link tools to Views and set model or app visibility.

Resource _meta

Configure CSP, permissions, stable domains, and borders.