defmodule Phantom.App do @moduledoc """ Behaviour for MCP App UI resources. [MCP Apps](https://apps.extensions.modelcontextprotocol.io/) are interactive HTML interfaces that render inside MCP hosts (like Claude Desktop) as sandboxed iframes. `Phantom.App` works like a Phoenix Controller — it's a Plug pipeline that renders HTML and provides metadata to the host. > #### Client-side JavaScript is required {: .warning} > > The rendered HTML **must** include JavaScript from the > [`@modelcontextprotocol/ext-apps`](https://www.npmjs.com/package/@modelcontextprotocol/ext-apps) > npm package. This JS handles the `postMessage` handshake with the host. > Without it, the host renders a blank iframe. > > See the [MCP Apps JS SDK](https://github.com/modelcontextprotocol/ext-apps) > for the full client-side API, including theming, tool input/result > handling, and host communication. ## Quick Start 1. Define your app module 2. Register it on a tool in your MCP router 3. Bundle the ext-apps JavaScript client 4. Include the bundle in your root layout via a base64 data URI ### App module defmodule MyApp.MCP.DashboardApp do use Phantom.App, permissions: [:clipboard_write], prefers_border: true use Phoenix.Component import Phoenix.Controller, only: [put_root_layout: 2, put_layout: 2] plug :put_root_layout, html: {MyAppWeb.MCP.Layouts, :root} plug :put_layout, html: {MyAppWeb.MCP.Layouts, :app} plug Phantom.App.CSP, connect_domains: ["https://api.example.com"] @impl Phantom.App def mount(_params, session) do {:ok, %{user: session.assigns.user}} end @impl Phantom.App def render(assigns) do ~H\"\"\"