Skip to main content
An MCP App is interactive UI that your MCP server renders inside a Claude conversation, such as an interactive chart or map. In this quickstart you give the roll_dice tool from Build your first MCP server for Claude a small UI that draws each die and has a Roll again button, by adding about 50 lines to the same server.mjs and no build tooling. At the end your server advertises the UI the way an MCP Apps host expects, and you’ve checked that from the command line. This quickstart is for developers who finished the first quickstart and have its server on their machine. It stops at what you can verify locally: seeing the UI render needs the server hosted where Claude can reach it, which the last section covers.

Install the MCP Apps SDK

The MCP Apps SDK has two halves: server helpers that attach UI metadata to your tools and resources, and a small browser client that the UI itself loads to talk to the host. One package provides both. In your terminal, from the mcp-quickstart folder, install the 1.x line of the package, which is the one that pairs with the 1.x MCP SDK you already have:
Run npm ls --depth=0 to confirm. The list now has three packages:
This page was verified with @modelcontextprotocol/ext-apps@1.7.5.

Add the UI to the tool

On the server, an MCP App is a resource with a ui:// URI whose content is the HTML to render, plus a _meta.ui.resourceUri field on the tool that points at that resource. When a host that supports MCP Apps calls the tool, it reads that field, fetches the resource, and renders the HTML in a sandboxed frame. Make these edits to server.mjs in order. If you’d rather paste the whole file, it’s in The complete file at the end of this section.
1

Import the server helpers

Add one import under the existing SDK imports at the top of server.mjs. registerAppTool and registerAppResource wrap the SDK’s own registerTool and registerResource and fill in the UI metadata for you:
server.mjs
2

Write the UI as an HTML string

Below the imports and above function buildServer(), add the resource URI and the HTML the host will render. The HTML is ordinary markup plus one <script type="module">. The highlighted lines are the ones that make it an MCP App: the script loads the SDK’s browser client, App, from a CDN copy of the version you installed, registers handlers for the tool’s input and result, wires the button to call the tool again through the host, and then connects:
server.mjs
If you change the installed ext-apps version, change the version in the unpkg.com URL to match.
3

Link the tool to the UI

Inside buildServer(), replace the server.registerTool( call with registerAppTool(server, and make two additions, both highlighted: a _meta.ui.resourceUri entry that names the UI resource, and a structuredContent object in the result. The UI reads structuredContent to draw the dice, and the content text stays for hosts that don’t render UI:
server.mjs
4

Register the UI resource

Still inside buildServer(), before return server;, register the resource that serves the HTML. registerAppResource sets the MIME type an MCP App resource must have, text/html;profile=mcp-app, which the SDK exports as RESOURCE_MIME_TYPE. The highlighted csp line tells the host’s sandbox to allow scripts from unpkg.com, because by default the frame only runs scripts that are inline or from its own origin, and the App import would be blocked. For production, serve the client script from your own origin, or bundle it into the HTML, and list only that origin in resourceDomains:
server.mjs
Run node --check server.mjs to catch typos. It prints nothing when the file parses.

The complete file

For reference, this is server.mjs with every edit applied. Everything below buildServer() is unchanged from the first quickstart.
server.mjs

Check that the server advertises the UI

A host discovers the UI from the _meta on the tool in tools/list and loads it from resources/read, and your server now returns both. You can see both with curl before any host is involved.
1

Restart the server

In the terminal where the server runs, press Ctrl+C to stop it, then start it again from the mcp-quickstart folder so it picks up your edits:
It prints quickstart-server listening on http://localhost:3000/mcp as before.
2

Check the tool points at the UI

In your second terminal, list the tools again:
The roll_dice entry now ends with a _meta object naming the resource. The helper also writes the same URI under the legacy flat key ui/resourceUri. The rest of the entry is shortened to ... here:
3

Read the UI resource

Fetch the resource the way a host does, by its ui:// URI:
The reply carries the MCP App MIME type and your HTML as text, shortened here after the opening tags:
4

Call the tool and see the structured result

Call roll_dice once more:
The result has both the text block and the structuredContent object the UI draws from:
Claude Code still works with the server after these edits, as a text-only client: claude mcp list shows quickstart connected, and the same claude -p prompt from the first quickstart calls the tool and reports the rolls. One difference shows up in the stream-json output: because the tool now returns structuredContent, the tool_result Claude receives is that JSON object as a string, such as {"sides":20,"count":3,"rolls":[7,11,12],"total":30}, rather than your content text.

See the UI render in Claude

Rendering the UI takes a host that supports MCP Apps, such as the Claude desktop app. Claude Code calls the tool as text and doesn’t render the UI. When you add a server by its URL on claude.ai or in the desktop app, Claude connects to it from Anthropic’s infrastructure over the internet, so a localhost address isn’t reachable that way. To see an MCP App render, take one of these routes:
  • Host this server and add it as a custom connector: give the server a public HTTPS URL, or expose it through a tunnel while you iterate as Test a local server describes. Then add it by URL, turn the connector on in a chat, and ask Claude to roll dice. If the tool runs but no UI appears, Troubleshoot MCP Apps lists what to check
  • Run a finished example locally in Claude Desktop: Try an example MCP App in Claude Desktop connects one of the SDK’s example servers through the desktop app’s configuration file, with nothing to host
Before you host this server for other people, add authentication, because as written it lets anyone who can reach it call its tools. Authentication for connectors covers the options.

Next steps

  • Design guidelines: choose a display mode and use Claude’s style variables so the UI looks native in the conversation
  • Set ui.domain for Claude: give the UI a stable sandbox origin, which it needs if it runs its own OAuth flow
  • Troubleshoot MCP Apps: open developer tools in Claude and fix a UI that doesn’t appear
  • Submit a connector: list the server in the directory, including the screenshots an MCP App listing needs