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.
- If you haven’t built the quickstart server yet, start with Build your first MCP server for Claude
- If you want to see finished MCP Apps running in Claude first, see Try an example MCP App in Claude Desktop
- If you’re adding UI to your own production server, see Build your own MCP App for the SDK documentation and full examples
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 themcp-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:
npm ls --depth=0 to confirm. The list now has three packages:
@modelcontextprotocol/ext-apps@1.7.5.
Add the UI to the tool
On the server, an MCP App is a resource with aui:// 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 If you change the installed
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
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
node --check server.mjs to catch typos. It prints nothing when the file parses.
The complete file
For reference, this isserver.mjs with every edit applied. Everything below buildServer() is unchanged from the first quickstart.
server.mjs with the MCP App added
server.mjs with the MCP App added
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 It prints
Ctrl+C to stop it, then start it again from the mcp-quickstart folder so it picks up your edits: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 The reply carries the MCP App MIME type and your HTML as
ui:// URI:text, shortened here after the opening tags:4
Call the tool and see the structured result
Call The result has both the text block and the
roll_dice once more:structuredContent object the UI draws from: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 alocalhost 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
Next steps
- Design guidelines: choose a display mode and use Claude’s style variables so the UI looks native in the conversation
- Set
ui.domainfor 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