A View is an HTML document your server serves as a ui:// resource. Allternit renders it inline in the conversation and connects it to the host with a JSON-RPC bridge over postMessage. This page follows the MCP Apps specification version 2026-01-26 and lists what Allternit’s host implements.

Linking a tool to a View

Set _meta.ui.resourceUri on the tool. The value must start with ui://.
Allternit also reads the older flat key _meta["ui/resourceUri"] and the output-template key some hosts use (see Porting), so a server written for another host is found without changes. registerAppTool from @modelcontextprotocol/ext-apps/server writes both ui.resourceUri and ui/resourceUri. Serve the resource with:
  • URI starting with ui://
  • MIME type text/html;profile=mcp-app (Allternit also accepts plain text/html)
  • HTML in the text field, at most 2 MiB

Resource metadata

Set these under _meta.ui on the resource content:

The bridge

The View talks to the host with JSON-RPC 2.0 messages sent to window.parent. You can write these by hand (the quickstart does) or use the App class from @modelcontextprotocol/ext-apps, which wraps them.

Lifecycle

  1. The View sends the request ui/initialize with protocolVersion, appInfo, and appCapabilities.
  2. The host replies with its capabilities and hostContext (theme, styles, display mode, and so on).
  3. The View sends the notification ui/notifications/initialized.
  4. The host sends ui/notifications/tool-input (the arguments) and ui/notifications/tool-result (the result).
The host waits up to 20 seconds for the View to initialize. After that it shows an error state instead of a blank frame.

Messages the host sends to the View

Messages the View can send to the host

Any other method is rejected with JSON-RPC error -32601. ui/download-file is not implemented. The host advertises these capabilities at initialize: openLinks, serverTools, serverResources, logging, and x-allternit. It also advertises sandbox with the csp and permissions your resource declared.

Handling a tool call from the View

The call goes through the host to your server with the user’s own OAuth token. The View never sees the token.

Host context

hostContext is sent in the ui/initialize result and again in ui/notifications/host-context-changed.

Display modes

Views start inline. Request fullscreen with ui/request-display-mode when a task needs the space:
Only inline and fullscreen exist. Picture-in-picture is not supported. Any other requested mode resolves to inline. The View is not reloaded when the mode changes. Handle host-context-changed to adapt the layout.

Theming

The host resolves its design tokens to concrete colors and sends them as CSS variables in hostContext.styles.variables. Apply them to your document root and use them in your CSS, with fallbacks for hosts that send none:
Variables sent: When the user changes theme, the host sends ui/notifications/host-context-changed with new values. Re-apply them. Colors are sent as resolved values, not var() references, so they are safe to use inside your View’s own stylesheet.

Content Security Policy

The host builds the View’s CSP from the csp object on your resource. The default is closed: no network access, no nested frames.
The resulting policy is:
Rules for entries:
  • Each entry must be an origin: scheme://host[:port], optionally with a leading *. wildcard, for example https://api.example.com or https://*.example.com.
  • Entries that contain whitespace, ;, ,, or quotes, or that are bare keywords, a lone *, or bare schemes like data: or blob:, are dropped.
  • Declare only what the View needs. The review scan warns on wildcards, non-TLS schemes, more than 10 domains in total, and any frameDomains, which need a written justification.
The CSP is applied as a <meta> element placed before your document’s own content. Inline scripts and styles are allowed, so a single self-contained HTML file works. Because <script src> and stylesheet links resolve against an opaque origin, use absolute URLs on a declared resourceDomains entry, or inline the code. CSP violations are reported to the host, which logs them. They do not break the View. If something is silently blocked, look in the browser console for Content Security Policy messages.

Sandbox rules

The View runs inside two layers:
  1. The sandbox proxy is a static page on a separate origin from Allternit. Allternit’s web client embeds it, sends it your HTML over postMessage, and refuses to render Views if no sandbox origin is configured.
  2. Inside the proxy, your HTML runs in a nested iframe whose sandbox attribute does not include allow-same-origin.
What follows for your View:
  • Opaque origin. localStorage, sessionStorage, document.cookie, and IndexedDB are unavailable or throw. fetch from the View carries no cookies and sees no ambient credentials.
  • No access to the host. The View cannot read the host page, the proxy page, or the user’s tokens.
  • Nested frames are blocked unless you declare frameDomains.
  • Navigation. The sandbox allows scripts, forms, popups (which escape the sandbox), downloads, and modals. It never allows top-level navigation. To send the user elsewhere, call ui/open-link.
  • Permissions. Camera, microphone, geolocation, and clipboard-write are available only if you declare them in permissions.
  • One View per proxy document. A View cannot replace itself with another.

State guidance

Because the View has no storage, treat it as a pure function of what the host gives it.
  • Render from tool-input and tool-result. Do not assume they arrive before your first paint; render a placeholder first.
  • Keep durable state on your server. Save it with a tool that has visibility: ["app"] and read it back with tools/call on load.
  • Keep model-relevant state out of the View. Use ui/update-model-context to tell the model what matters (for example, which row the user selected) and ui/message to add a message to the conversation.
  • Expect the View to be torn down and re-created when the conversation is reloaded. Rebuild it from the tool result.
  • Report your content height with ui/notifications/size-changed after every layout change, or the inline frame will not fit your content.