{# Base template for an MCP App (a "text/html;profile=mcp-app" UI resource rendered by the host in a sandboxed iframe). The iframe HTML is a STATIC shell. Per-request data is NOT rendered into it with Twig: the host delivers the tool result at runtime via the `ui/notifications/tool-result` message, which calls `render(model)`. There are two ways to turn that result into markup: 1. HTML-over-the-wire (default, recommended): your tool method returns a plain context array and names its template on the attribute — `toolTemplate` on `#[AsMcpApp]` for the primary tool, or `template` on `#[AsMcpAppTool]` for extra tools. The bundle renders that template with the array and injects the result as the tool result's `html` field; the default `render()` below drops it into `#root` and mirrors any scalar context values into matching form inputs (e.g. a `query` field fills ``). You write no Twig call and no JS: {% extends '@Mcp/app/base.html.twig' %} {% block title %}My App{% endblock %} {% block style %}.card { ... }{% endblock %} (the body defaults to a
, where the tool result HTML lands) 2. JS-driven: override `app_script` with your own `render(model)` (it shadows the default) and build the DOM from a structured model. Use this for rich client-side interactivity: {% block body %}
{% endblock %} {% block app_script %} function render(model) { /* build the DOM from the structured tool result */ } {% endblock %} Either way, the Twig context here is only for static shell composition (branding, styles, app name). Interactions need no JS either: the bridge wires DOM attributes to tool calls. An element with `data-call="tool_name"` invokes that tool on activation (args from its `data-arg-*` attributes, e.g. `data-arg-slug` -> `{ slug }`), a `
` sends its named fields as the args, and the returned HTML is rendered automatically. `data-open="https://…"` opens an external link.
The `bridge` block below implements the MCP Apps postMessage protocol so you don't have to: the ui/initialize -> ui/notifications/initialized -> size-changed handshake, request/response plumbing (`sendRpc`/`sendNotification`), an `openLink(url)` helper (sandboxed iframes cannot open links directly), the declarative `data-call`/`data-open` wiring above, and dispatch of tool-result/tool-input to your `render`/`onToolInput` hooks. Spec: https://github.com/modelcontextprotocol/ext-apps #} {% block title %}MCP App{% endblock %} {% block head %}{% endblock %} {% block body %}
{% endblock %}