Independent AI intelligence Two editions daily · ET
FervorAI

Analysis · July 25, 2026 · concept

MCP AppsModel Context ProtocolClaudeChatGPTmcpmcp-securityagent-infrastructureprivacy

MCP Apps Let a Tool Server Draw the Interface You Click

Server-rendered UI becomes an official MCP extension on July 28. Here is how it works, and why half its security model sits on your client's side of the wire.

An MCP server can hand your AI client raw HTML and JavaScript, and your client will render it. Not a description of a chart. Not structured data the client draws however it likes. Actual markup, written by whoever wrote the server, running inside Claude or ChatGPT or VS Code, drawing the buttons you then press.

The MCP maintainers say this plainly in the security section of the announcement: "Running UI from MCP servers means running code you didn't write within your MCP host."

That capability shipped in January. What happens on July 28 is that the Extensions framework becomes first-class in the specification and MCP Apps lands as one of two official extensions alongside Tasks. Extensions get reverse-DNS identifiers, negotiated capability maps, their own repositories with delegated maintainers, and independent version numbers. Translated: server authors can now assume server-rendered UI is a normal part of the protocol rather than a thing a few clients happen to support.

The party asking for permission now owns the pixels

Here is my position, and it is not "don't use this." MCP Apps solves a real problem and I want it. Text is a terrible interface for filtering a table, picking a color, or reviewing a contract clause by clause. If a tool returns four hundred rows, every follow-up question ("sort by revenue," "just last week," "what's row 47") costs a round trip through the model. A sortable table costs zero.

The problem is narrower and sharper than "untrusted code is scary." It is this: the entity that wants your approval to do something is now the entity that draws the screen where you approve it. And in the same specification release, MCP is rebuilding how servers ask you questions mid-call. Multi Round-Trip Requests let a server return an InputRequiredResult with a message like "Delete 3 files?" and a schema for your answer, then resume when you reply. Put those two changes next to each other and the consent prompt and the code drawing the consent prompt can both come from the same server.

The spec's own answer to this is good, and it is entirely optional.

The mechanism, in four moving parts

A tool declares UI by adding one field to its definition:

{
  name: "visualize_data",
  description: "Visualize data as an interactive chart",
  inputSchema: { /* ... */ },
  _meta: {
    ui: {
      resourceUri: "ui://charts/interactive"
    }
  }
}

That ui:// URI points at a server-side resource holding bundled HTML and JavaScript. The host fetches it, renders it in a sandboxed iframe, and opens a bidirectional JSON-RPC channel over postMessage. Inside the iframe, the app uses the @modelcontextprotocol/ext-apps package:

import { App } from "@modelcontextprotocol/ext-apps";

const app = new App();
await app.connect();

app.ontoolresult = (result) => { renderChart(result.data); };

const response = await app.callServerTool({
  name: "fetch_details",
  arguments: { id: "123" },
});

await app.updateModelContext({
  content: [{ type: "text", text: "User selected option B" }],
});

Read those last two calls again. callServerTool lets the rendered UI invoke tools on its own. updateModelContext lets it write text into the model's context. A widget you are looking at can put words in front of the model without the model having asked for them, and without you typing anything.

Now the mitigations, quoted from the maintainers' security model with the modal verbs left in:

  • Iframe sandboxing: "All UI content runs in sandboxed iframes with restricted permissions."
  • Pre-declared templates: "Hosts can review HTML content before rendering."
  • Auditable messages: "All UI-to-host communication goes through loggable JSON-RPC."
  • User consent: "Hosts can require explicit approval for UI-initiated tool calls."

Two of those four are guarantees. Two are permissions granted to your client. Sandboxing and loggability are properties of the design. Reviewing the HTML and gating UI-initiated tool calls are things a host is allowed to do, which means a host is also allowed not to.

One genuine improvement lands in the same release and deserves credit. SEP-2260 makes it a requirement, not a recommendation, that servers may only issue requests back to the client while actively processing a client request. The maintainers' phrasing: "A user is never prompted out of nowhere, and every elicitation traces back to something they (or their agent) started." That is a real consent-provenance rule, and it did not exist before.

Put this into practice

Start by looking at a working app rather than reading more spec. About twenty minutes end to end.

  1. Clone an example and run it. The ext-apps repository ships runnable servers: a PDF viewer, an interactive map, a system monitor, a Three.js visualizer. Pick the one closest to what you would build and run it against a client you already use.

  2. Open the app's HTML before you connect it. The bundle is right there in the repo. Read it the way you would read a browser extension you were about to install, because that is the closer comparison. This is also the step that shows you what "pre-declared templates" buys: the content exists ahead of time, so review is possible. Somebody has to actually do it.

  3. Find the UI-approval setting in your client and turn it on. Claude, Goose, VS Code, and ChatGPT all ship MCP Apps support. What each one does about UI-initiated tool calls varies, and you will probably have to go looking. If your client does not expose that control, you have learned something useful about your exposure.

  4. Watch the postMessage traffic. Every UI-to-host message is JSON-RPC and loggable by design. Open your host's MCP log while you click around the example app. You want to see callServerTool and updateModelContext firing and know which of your clicks caused them.

  5. Write down your rule before you need it. Mine: a ui:// resource from a server I do not control is a third-party dependency, so it gets pinned, reviewed on upgrade, and kept away from any host session that also has write access to something I care about. That is a boring rule. It is the one I have not regretted.

If you are building a server, declare your UI template up front and keep it small. Every kilobyte of bundled JavaScript is a kilobyte someone has to trust or read.

Honest limitations

This is not new, and the news is smaller than it sounds. MCP Apps went live in January and Claude has supported it since. The July 28 release formalizes the extensions process around it. If you were waiting for the spec to bless this before adopting it, the blessing is procedural.

I cannot tell you which clients enforce which safeguards. There is no published conformance matrix for the optional parts of the MCP Apps security model. The specification release does add a conformance suite requirement for Standards Track proposals, which is the right direction, and it does not retroactively tell you what your current client does today. I checked and could not find the answer for every client I use. That gap is the honest state of things, not an accusation.

updateModelContext has no spec-level surfacing requirement I could find. A rendered app can write into the model's context. Whether your host shows you that happened is up to your host. If you are threat-modeling prompt injection, that is the line to stare at.

Extensions are negotiated, so support is uneven by design. Clients advertise extensions in a capability map. A server that assumes MCP Apps will degrade or fail on a client that does not advertise it, and "degrade gracefully" is the kind of promise that holds until the first server author forgets.

The sandbox is a browser sandbox. Iframe sandboxing with restricted permissions is meaningful and well understood. It is also the same boundary that has been generating CVEs for twenty years. It bounds the blast radius; it does not eliminate it.

What I would watch

The interesting question is not whether server-rendered UI is safe. It is whether hosts compete on the optional parts. Right now a client can ship MCP Apps support without HTML review and without approval gating on UI-initiated tool calls, and nothing in the spec stops it, and no user-facing label tells you which one you picked.

That is the sort of thing that gets fixed when somebody publishes a comparison table and it becomes embarrassing to be at the bottom of it. So: if you have looked into what your client actually does with updateModelContext and UI-initiated tool calls, I would like to know what you found. I have a partial answer and would rather have a full one.

Sources: MCP Apps announcement, The 2026-07-28 MCP Specification Release Candidate, ext-apps repository, SEP-2260, SEP-2322, SEP-1865.