Journalguides

Field guide / 6 min read

MCP Apps: when an AI tool needs a real interface

A practical guide to MCP Apps, including UI resources, sandboxed rendering, progressive fallback, security boundaries, and when an interface is worth building.

Aug 22, 20266 min readBy ISH Team
MCP Apps: when an AI tool needs a real interface
Advertisement

MCP Apps: when an AI tool needs a real interface

Most AI tools still answer with text. That works for a status check or a short lookup. It gets awkward when someone needs to sort a table, adjust several settings, inspect a document, or watch live data. Every click-shaped action turns into another prompt.

MCP Apps gives Model Context Protocol tools another kind of output: an interactive interface rendered inside the conversation. A tool can return its usual text or structured data while pointing the host to an HTML view. The host loads that view in a sandboxed iframe, passes in the tool result, and lets the interface communicate with the host through a defined protocol.

Direct manipulation is useful when it cuts out a slow conversational loop. It does not belong on every MCP tool.

What changes when a tool can return UI

A regular MCP tool exposes a name, an input schema, and a handler. The model chooses the tool, supplies arguments, and receives a result. An MCP App keeps that flow and adds a UI resource.

The tool metadata contains a _meta.ui.resourceUri value such as ui://sales/dashboard.html. The MCP server registers that resource too and serves it with the MCP Apps HTML media type. When a compatible host calls the tool, it can fetch the resource and render the bundled HTML, CSS, and JavaScript beside the conversation.

The official extension is stable, but it sits on top of core MCP, so developers still need to check client support. The current MCP overview lists Claude, Claude Desktop, VS Code GitHub Copilot, Microsoft 365 Copilot, Goose, Postman, and MCPJam among the supported clients. A server should return useful text or structured content when a host does not support the extension.

That fallback matters when the same server runs across several clients. The interface should improve the result without becoming the only way to understand it.

How a request moves through the system

The request flow has four parts:

  1. A tool declares a ui:// resource in _meta.ui.resourceUri.
  2. The host calls the tool and fetches the matching UI resource.
  3. It renders the resource in a sandboxed iframe.
  4. The view and host exchange JSON-RPC messages through postMessage.

The view can receive tool arguments and results, call permitted tools on the same server, request resources, send a follow-up message, or update the model's context. A filter change or selected row can therefore remain meaningful to the model without making the user describe the selection in prose.

Because the wire format uses web standards, the SDK is optional. In practice, @modelcontextprotocol/ext-apps handles connection setup and host communication. The package also includes server helpers, React hooks, and an App Bridge for host developers. The official repository has starter projects for React, Vue, Svelte, Preact, Solid, and vanilla JavaScript.

A minimal server shape

The official build guide starts by installing the core SDK and the extension:

npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk

On the server, register a tool and a resource that share the same URI:

import {
  registerAppResource,
  registerAppTool,
  RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";

const resourceUri = "ui://deploy/review.html";

registerAppTool(
  server,
  "review-deployment",
  {
    description: "Review deployment settings before release.",
    inputSchema: {},
    _meta: { ui: { resourceUri } },
  },
  async () => ({
    content: [{ type: "text", text: "Deployment settings are ready for review." }],
  }),
);

registerAppResource(
  server,
  resourceUri,
  resourceUri,
  { mimeType: RESOURCE_MIME_TYPE },
  async () => ({
    contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }],
  }),
);

The interface code is omitted here so the contract is easier to see: both registrations use the same resource URI. For production, bundle the view, validate the tool result before rendering it, and keep the text response useful for hosts that cannot display the app.

When an interface earns the extra code

Build an MCP App when people need to manipulate or inspect something that text represents poorly.

A table is a good candidate if readers need sorting, filtering, selection, or drill-down. A deployment configuration works better as a form when fields depend on earlier choices. A PDF review benefits from visible pages and annotations. Live logs and metrics need updates that should not consume a fresh conversational turn each time.

Text remains the better choice for a single confirmation, a short answer, or a result the model can summarize without losing useful detail. Adding a miniature web app to every tool means more code, more browser behavior to test, and a larger security surface.

Count the prompts required after the first result. If users repeatedly ask to re-sort, narrow, page through, preview, or select, an interface may remove that friction. If the job ends after one answer, keep the tool simple.

The sandbox does not replace authorization

According to the MCP Apps specification, web hosts must place views behind a sandbox proxy. The host controls capabilities and mediates messages between the iframe and the wider application. This is a useful boundary, but it does not make arbitrary UI trustworthy.

Resource metadata can declare a content security policy and request permissions such as camera or microphone access. Hosts decide whether to grant them. Tools can also set visibility so an operation is callable by the model, by the app, or by both. App-only tools stay out of the model's tool list and cannot be called across servers.

Server authors still need to treat every UI request as untrusted input. Validate arguments, apply authorization at the tool handler, avoid putting secrets in the HTML bundle, and require confirmation for consequential actions. A polished button does not make the operation behind it safe.

Host developers need to enforce the sandbox, restrict navigation and external origins, show permission requests clearly, and log tool calls initiated by the UI. VS Code's MCP Apps implementation is a useful reference because it shows these interfaces inside an existing agent surface instead of a standalone demo page.

Build for capability differences

Start with one workflow whose text version is plainly clumsy, and keep the tool result complete enough to work without the interface. Add the view as a progressive layer.

Test a host with full MCP Apps support, one that ignores the UI metadata, and one where the resource load fails or is blocked. The user should receive a useful result in every case. Then test keyboard navigation, narrow layouts, dark and light host themes, slow tool calls, repeated tool results, and denied permissions.

This split also works when an AI service has both chat and API surfaces. The API can keep returning portable structured data while compatible chat hosts render a richer view. Readers who want to compare the underlying model behavior can use ISH chat, while developers can keep tool execution and usage visible through the ISH API dashboard.

Treat MCP Apps as an escape hatch from text. Build the smallest interface that removes a real interaction bottleneck, keep the tool result portable, and leave enforcement of the view boundary to the host.

#mcp#mcp-apps#developer-tools#ai-agents#user-interface
Advertisement

Keep reading

Related stories

Browse the archive