Rightshold™ Sign up

Answers · MCP security

Our MCP server is adding an MCP App, an interactive HTML view the host renders in the chat. What does the host's sandbox actually protect, what can the app still do, and how do we lock it down so a bug or a hostile input in the view can't call our tools or leak data?

Last updated

TL;DR: An MCP App (the MCP Apps extension, SEP-1865, stable spec 2026-01-26) is a ui:// resource of type text/html;profile=mcp-app that a tool links through _meta.ui.resourceUri. Hosts must render it in a sandboxed iframe, talk to it only through postMessage JSON-RPC, and build its Content Security Policy from what the resource declares in _meta.ui.csp; with nothing declared, the spec's default allows no network connections (connect-src 'none'), no nested frames and no undeclared origins. That protects the host page and the user's cookies from your view. It doesn't protect your server from your view: the app can ask the host to call your tools, send messages into the chat, update the model's context and open links, and the spec only says hosts MAY ask the user first. So the lock-down is mostly yours: declare the fewest origins and permissions, mark UI-only tools with visibility ["app"] and still authorize them on the server, treat tool input and results shown in the view as untrusted, keep secrets out of the HTML (hosts may prefetch and cache it), and keep the template stable so hosts that hash or review it don't see surprises.

What should I check, step by step?

1. Declare only the origins the view needs in _meta.ui.csp: connectDomains for fetch, XHR and WebSocket, resourceDomains for scripts, styles, images and fonts. Leave frameDomains and baseUriDomains out unless you need them; omitted, the host uses frame-src 'none' and base-uri 'self'.

  1. Prefer bundling scripts and styles into the HTML over loading them from a CDN, so there's no third-party origin in resourceDomains that could change what runs.
  2. Request camera, microphone or geolocation in _meta.ui.permissions only if the view truly needs them; hosts map them onto the iframe's allow attribute and may still refuse.
  3. Mark tools that only the view should call with _meta.ui.visibility ["app"] so the host keeps them out of the model's tool list, and mark tools the view must never call with ["model"].
  4. Authorize every tools/call on the server as if it came from anyone: visibility is enforced by the host, not by your server, and a host that doesn't support MCP Apps simply treats the tool as a normal tool.
  5. Treat tool input and tool results the host pushes into the view as untrusted data: render them as text, never through innerHTML or eval, since they can carry content from the model or from third parties.
  6. Don't put API keys, tokens or user data in the ui:// HTML. Hosts may prefetch and cache the resource, and any client that can call resources/read can fetch it.
  7. Don't have the view send ui/message or ui/update-model-context text the user didn't write or see; content that steers the model is exactly what a reviewer will flag.
  8. Keep the template stable between releases and version it on purpose, and scan its text for hidden markup before publishing, because hosts may hash, review or allowlist templates.

What does the MCP Apps iframe sandbox and CSP protect?

The spec requires every view to run in a sandboxed iframe, with all communication through postMessage, so the view can't read the host page's DOM, cookies or local storage, navigate the parent page or run script in the host's context. A web host must also put the view behind a sandbox proxy on a different origin from the host, and the proxy loads your HTML with a CSP built from your declared domains and with object-src 'none'.

What can an MCP App view still do inside the host, and do hosts ask the user first?

Through the host, a view can request tools/call on its own server, send a message into the conversation (ui/message), replace the context the model sees on its next turn (ui/update-model-context), ask the host to open a link (ui/open-link) and ask for a different display mode. The spec says hosts should add ui/message content to the conversation and may request user consent, should pass model-context updates to the model and may show them to the user. The project's announcement says hosts can require explicit approval for UI-initiated tool calls; it isn't a requirement.

Does MCP Apps tool visibility (app-only tools) protect your server's tools?

_meta.ui.visibility defaults to ["model", "app"]. With ["app"], the host must leave the tool out of the model's tool list, only the view from the same server connection may call it, and cross-server calls to app-only tools are always blocked. The host must reject a view's call to a tool whose visibility doesn't include "app".

How do I ask Rightshold about this (free, no key)?

Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code, then ask: “Before we publish our MCP App, scan the text of our ui:// HTML template and the linked tool's description for content a reviewer can't see.”

One-click setup

{
  "tool": "scan_text",
  "arguments": {
    "text": "<the ui:// resource HTML and the tool description>"
  }
}

Rightshold scans text and records evidence about MCP servers. It isn't an MCP host, doesn't render or sandbox MCP Apps and isn't affiliated with the MCP project or any host named here. Requirements are quoted from the MCP Apps specification (2026-01-26) and the project's documentation as checked on November 10, 2026; host behavior varies by client.

What are the sources?

Related

Not legal advice. JSON · Markdown · All answers

Get a free API key in one call

No card and no email needed. 100 free credits; the license and policy checks stay free without a key.

curl -s -X POST https://www.rightshold.com/v1/accounts/signup -H "Content-Type: application/json" -d '{"accept_terms":true}'
export RIGHTSHOLD_API_KEY=rs_...   # the api_key from the reply (shown once)
curl -s https://www.rightshold.com/v1/licenses/check -H "Authorization: Bearer $RIGHTSHOLD_API_KEY" \
  -H "Content-Type: application/json" -d '{"license":"MIT","intended_use":{"commercial_use":true}}'