# `Sake.UI`
[🔗](https://github.com/hattoriai/sake/blob/v0.4.3/lib/sake/ui.ex#L1)

An MCP App: an HTML document the host renders in a sandboxed iframe when
a tool that links it is called (the `io.modelcontextprotocol/ui`
extension). On the wire it is a resource with a `ui://` URI and the
`text/html;profile=mcp-app` MIME type, so it is listed on a server with
the other resources:

    defmodule MyApp.MCP.SalesMap do
      use Sake.UI,
        uri: "ui://my-app/sales-map",
        name: "sales_map",
        title: "Sales map",
        csp: [resource_domains: ["https://tile.openstreetmap.org"]],
        prefers_border: true

      @impl true
      def render(_ctx), do: ~s(<!doctype html><html>...</html>)
    end

    defmodule MyApp.MCP do
      use Sake.Server, name: "my-app", version: "1.0.0"
      tools [MyApp.MCP.ShowSales]
      resources [MyApp.MCP.SalesMap]
    end

A tool shows it with `use Sake.Tool, ui: MyApp.MCP.SalesMap`.

Options: `uri` (required, `ui://`), `name` (default: the module's last
segment in snake case), `title`, `description`, `icons`, `scope`, `ttl`,
`meta`, `csp` (`connect_domains`, `resource_domains`, `frame_domains`,
`base_uri_domains`: the origins the document may reach), `permissions`
(`:camera`, `:microphone`, `:geolocation`, `:clipboard_write`), `domain`
(a dedicated sandbox origin, in the host's own format), `prefers_border`,
and `template`.

The document comes from `render/1`, which returns a string or iodata,
`{:safe, iodata}`, or, when the host has `phoenix_html`, anything that
implements `Phoenix.HTML.Safe` (the result of a HEEx function
component), or from an EEx file named by `template:`,
relative to the module's own file and compiled into the module. A
template's assigns come from `assigns/1`, and `<%= %>` escapes its
output as HTML unless the value is `raw/1`. `inline/1` reads a file into
the document at compile time (a bundled script or stylesheet), and the
module recompiles when the file or the template changes.

The host learns the document's CSP and permissions from the resource's
`_meta.ui`, on its `resources/list` entry and on its `resources/read`
contents alike. A client that did not declare the extension does not
see UI resources listed (it can still read them).

# `assigns`
*optional* 

```elixir
@callback assigns(ctx :: Sake.Context.t()) :: map() | keyword()
```

The assigns of a `template:`, as a map or a keyword list.

# `render`
*optional* 

```elixir
@callback render(ctx :: Sake.Context.t()) :: iodata() | {:safe, iodata()} | struct()
```

The HTML document of the app.

# `client_capability`

```elixir
@spec client_capability() :: map()
```

The client capability a host declares to render MCP Apps (the one
`Sake.Client.connect/2` declares with `ui: true`).

# `escape`

```elixir
@spec escape(term()) :: String.t()
```

Escapes a value for an HTML document; `{:safe, iodata}` passes through.

# `extension`

```elixir
@spec extension() :: String.t()
```

The identifier of the MCP Apps extension.

# `inline`
*macro* 

The contents of `path`, relative to the project's root, read at compile
time: the module recompiles when the file changes. For the bundled
script or stylesheet of the app, so that the document is
self-contained.

    <script><%= raw inline("priv/static/assets/sales_map.js") %></script>

# `mime_type`

```elixir
@spec mime_type() :: String.t()
```

The MIME type of an MCP App document.

# `raw`

```elixir
@spec raw(iodata() | {:safe, iodata()}) :: {:safe, iodata()}
```

Marks `iodata` as HTML that a template must not escape.

# `supported?`

```elixir
@spec supported?(Sake.Context.t()) :: boolean()
```

Whether the client renders MCP Apps: it declared the extension, with the
`text/html;profile=mcp-app` MIME type among its `mimeTypes`. A tool can
use it to word its text for a client that shows no UI.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
