mcp-app-boilerplate
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-app-boilerplateShow the interactive greeting widget and set the tone to casual."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MCP App plugin boilerplate
A deployable starting point for an agent plugin whose core is an MCP App — a remote MCP server that ships an interactive HTML widget the host renders next to the tool result. One repo covers both halves:
Layer | Where | What it is |
MCP App |
| A Next.js route serving a remote MCP server, and the widget it hands the host |
Plugin package |
| Agent Plugins 1.0.0 package that installs into Cursor, Codex, and Claude Code |
This is a GitHub template repository — click Use this template, or copy the
directory, to start a project from it. Nothing here is bound to a particular
deployment: mcp.json and .mcp.json ship with a replace-me.example.com
placeholder so a project that forgets to set its own endpoint fails loudly
instead of silently talking to someone else's server.
How the MCP App works
MCP Apps is SEP-1865,
stable since 2026-01-26 and identified as io.modelcontextprotocol/ui. Three
things wire it together:
A resource with a
ui://URI and MIME typetext/html;profile=mcp-appreturns the widget's HTML.A tool points at that resource through
_meta.ui.resourceUri.The host renders the HTML in a sandboxed iframe and speaks MCP JSON-RPC to it over
postMessage.
The widget is not served as a web page. Its HTML travels as text inside the
JSON-RPC response, and the host injects it into an iframe under the host's own
origin. Your deployment is never navigated to — it only answers /mcp.
That is why this repo has two builds and one deployment. Next.js is the server: it terminates Streamable HTTP, runs your tools, and serves resources. The widget is a Vite app compiled into a single self-contained HTML file, which the MCP route reads off disk and returns as the resource.
Inlining everything is deliberate. Host sandboxes enforce a CSP that restricts which origins a widget may load subresources from, and a widget that fetches nothing works on every host regardless of that policy. It also removes the failure mode where a widget renders as readable but unstyled and inert. See DECISIONS.md for the measurements behind this.
app/
mcp/route.ts MCP server — tools + the ui:// resource
widget/
index.html Vite entry
main.tsx React root
app.tsx the widget UI
use-mcp-app.ts the host bridge: tool input, tool result, callTool
styles.css Tailwind
dist/index.html built bundle (generated, gitignored)
vite.config.mts single-file widget build
proxy.ts CORS headers, so browser-based MCP clients can connect
scripts/dev-codex.mjs isolated Codex instance with this plugin installed
scripts/test-client.mjs protocol smoke test
scripts/set-endpoint.mjs writes your deployment URL into both manifests
scripts/capture-seed-thread.mjs captures one of your threads as a seed fixture
scripts/fixtures/ seed threads the isolated instance starts withThe two kinds of tool
The demo registers both, because real MCP Apps need both:
greetis model-visible. The agent calls it, and the host opens the widget.set_toneis declaredvisibility: ["app"], so it is hidden from the model and exists only for the widget to call when the user clicks a button. The widget invokes it throughcallToolfromuseMcpApp().
Related MCP server: skill-deck
Develop
Requires macOS with the Codex desktop app, and Node 22.12+ (Vite 8).
pnpm install
pnpm devpnpm dev is the whole loop. It builds the widget, starts a Vite watcher and the
Next.js dev server on port 3100, installs this repo as the only local plugin in a
throwaway Codex instance, points that plugin at http://127.0.0.1:3100/mcp,
and launches a separate Codex desktop window. Closing the window removes the
isolated state. Your everyday Codex install is untouched.
No tunnel is required, because the widget requests no subresources. Because each
run gets a fresh Codex instance, its widget cache starts empty, so you see the
current bundle without bumping UI_VERSION.
Seed threads
A fresh instance also starts with an empty thread list, which means retyping the
same setup before every test. scripts/fixtures/codex-dev-seed-thread-* holds
conversations that pnpm dev replays into the isolated instance, so they are
waiting in the thread list, ready to continue. The one shipped here establishes
that you are Ada and that greetings go through the app, so "greet me" opens the
widget on the first turn.
Capture your own from a thread you have already had:
pnpm capture-thread --last # or: pnpm capture-thread <thread-id>
pnpm capture-thread <thread-id> --name my-case --description "what it sets up"Capturing rewrites your home directory, Codex home, and working directory into placeholders that the harness substitutes at seed time. It does not rewrite the conversation, so read a fixture before committing it.
For server-only work, or to check the protocol:
pnpm dev:server # Vite watcher + Next.js on port 3000
pnpm test:client # verifies the protocol against localhost:3000pnpm test:client connects as a UI-capable client and asserts the parts that are
easy to get subtly wrong: the extension capability, the ui:// metadata on the
tool, the resource MIME type, that the resource is real HTML, and that the bundle
is self-contained.
Build your own plugin
The work happens in three places:
app/mcp/route.ts— replacegreet/set_tonewith your tools. BumpUI_VERSIONwhenever you ship a widget change, or hosts will serve a cached copy.widget/app.tsx— your widget. Read state fromuseMcpApp().skills/<name>/SKILL.md— teach agents when to call your tools. Thedescriptionis all an agent sees before loading the file, so it has to name concrete triggers.
Rename it for a new project
The boilerplate name survives in five places:
File | What to change |
|
|
|
|
| the |
| directory name and the |
|
|
The endpoint URL is the one thing you can't set until you've deployed, so leave
it and run pnpm set-endpoint afterwards.
Deploy
pnpm build # widget bundle, then Next.js
vercel deploy --prod
pnpm set-endpoint https://your-project.vercel.app # updates both manifests
pnpm test:client https://your-project.vercel.appnext.config.ts lists the widget bundle in outputFileTracingIncludes so the
serverless function can read it at runtime. If you move the bundle, update that
path too, or production will fail to serve the resource.
Install the plugin
The plugin root is the repository root, and both manifests describe the same
package; distribute it as a git repo. Run pnpm set-endpoint first — installing
while the placeholder URL is still in place gives you a plugin whose MCP server
never connects.
Cursor natively supports Agent Plugins. Add the repo through
Customize → Plugins, which reads the root plugin.json.
Codex (0.147+) reads the portable root plugin.json and mcp.json:
codex plugin marketplace add <your-org>/<your-repo>
codex plugin add <plugin-name>@<marketplace>
codex plugin list --jsonClaude Code does not yet parse the Agent Plugins $schema, so it reads the
parallel .claude-plugin/plugin.json and .mcp.json committed here. Its
skills/ discovery is the same directory, so the skill is shared:
claude plugin marketplace add <your-org>/<your-repo>To connect only the MCP server without the plugin wrapper, any host that speaks Streamable HTTP can point at the endpoint directly:
claude mcp add --transport http my-plugin https://your-project.vercel.app/mcpStack
mcp-handler2 — framework-agnostic MCP HTTP adapter, serves the 2026-07-28 protocol natively with a fallback for 2025-era Streamable HTTP clients@modelcontextprotocol/server2 and@modelcontextprotocol/ext-apps2 — MCP SDK v2 plus the MCP Apps helpers (registerAppTool,registerAppResource)Vite 8 with
vite-plugin-singlefileand Tailwind 4 — the widget bundleNext.js 16 on Vercel Fluid Compute — the MCP server
Derived from vercel-labs/mcp-apps-nextjs-starter,
upgraded to the v2 MCP stack, packaged as an Agent Plugin, and moved to a
self-contained widget bundle.
This server cannot be deployed
Maintenance
Related MCP Connectors
Deploy AI-generated HTML/CSS/JS to instant public HTTPS URLs from any MCP-compatible agent.
Give any MCP-compatible AI assistant a builder for live, hosted web tools and workflows.
Host your MCP tool over streamable HTTP in one command.
Build, preview, version and publish small browser apps from coding agents over MCP.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA minimal, dependency-free MCP server that renders interactive widgets (like bar charts) inline in Claude, ChatGPT, and Grok from a custom connector.1MIT
- AlicenseNot gradedqualityDmaintenanceMCP server that adapts, activates, and shares local Agent Skills for runtimes like OpenAI Agents SDK and Claude Code, enabling dynamic discovery and shareable HTML/image artifacts.30 npm2MIT

CustomJS MCP Serverofficial
AlicenseNot gradedqualityDmaintenanceA remote MCP server that enables AI agents to host HTML pages, generate PDFs, capture screenshots, scrape sites, and run sandboxed code using a CustomJS API key.MIT- AlicenseNot gradedqualityBmaintenanceA local MCP server that lets AI agents publish markdown/HTML to a browser viewer with session management, revision diffs, and live updates via a single tool call.2MIT