Описание
Small Test Set for MCP-App SEP-1865
README
A reference MCP Apps server you can deploy in one click — twenty-six interactive widgets that exercise every part of the spec. Use it to test your own MCP host, prototype your own widgets, or play with the protocol.
What is MCP Apps? SEP-1865 — MCP Apps: Interactive User Interfaces for MCP is the open standard for embedding interactive widgets in chat clients. An MCP server registers
ui://resources withmimeType: text/html;profile=mcp-app, and a host (Claude, ChatGPT, Goose, VS Code, …) renders them in an iframe with a JSON-RPC bridge to the model. This repo is a plug-and-play reference server for the spec.
Live demo: https://mcp-ui-extapps-demo.onrender.com/mcp (Render free tier — cold-starts after idle).
The widgets
All twenty-six are spec-compliant: each tool declares _meta.ui.resourceUri, the
matching resource is registered with text/html;profile=mcp-app, and the
client-side JS uses the official @modelcontextprotocol/ext-apps SDK.
Each row names the SEP-1865 surfaces the widget exercises. Brackets give the spec section / method ID so you can grep the spec.
| # | Tool | What it shows | SEP-1865 surface tested |
|---|---|---|---|
| 1 | show_hello |
Static greeting card | text/html;profile=mcp-app resource + _meta.ui.resourceUri on tool + ui/initialize handshake (bare minimum) |
| 2 | show_counter |
+1 / −1 buttons, "Grow" panel | ui/notifications/tool-input (read tool args) + ui/notifications/size-changed (SDK auto-resize) |
| 3 | show_feedback |
"What didn't you like?" negative-feedback card — multi-select reason chips, expandable "Harmful or inappropriate" subgroup, optional details, send-logs toggle, dismiss-X | tools/call host→app (submit_feedback) + ui/message (auto-posts a brief user message into chat on submit so the assistant has context) + ui/notifications/request-teardown (dismiss X) |
| 4 | show_price_chart |
Live BTC/ETH/SOL/DOGE/ADA chart | _meta.ui.csp.resourceDomains (Chart.js CDN) + _meta.ui.csp.connectDomains (CoinGecko fetch) + ui/open-link |
| 5 | show_map |
Click-to-drop-pin map | _meta.ui.csp.resourceDomains (Leaflet + OSM tile servers) + tools/call (save_pin) + ui/open-link |
| 6 | show_tictactoe |
Tic-tac-toe vs server-side minimax | tools/call with outputSchema / structuredContent round-trip per move |
| 7 | show_drawing |
Canvas drawing pad with brush + colors | Large-payload tools/call (PNG data URL via save_drawing) — stresses message-channel size limits |
| 8 | show_todos |
Todo list with add / toggle / delete | Multiple tools/call round-trips per session (add_todo / toggle_todo / delete_todo) |
| 9 | show_weather |
Current conditions for any city | _meta.ui.csp.connectDomains against a second external API (Open-Meteo, no auth) |
| 10 | show_3d |
Rotating 3D cube / sphere / torus / knot | _meta.ui.csp.resourceDomains (three.js CDN) + WebGL inside the sandbox |
| 11 | show_punch_monkey |
15-second clicker mini-game | High-frequency local state, single end-of-round tools/call (record_punch_score) — verifies the host doesn't drop late callbacks |
| 12 | show_adventure |
Branching Apple-II-style mystery | Split UX: scene rendered in widget via tools/call, user answers typed into chat → model interprets → next tools/call |
| 13 | show_hover |
Spotlight that follows the cursor over hidden words | Pure pointer-event widget — verifies the iframe receives mousemove/mouseenter/mouseleave (no clicks, no callbacks) |
| 14 | show_florida_man |
Frogger-style crossing dodging golf carts 🤠🛺🐊 | Canvas + requestAnimationFrame + keyboard input inside the sandbox; end-of-round tools/call (record_florida_score) |
| 15 | show_kanban |
Kanban board with Maximize button | availableDisplayModes app capability + ui/request-display-mode (view→host) + ui/notifications/host-context-changed (host→view) |
| 16 | show_themed_swatches |
Live grid of host design tokens | ui/notifications/host-context-changed reactivity over HostContext.styles.variables.* (CSS design tokens) |
| 17 | show_signature |
Signature pad with native PNG download | ui/download-file with an inline EmbeddedResource (image/png blob) — no callback round-trip |
| 18 | show_one_shot |
Self-dismissing 1-question survey | ui/notifications/request-teardown (view→host) + ui/resource-teardown (host→view) cleanup hook |
| 19 | show_internal_counter |
Counter with model-hidden +1 callback | _meta.ui.visibility: ["app"] — bump_counter is callable from the iframe but absent from the model's tools/list |
| 20 | show_url |
Wraps an arbitrary URL as an MCP-app via nested iframe | _meta.ui.csp.frameDomains (CSP frame-src) — surfaces both CSP-block and X-Frame-Options: DENY failure modes |
| 21 | show_user_profile |
Preferences form (name / units / reply style) | ui/update-model-context (view→host) — pushes context blocks into the model with no tools/call round-trip |
| 23 | show_product_carousel |
Snap-scrolling carousel with Unsplash photos + embedded checkout modal | tools/call (add_to_cart, checkout) + _meta.ui.csp.resourceDomains (Unsplash CDN) — also a CSS playground (gradient borders, shine sweep, lift-on-hover) |
| 24 | show_age_picker |
iOS-style wheel + manual input with life-stage chip | tools/call (submit_age) + ui/notifications/tool-input (initial age) — visual demo with morphing gradient theme |
| 25 | show_age_gate |
Neutral FTC-style DOB gate, jurisdiction-aware (COPPA / GDPR / LGPD / DPDP / PIPA), persistent decision | tools/call (submit_age_check) — data-minimization pattern: DOB stays client-side, only the derived decision crosses the boundary |
| 26 | show_newsapi_news |
NewsAPI-backed news grid — hero card, "Sponsored" badges, "Recommended by NEWSAPI" footer, live category picker (general / business / tech / science / health / sports / entertainment), liquid-glass styling | Real keyed external API (NewsAPI) proxied server-side via NEWSAPI_KEY env var + structured result inlined into the resource HTML at resources/read time (works around hosts that route widget→server tool results to chat instead of back to the iframe) + ui/open-link |
| 27 | show_ebay_search |
Live eBay listing search — eBay-colored brand wordmark, in-widget search box, square thumbnails, price-forward cards with Auction / Buy-It-Now badges, condition + seller feedback %, click-to-open the listing | Real OAuth2 external API (eBay Browse) with in-process client-credentials token cache, EBAY_CLIENT_ID / EBAY_CLIENT_SECRET env vars; same inline-payload-into-HTML pattern as #26; in-widget app.callServerTool('show_ebay_search', …) for re-search (best-effort remount) + ui/open-link |
| 28 | show_youtube |
YouTube player — privacy-enhanced nested iframe, accepts a URL or bare 11-char ID, preset pills, click-to-open on youtube.com | _meta.ui.csp.frameDomains (CSP frame-src) scoped to youtube-nocookie.com + ui/open-link — video bytes stream straight from Google to the iframe; the server only ships the player + allowlist |
| 29 | show_video |
Native HTML5 <video controls> for a direct file URL (.mp4 / .webm / .ogg), HTTPS-only with load/CORS error overlay |
_meta.ui.csp.connectDomains for the media origin (host derives media-src) — no iframe; the host's own media pipeline fetches the file |
| 30 | show_hls |
Adaptive HLS streaming via hls.js (CDN) with a quality-level picker; native HLS fallback on Safari/iOS; auto network/media-error recovery |
hls.js from resourceDomains + playlist/segment origin in connectDomains (XHR connect-src) — adaptive bitrate over .m3u8 |
Plus the callback tools the widgets invoke: submit_feedback, save_pin,
make_move, save_drawing, add_todo, toggle_todo, delete_todo,
record_punch_score, advance_adventure, record_florida_score,
submit_survey, bump_counter (app-only), add_to_cart, checkout,
submit_age, submit_age_check.
Spec coverage analysis
TL;DR — All are spec-compliant, but several test the same SEP-1865 surface from a different angle. If your goal is minimum-redundant coverage of the spec (rather than a varied gallery), the breakdown below shows which widgets you can drop without losing any surface.
Which widgets test which spec surface (click to expand)
Surfaces tested by exactly one widget — keep all of these
| Spec surface | Tested by |
|---|---|
text/html;profile=mcp-app + _meta.ui.resourceUri + ui/initialize (bare-minimum static render) |
#1 show_hello |
ui/notifications/size-changed (auto-resize) |
#2 show_counter |
ui/message (post user message into chat) |
#3 show_feedback |
tools/call with outputSchema / structuredContent |
#6 show_tictactoe |
Large-payload tools/call (message-channel size limits) |
#7 show_drawing |
Same _meta.ui.resourceUri shared by multiple tools (the second tool updates the existing widget) |
#12 show_adventure |
availableDisplayModes + ui/request-display-mode + host-context-changed (displayMode field) |
#15 show_kanban |
host-context-changed reactivity over styles.variables.* (theme tokens) |
#16 show_themed_swatches |
ui/download-file with embedded blob |
#17 show_signature |
ui/notifications/request-teardown + ui/resource-teardown |
#18 show_one_shot |
_meta.ui.visibility: ["app"] (model-hidden tool) |
#19 show_internal_counter |
_meta.ui.csp.frameDomains (CSP frame-src for nested iframes) |
#20 show_url, #28 show_youtube (video-specialized) |
<video> media playback (media-src) + HLS adaptive streaming over XHR |
#29 show_video (native file), #30 show_hls (hls.js) |
ui/update-model-context (view→host context push) |
#21 show_user_profile |
Surfaces tested by multiple widgets
| Spec surface | Tested by | Verdict |
|---|---|---|
tools/call (host→app proxy) |
#3, #5, #6, #7, #8, #11, #12, #14, #18, #19 | most fully redundant; #6 (outputSchema) and #7 (large payload) add real angles |
_meta.ui.csp.resourceDomains (CSP script-src) |
#4 (Chart.js), #5 (Leaflet), #10 (three.js) | #10 adds nothing #4/#5 don't already prove |
_meta.ui.csp.connectDomains (CSP connect-src) |
#4 (CoinGecko), #9 (Open-Meteo) | one external API is enough |
ui/open-link |
#4, #5 | minor overlap |
host-context-changed |
#15 (displayMode), #16 (theme + styles.variables) |
soft — different fields; hosts often propagate one and not the other, so both stay useful |
Cross-origin <img> loading inside the sandbox |
#5 (OSM tiles), #4 (Chart.js fonts) | soft — distinct CSP directive (img-src) |
Canvas + requestAnimationFrame inside the sandbox |
#11, #14 | not a spec surface (gated by allow-scripts); pick one |
Drop candidates (spec-redundant)
| # | Widget | Why redundant |
|---|---|---|
| 8 | show_todos |
Adds no new spec surface — just more tools/calls. Already covered by #3, #5, #6 |
| 9 | show_weather |
Same csp.connectDomains story as #4, just a different API host |
| 10 | show_3d |
Same csp.resourceDomains story as #4/#5; WebGL itself isn't a spec surface |
| 11 | show_punch_monkey |
Spec-wise = #3 (tools/call); "high-frequency state" is not a host-side surface |
| 13 | show_hover |
Pointer events propagating to a sandboxed iframe isn't a distinct spec surface — allow-scripts covers it |
| 14 | show_florida_man |
Same as #11 spec-wise; keyboard delivery isn't a separate spec surface |
Keep one of {#11, #14} if you want a representative "stresses local state + late callback" demo. The others can go without losing any spec coverage.
Minimal complete-coverage set (13 widgets)
If you cut hard: #1, #2, #3, #4, #5, #6, #7, #12, #15, #16, #17, #18, #19, #20, #21 (plus optionally one of #11/#14). That hits every SEP-1865 surface this repo currently exercises, with no two widgets covering the same surface.
Surfaces still uncovered (no widget tests these)
Pruning won't fix these — only adding new widgets will:
_meta.ui.csp.baseUriDomains_meta.ui.permissions.{camera, microphone, geolocation, clipboardWrite}_meta.ui.domain(custom origin)pipdisplay mode (onlyinline/fullscreenare exercised in #15)ui/notifications/tool-input-partial/tool-cancellednotifications/tools/list_changed/resources/list_changedsamplinghost capability (createSamplingMessage)containerDimensions,safeAreaInsets,deviceCapabilitiesfromHostContext
Quick deploy on Render
- Click the Deploy to Render button above (or fork → connect in Render).
- Accept the
render.yamlblueprint. Free tier is fine. - Wait ~2 min for the first build.
- Add the URL to your MCP host:
https://<your-service>.onrender.com/mcp.
That's it for 24 of 26 widgets — no auth, no DB. Two widgets call out to keyed external APIs and need credentials in env vars:
#26 show_newsapi_news (NewsAPI)
- Grab a free key at https://newsapi.org/register (no credit card, 100 reqs/day — enough for ~14 widget mounts).
- Render dashboard → Environment → Add Environment Variable →
NEWSAPI_KEY=<your-key>.
#27 show_ebay_search (eBay Browse API)
- Sign up at https://developer.ebay.com/my/keys → create an application → grab the Production keyset (Client ID + Client Secret). Free; no credit card.
- Render dashboard → Environment → add two variables:
EBAY_CLIENT_IDandEBAY_CLIENT_SECRET. Optionally setEBAY_ENV=sandboxto point at the sandbox host.
All three are declared in render.yaml with sync: false so values
must be set in the dashboard, not committed.
Run locally
Requires Python 3.11+ and uv.
uv sync
uv run python server/server.py --port 8767
The server speaks Streamable HTTP at http://localhost:8767/mcp.
For show_newsapi_news and show_ebay_search, drop the credentials
into a .env file at the repo root (already in .gitignore); the
server auto-loads it on startup:
NEWSAPI_KEY=your-newsapi-key
EBAY_CLIENT_ID=your-ebay-client-id
EBAY_CLIENT_SECRET=your-ebay-client-secret
# EBAY_ENV=sandbox # optional; defaults to production
Or export inline: EBAY_CLIENT_ID=… EBAY_CLIENT_SECRET=… uv run python server/server.py.
Test against the reference basic-host:
git clone https://github.com/modelcontextprotocol/ext-apps && cd ext-apps
npm install && npm run build
SERVERS='["http://localhost:8767/mcp"]' npm run start --prefix examples/basic-host
# open http://localhost:8080
Connect from a host
Claude.ai / Claude Desktop
Settings → Connectors → Add custom connector → paste the /mcp URL.
Any MCP-compliant host
The server is plain Streamable HTTP. The handshake sequence is the standard MCP
one (initialize → notifications/initialized), and every widget-emitting tool
declares _meta.ui.resourceUri so spec-aware hosts know to render the widget.
Architecture
┌──────────────────────────────────────────────────────────────────┐
│ Host (Claude, ChatGPT, Goose, custom MCP host…) │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ iframe (sandboxed) │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ Widget HTML (served from ui://… resource) │ │ │
│ │ │ imports @modelcontextprotocol/ext-apps from CDN │ │ │
│ │ │ app.callServerTool(...) → tools/call (JSON-RPC) │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ JSON-RPC over postMessage (SEP-1865) │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ MCP client │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
▲
Streamable HTTP MCP transport
▼
┌──────────────────────────────────────────────────────────────────┐
│ This server (Python, FastMCP) │
│ ─ 26 ui://… resources, mime "text/html;profile=mcp-app" │
│ ─ 26 widget-emitting tools with _meta.ui.resourceUri │
│ ─ N callback tools (save_pin, make_move, save_drawing, …) │
└──────────────────────────────────────────────────────────────────┘
Repo layout
.
├── server/
│ ├── server.py FastMCP server, 20 widgets + callbacks
│ └── apps/ Self-contained widget HTML (one file each)
├── render.yaml Render blueprint (one-click deploy)
├── pyproject.toml uv / pip dependencies
└── COMPLIANCE.md Per-widget spec coverage checklist
Adding your own widget
- Drop a self-contained HTML file in
server/apps/that imports the SDK:<script type="module"> import { App } from "https://cdn.jsdelivr.net/npm/@modelcontextprotocol/[email protected]/dist/src/app-with-deps.js"; const app = new App({ name: "my-widget", version: "1.0.0" }); app.ontoolinput = (params) => { /* read params.arguments here */ }; app.connect(); </script> - In
server.py, register the resource and a tool that points at it:@mcp.resource( "ui://my-widget", mime_type="text/html;profile=mcp-app", meta={"ui": {"prefersBorder": True, "csp": _SDK_CSP}}, ) def my_widget_app() -> str: return _load_app("my-widget.html") @mcp.tool(meta={"ui": {"resourceUri": "ui://my-widget"}}) async def show_my_widget() -> str: return "Rendered." - Push to
main— Render auto-redeploys.
If your widget needs external scripts or APIs, add them to the resource's
csp.resourceDomains / csp.connectDomains.
Compliance
Validated against SEP-1865 (draft) and the official @modelcontextprotocol/ext-apps SDK (v1.7.1). See COMPLIANCE.md for the per-widget checklist.
License
MIT. Have fun.
Установка Ui Extapps Demo
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/omeirhaeghe/mcp-ui-extapps-demoFAQ
Ui Extapps Demo MCP бесплатный?
Да, Ui Extapps Demo MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Ui Extapps Demo?
Нет, Ui Extapps Demo работает без API-ключей и переменных окружения.
Ui Extapps Demo — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Ui Extapps Demo в Claude Desktop, Claude Code или Cursor?
Открой Ui Extapps Demo на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
LibreOffice Tools
Enables AI agents to read, write, and edit Office documents via LibreOffice with token-efficient design. Supports multiple formats including DOCX, XLSX, PPTX, a
автор: passerbyflutterdannote/figma-use
Full Figma control: create shapes, text, components, set styles, auto-layout, variables, export. 80+ tools.
автор: dannoteLogo.dev
Search and retrieve company logos by brand or domain. Customize size, format, and theme to match your design needs. Accelerate design, prototyping, and content
автор: NOVA-3951Design Inspiration Server
Searches top design platforms like Dribbble and Behance to provide UI inspiration, color palettes, and layout patterns via the Serper API. It allows users to re
автор: YonasValentinPIX4Dmatic
Enables GUI automation for controlling PIX4Dmatic on Windows through MCP. Supports launching, focusing, capturing screenshots, sending hotkeys, clicking UI elem
автор: jangjo123Figma
Extract design specs and assets
автор: Figmamcp-dockmaster
An Open-Sourced UI to install and manage MCP servers for Windows, Linux and macOS.
ariekogan/ateam-mcp
Build, validate, and deploy multi-agent AI solutions on the ADAS platform. Design skills with tools, manage solution lifecycle, and connect from any AI environm
автор: ariekoganthinkchainai/mcpbundles
MCP Bundles: Create custom bundles of tools and connect providers with OAuth or API keys. Use one MCP server across thousands of integrations, with programmatic
автор: thinkchainaiarikusi/nakkas
MCP server that turns AI into an SVG artist. One rendering engine with JSON config, AI controls all design parameters. CSS @keyframes + SMIL animations, 16+ ele
автор: arikusiCompare Ui Extapps Demo with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории design
