MCP Apps
Attach an interactive UI to an agent and an mcp deployment
serves it to any host that supports the MCP Apps extension
(SEP-1865) — Claude,
ChatGPT, Copilot. The host renders your HTML inline in a sandboxed iframe instead
of plain text; a host that doesn't support it ignores the UI and shows the reply.
Author once on the agent, render everywhere the smith runs.
You write standard MCP Apps HTML (the @modelcontextprotocol/ext-apps App
class, the ui:// scheme, the text/html;profile=mcp-app MIME) — Ingram Cloud
is a conforming MCP Apps server. Templates are tenant-authored and
version-pinned; Ingram never generates UI at runtime.
The pieces
- A template is an HTML bundle attached to an agent under a
name. Its bytes live in blob storage; the agent carries the reference. - Publishing an agent version freezes its templates into the immutable snapshot, so a rendered panel is reproducible per version.
- A smith running that agent (over its
mcpdeployment) is what the host talks to; the panel it renders is byte-identical for every caller.
Author a template
Upload the bundle as multipart — the HTML file part plus a JSON metadata
sidecar. Uploads land on the agent's draft; publish a version to freeze it.
# Authorization: tenant-admin token (server-side only)
curl -X POST https://api.cloud.ingram.tech/v1/agents/agt_…/ui \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-F 'file=@dashboard.html;type=text/html' \
-F 'metadata={"name":"dashboard","csp":{"connectDomains":["https://api.example.com"]}}'
# → 200 { "name": "dashboard", "content_hash": "…", "csp": { … } }
metadata fields:
name— unique per agent ([a-z0-9_-]); re-uploading the same name replaces it.csp— the MCP Apps_meta.ui.cspobject (connectDomains,resourceDomains,frameDomains,baseUriDomains), the origins the sandboxed iframe may reach. Omit it and the deny-by-default sandbox allows only a self-contained bundle.permissions— the_meta.ui.permissionsmap the template requests (e.g.{ "camera": {} }).tool— present only for a typed app-tool (below).
List, fetch, and remove templates on the draft:
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/agents/agt_…/ui \
-H "Authorization: Bearer $IC_TOKEN" -H "IC-Api-Version: 2026-05-01"
# → { "data": [ { "name": "dashboard", … } ] }
# The raw bytes (302 to a presigned URL, or inline):
curl https://api.cloud.ingram.tech/v1/agents/agt_…/ui/dashboard/content \
-H "Authorization: Bearer $IC_TOKEN" -H "IC-Api-Version: 2026-05-01"
curl -X DELETE https://api.cloud.ingram.tech/v1/agents/agt_…/ui/dashboard \
-H "Authorization: Bearer $IC_TOKEN" -H "IC-Api-Version: 2026-05-01"
The internal blob key is never returned. These endpoints ride the same
runs:read / runs:write scopes and tenant-admin gate as the rest of
/v1/agents — a smith-scoped token can't touch them.
Then publish so the template is live for the deployment:
# Authorization: tenant-admin token (server-side only)
curl -X POST https://api.cloud.ingram.tech/v1/agents/agt_…/versions \
-H "Authorization: Bearer $IC_TOKEN" -H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" -d '{ "note": "with dashboard UI" }'
How it renders
Deploy the agent as an mcp server and
connect a host. There are three ways the smith drives which
template shows:
- Bound to
ask— name a templateaskand everyaskreply renders inside it. Theasktool descriptor carries_meta.ui.resourceUriso the host prefetches the bundle. - Typed app-tools — give a template a
tool({ description, input_schema, instruction, mutating }). The deployment surfaces one MCP tool per app-tool, each linked to its own template; the host's model routes to the right one, and the smith runs the template's scopedinstructionwith the host-supplied arguments. Amutatingtool carries the standarddestructiveHintannotation, so the host asks the user before a write; reads run freely. - Dynamic — the smith calls the built-in
render_apptool mid-reply to pick a template (by name) and the JSON it renders. Enabled automatically once the agent has at least one template.
In every case the panel receives the run's structured result over the standard
MCP Apps channel, and any tool the panel calls back rides the same tools/call
endpoint — a write that needs approval pauses the run on the standard
approval channel.
Degradation
Only MCP hosts render ui:// panels. On a chat channel (Slack, Telegram, email)
or any host without the extension, the smith's text reply stands on its own — so
write a reply that reads well without the UI.
In the console
An agent's Apps tab lists its templates, uploads new bundles, sets CSP origins and permissions, binds a tool, and previews a template in a sandboxed iframe so you can test it without connecting a host.