elicit_display
Show information to the user as rendered HTML instead of Markdown: pass a
template plus JSON context, and hosts that
support MCP Apps
render it inline in the conversation — tables, reports, cards, images.
When to use
Section titled “When to use”When the agent needs to show something the user should scan — a comparison
table, a status report, a summary with images — and Markdown tables and prose
fall short. It does not collect a decision: pair it with
elicit_confirm, or use a Pro tool such as
elicit_approval or
elicit_selection when the human must answer.
Write the template once (in a skill, say) and the model only supplies data:
the same layout every time, however the data changes. The rendered HTML never
enters the model’s context — it gets back only your summary — and when the
host renders, the model doesn’t need to repeat the information in its reply.
| Parameter | Type | Required | Description |
|---|---|---|---|
template |
string | yes | A template producing an HTML fragment, rendered against {{ context.* }}. Max 32 KB. |
context |
object | no | JSON data exposed to the template as context. Max 64 KB serialized. |
title |
string | no | Heading the view shows above the render, beside the host’s Fullscreen button where there is one (1–120 characters). Leave it out when the template has its own heading. |
summary |
string | no | One or two sentences describing what is shown; returned as the tool’s text result (1–500 characters). |
The template contract — escaping, JavaScript-style truthiness, own-property access — is the same as the review pages’; see Templates.
{ "title": "Open invoices", "summary": "Three open invoices totalling $4,210.", "template": "<link href=\"https://cdn.jsdelivr.net/npm/daisyui@5\" rel=\"stylesheet\"><script src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4\"></script><table class=\"table table-zebra\">{% for i in context.invoices %}<tr><td>{{ i.number }}</td><td class=\"text-right\">{{ i.amount }}</td></tr>{% endfor %}</table>", "context": { "invoices": [ { "number": "INV-1041", "amount": "$1,200" }, { "number": "INV-1043", "amount": "$2,450" }, { "number": "INV-1047", "amount": "$560" } ] }}Output
Section titled “Output”The rendered HTML is not returned to the model. It travels in the result’s
_meta (_meta["elicitly/display"]), which hosts deliver to the view but keep
out of the model’s context. The model gets a short text result: your
summary (or Displayed "<title>".), plus a note when something needs its
attention:
- The host didn’t advertise MCP Apps — the user may not see the render, so the note asks the model to present the key information in its reply too.
- The template references blocked origins — the note names each origin the view will block (with a reference count) and the allowed ones, so the model can fix the template instead of guessing.
On the hosted Pro server the result also carries structuredContent, with the
same notes — some clients give the model only structuredContent and drop the
text:
{ "summary": "Three open invoices totalling $4,210.", "blockedOrigins": [{ "origin": "https://upload.wikimedia.org", "references": 1 }], "appsAdvertised": true, "notes": [ "The view blocks content from https://upload.wikimedia.org (1 reference), so it won't load. …" ]}appsAdvertised is whether the host advertised MCP Apps support — null when
the server couldn’t tell.
A template that fails to parse or render returns a tool error with the template engine’s message, so the model can correct it.
Images, scripts, and styles
Section titled “Images, scripts, and styles”The view runs in the host’s sandbox under a fixed Content Security Policy. Templates may load from:
https://cdn.jsdelivr.net— always. Load Tailwind (https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4) and DaisyUI (https://cdn.jsdelivr.net/npm/daisyui@5) with your own<script>/<link>tags. The view setsdata-themeto the host’s light/dark theme, so DaisyUI follows it.- Origins you allow.
- Pro (hosted): an organization admin lists them under Organization →
elicit_display allowed origins in the dashboard (up to 20; wildcard
subdomains such as
https://*.example.comwork). Templates keep using the normal URLs: when the tool renders, the server rewrites them to signed links on Elicitly’s resource proxy, which fetches the image, stylesheet, font or script for the view. The links are bound to your organization and expire after 30 days, and removing an origin stops its links from loading within about a minute — including in past renders. The proxy serves only images, stylesheets, fonts and scripts (up to 5 MB each), and its requests carry no user or organization details. - Free (local): set
ELICITLY_DISPLAY_RESOURCE_DOMAINSto comma-separatedhttps://origins (e.g.https://upload.wikimedia.org), or fill in elicit_display: extra allowed origins in the Claude Desktop extension’s settings. Wildcard subdomains (https://*.example.com) work.
- Pro (hosted): an organization admin lists them under Organization →
elicit_display allowed origins in the dashboard (up to 20; wildcard
subdomains such as
data:URIs — for images that aren’t publicly reachable (generated, read from a local file, or from a private source). Pass them incontextrather than the template: 64 KB versus 32 KB.
Everything else is blocked, and the tool’s result tells the model which origins it used that aren’t allowed, so it can drop them or ask you to allow them. The model is told not to download public images to get around the policy.
Links in a template open in the user’s browser (the view routes clicks through
the host), so they don’t need target="_blank" here.
Examples
Section titled “Examples”A carousel with images and links: Jupiter’s moons
Section titled “A carousel with images and links: Jupiter’s moons”Cards the user swipes through, each with an image, a few facts, and a link
to read more. The images come from https://upload.wikimedia.org, so the
origin must be allowed first (see above); the
Wikipedia links need nothing — the host opens them in the browser.
{ "summary": "Jupiter and its four Galilean moons — Io, Europa, Ganymede and Callisto — with size, orbit and a link to each on Wikipedia.", "template": "<link href=\"https://cdn.jsdelivr.net/npm/daisyui@5\" rel=\"stylesheet\"><script src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4\"></script>{% assign p = context.planet %}<div class=\"flex items-center gap-4 mb-4\"><img src=\"{{ p.image }}\" alt=\"{{ p.name }}\" class=\"size-20 rounded-full bg-black shrink-0\"><div><h2 class=\"text-lg font-bold\">{{ p.name }}</h2><p>{{ p.blurb }}</p><a class=\"link\" href=\"{{ p.url }}\">{{ p.name }} on Wikipedia</a></div></div><div class=\"carousel carousel-center w-full gap-3 pb-2 motion-reduce:scroll-auto focus-visible:outline-2 focus-visible:outline-primary rounded-box\"tabindex=\"0\" role=\"region\" aria-label=\"Moons — scroll sideways for more\">{% for m in context.moons %}<div class=\"carousel-item w-64\"><div class=\"card bg-base-200 w-full\"><figure class=\"bg-black\"><img src=\"{{ m.image }}\" alt=\"{{ m.name }}\" class=\"h-36 object-contain\"></figure><div class=\"card-body p-4 gap-1\"><h3 class=\"card-title text-base\">{{ m.name }}<span class=\"badge badge-sm\">{{ forloop.index }} of {{ forloop.length }}</span></h3><p>{{ m.fact }}</p><p class=\"text-sm\">{{ m.diameterKm }} km across · orbits in {{ m.orbitDays }} days</p><a class=\"link\" href=\"{{ m.url }}\">{{ m.name }} on Wikipedia</a><p class=\"text-xs mt-1\">Image: {{ m.credit }}</p></div></div></div>{% endfor %}</div>", "context": { "planet": { "name": "Jupiter", "blurb": "The largest planet: 139,820 km across, one orbit every 11.86 years.", "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Jupiter_OPAL_2024.png/330px-Jupiter_OPAL_2024.png", "url": "https://en.wikipedia.org/wiki/Jupiter" }, "moons": [ { "name": "Io", "fact": "The most volcanically active body in the Solar System.", "diameterKm": "3,643", "orbitDays": "1.77", "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/07/Io_highest_resolution_natural_color.png/330px-Io_highest_resolution_natural_color.png", "url": "https://en.wikipedia.org/wiki/Io_(moon)", "credit": "NASA (public domain)" }, { "name": "Europa", "fact": "An ice shell over a salty ocean that may hold more water than Earth's.", "diameterKm": "3,122", "orbitDays": "3.55", "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b3/Europa_-_Perijove_45_%28cropped%29.png/330px-Europa_-_Perijove_45_%28cropped%29.png", "url": "https://en.wikipedia.org/wiki/Europa_(moon)", "credit": "NASA/JPL-Caltech/SwRI/MSSS/Kevin M. Gill (CC BY 2.0)" }, { "name": "Ganymede", "fact": "The largest moon in the Solar System — bigger than Mercury.", "diameterKm": "5,268", "orbitDays": "7.15", "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Ganymede_-_Perijove_34_Composite.png/330px-Ganymede_-_Perijove_34_Composite.png", "url": "https://en.wikipedia.org/wiki/Ganymede_(moon)", "credit": "NASA/JPL-Caltech/SwRI/MSSS/Kevin M. Gill (CC BY 2.0)" }, { "name": "Callisto", "fact": "One of the most heavily cratered surfaces known.", "diameterKm": "4,821", "orbitDays": "16.69", "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c6/Callisto_VGR2_C2060635_OGB.png/330px-Callisto_VGR2_C2060635_OGB.png", "url": "https://en.wikipedia.org/wiki/Callisto_(moon)", "credit": "NASA / zelario12 (CC BY-SA 2.0)" } ] }}<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> {% assign p = context.planet %} <div class="flex items-center gap-4 mb-4"> <img src="{{ p.image }}" alt="{{ p.name }}" class="size-20 rounded-full bg-black shrink-0"> <div> <h2 class="text-lg font-bold">{{ p.name }}</h2> <p>{{ p.blurb }}</p> <a class="link" href="{{ p.url }}">{{ p.name }} on Wikipedia</a> </div> </div> <div class="carousel carousel-center w-full gap-3 pb-2 motion-reduce:scroll-auto focus-visible:outline-2 focus-visible:outline-primary rounded-box" tabindex="0" role="region" aria-label="Moons — scroll sideways for more"> {% for m in context.moons %} <div class="carousel-item w-64"> <div class="card bg-base-200 w-full"> <figure class="bg-black"><img src="{{ m.image }}" alt="{{ m.name }}" class="h-36 object-contain"></figure> <div class="card-body p-4 gap-1"> <h3 class="card-title text-base">{{ m.name }} <span class="badge badge-sm">{{ forloop.index }} of {{ forloop.length }}</span></h3> <p>{{ m.fact }}</p> <p class="text-sm">{{ m.diameterKm }} km across · orbits in {{ m.orbitDays }} days</p> <a class="link" href="{{ m.url }}">{{ m.name }} on Wikipedia</a> <p class="text-xs mt-1">Image: {{ m.credit }}</p> </div> </div> </div> {% endfor %} </div>The carousel is DaisyUI’s, which scrolls with CSS alone — swipe, trackpad, or focus it and use the arrow keys. The next card peeks in at the edge, so it’s clear there’s more.
A ranked list with photos: top salespeople
Section titled “A ranked list with photos: top salespeople”A leaderboard a sales skill can show before asking who should take the next
big account. Four people have photos; the fifth has none, so the template
falls back to initials — computed in the template, not sent in context.
{ "title": "Top salespeople — October", "summary": "The top five salespeople by sales this month, with quarter and year-to-date totals and progress to quarterly quota.", "template": "<link href=\"https://cdn.jsdelivr.net/npm/daisyui@5\" rel=\"stylesheet\"><script src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4\"></script><ol class=\"@container list bg-base-100 rounded-box\">{% for p in context.people %}<li class=\"list-row items-center\"><span class=\"text-2xl font-bold tabular-nums w-6 text-center\">{{ forloop.index }}</span>{% if p.photo %}<div class=\"avatar\"><div class=\"w-14 rounded-full\"><img src=\"{{ p.photo }}\" alt=\"\"></div></div>{% else %}{% assign parts = p.name | split: \" \" %}<div class=\"avatar avatar-placeholder\"><div class=\"w-14 rounded-full bg-neutral text-neutral-content\"><span class=\"text-lg\">{{ parts.first | slice: 0 }}{{ parts.last | slice: 0 }}</span></div></div>{% endif %}<div class=\"list-col-grow\"><div class=\"font-semibold\">{{ p.name }}</div><div class=\"text-sm\">{{ p.region }}</div><progress class=\"progress progress-primary w-full max-w-48\" value=\"{{ p.quotaPct }}\" max=\"100\"aria-label=\"{{ p.name }}: {{ p.quotaPct }}% of quarterly quota\"></progress><div class=\"text-xs\">{{ p.quotaPct }}% of quarterly quota</div></div><dl class=\"@max-md:row-start-2 @max-md:col-start-2 @max-md:col-span-2 @max-md:text-left grid grid-cols-3 gap-x-4 text-right tabular-nums\"><dt class=\"text-xs\">Month</dt><dt class=\"text-xs\">Quarter</dt><dt class=\"text-xs\">Year</dt><dd class=\"font-semibold\">{{ p.month }}</dd><dd>{{ p.quarter }}</dd><dd>{{ p.year }}</dd></dl></li>{% endfor %}</ol>", "context": { "people": [ { "name": "Mei Chen", "region": "West", "month": "$184K", "quarter": "$512K", "year": "$1.92M", "quotaPct": 118, "photo": "https://www.elicitly.ai/examples/salespeople/mei-chen.jpg" }, { "name": "Marcus Johnson", "region": "Central", "month": "$171K", "quarter": "$468K", "year": "$1.74M", "quotaPct": 109, "photo": "https://www.elicitly.ai/examples/salespeople/marcus-johnson.jpg" }, { "name": "Sofia Ramirez", "region": "Southeast", "month": "$158K", "quarter": "$455K", "year": "$1.61M", "quotaPct": 104 }, { "name": "Arjun Mehta", "region": "Northeast", "month": "$149K", "quarter": "$431K", "year": "$1.58M", "quotaPct": 98, "photo": "https://www.elicitly.ai/examples/salespeople/arjun-mehta.jpg" }, { "name": "Grace Okafor", "region": "EMEA", "month": "$142K", "quarter": "$402K", "year": "$1.49M", "quotaPct": 95, "photo": "https://www.elicitly.ai/examples/salespeople/grace-okafor.jpg" } ] }}<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> <ol class="@container list bg-base-100 rounded-box"> {% for p in context.people %} <li class="list-row items-center"> <span class="text-2xl font-bold tabular-nums w-6 text-center">{{ forloop.index }}</span> {% if p.photo %} <div class="avatar"><div class="w-14 rounded-full"><img src="{{ p.photo }}" alt=""></div></div> {% else %} {% assign parts = p.name | split: " " %} <div class="avatar avatar-placeholder"><div class="w-14 rounded-full bg-neutral text-neutral-content"> <span class="text-lg">{{ parts.first | slice: 0 }}{{ parts.last | slice: 0 }}</span> </div></div> {% endif %} <div class="list-col-grow"> <div class="font-semibold">{{ p.name }}</div> <div class="text-sm">{{ p.region }}</div> <progress class="progress progress-primary w-full max-w-48" value="{{ p.quotaPct }}" max="100" aria-label="{{ p.name }}: {{ p.quotaPct }}% of quarterly quota"></progress> <div class="text-xs">{{ p.quotaPct }}% of quarterly quota</div> </div> <dl class="@max-md:row-start-2 @max-md:col-start-2 @max-md:col-span-2 @max-md:text-left grid grid-cols-3 gap-x-4 text-right tabular-nums"> <dt class="text-xs">Month</dt><dt class="text-xs">Quarter</dt><dt class="text-xs">Year</dt> <dd class="font-semibold">{{ p.month }}</dd><dd>{{ p.quarter }}</dd><dd>{{ p.year }}</dd> </dl> </li> {% endfor %} </ol>To try it as written, allow https://www.elicitly.ai, where the sample photos
live. In your own skill, photos would come from your CRM or intranet — an
origin you allow once (Pro: for the whole organization) — or as data: URIs
in context.
When the host can’t render
Section titled “When the host can’t render”Not every host renders MCP Apps. The tool still works there: it returns the text result, with a note that tells the model to present the information itself. For the Jupiter example, a host without MCP Apps (the Claude Code CLI, say) gives the model:
Jupiter and its four Galilean moons — Io, Europa, Ganymede and Callisto — with size, orbit and a link to each on Wikipedia. However, this host did not advertise MCP Apps support, so the user may not see the render — present the key information in your reply as well.The model already has the data — it wrote the context — so the user still
gets an answer, in Markdown, the way chat answers always looked:
Jupiter's four Galilean moons:
| Moon | Diameter | Orbit | Notable for || --- | --- | --- | --- || Io | 3,643 km | 1.77 days | Most volcanically active body in the Solar System || Europa | 3,122 km | 3.55 days | Ice shell over a salty ocean || Ganymede | 5,268 km | 7.15 days | Largest moon in the Solar System || Callisto | 4,821 km | 16.69 days | Heavily cratered surface |Same facts, but the pictures and the at-a-glance layout are gone — which is
the difference elicit_display makes where hosts support it.
Host support
Section titled “Host support”MCP Apps is an extension to MCP, and support varies by host. Verified so far:
- Claude Desktop renders inline from the local
elicitlyserver and the Claude Desktop extension, follows the host theme, and offers fullscreen. In turns where the agent also runs commands, Claude Desktop groups the steps and shows the call as a collapsed row instead of the inline view. - Claude (claude.ai) renders inline from the hosted server and follows the host theme.
- Claude Code in the Claude desktop app renders inline from the hosted server (connectors there reach it through claude.ai).
- Claude Code CLI doesn’t advertise MCP Apps, so nothing renders; the model
gets the note to present the information itself. It reads
structuredContentrather than the text, which is why the notes are in both. - ChatGPT renders inline from the hosted server through the standard MCP
Apps metadata, follows the host theme (including a light/dark switch while
the render stays open), opens fullscreen as a side panel beside the chat, and
opens template links in the browser. ChatGPT passes
structuredContentto the model as well, so the model also sees{ summary, blockedOrigins, appsAdvertised, notes }.
Hosts that don’t support MCP Apps still get the text result — with the note asking the model to present the information itself. See the MCP client matrix for other hosts.