mcp-app-boilerplate
by dzhunka
README.md
# 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 | `app/mcp/route.ts` + `app/page.tsx` | Next.js app serving a remote MCP server and the widget UI |
| Plugin package | `plugin.json`, `mcp.json`, `skills/` | [Agent Plugins 1.0.0](https://agent-plugins.org/specification) 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.
A reference deployment of this boilerplate runs at
<https://mcp-app-boilerplate.vercel.app> (MCP endpoint at `/mcp`). It exists so
`pnpm test:client` has something to check against; your project should point at
its own.
## How the MCP App works
MCP Apps is [SEP-1865](https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp),
stable since 2026-01-26 and identified as `io.modelcontextprotocol/ui`. Three
things wire it together:
1. A resource with a `ui://` URI and MIME type `text/html;profile=mcp-app`
returns the widget's HTML.
2. A tool points at that resource through `_meta.ui.resourceUri`.
3. The host renders the HTML in a sandboxed iframe and speaks MCP JSON-RPC to it
over `postMessage`.
The trick that makes this pleasant to work on: the widget is an **ordinary
Next.js page**, and the MCP route fetches its own rendered HTML to serve as the
resource. You build the UI with the full framework instead of hand-writing an
HTML string.
```
app/
mcp/route.ts MCP server — tools + the ui:// resource
page.tsx the widget (also viewable as a normal web page)
about/page.tsx proves client-side navigation survives the iframe
hooks/use-mcp-app.ts the host bridge: tool input, tool result, callTool
layout.tsx iframe hardening (asset URLs, history, fetch patches)
baseUrl.ts resolves the public origin (local, tunnel, or Vercel)
proxy.ts CORS headers so the iframe can load cross-origin assets
scripts/test-client.mjs protocol smoke test
scripts/set-endpoint.mjs writes your deployment URL into both manifests
```
### The two kinds of tool
The demo registers both, because real MCP Apps need both:
- **`greet`** is model-visible. The agent calls it, and the host opens the widget.
- **`set_tone`** is declared `visibility: ["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 through `callTool` from `useMcpApp()`.
## Rename it for a new project
The boilerplate name survives in five places:
| File | What to change |
| --- | --- |
| `plugin.json` | `name`, `description` |
| `.claude-plugin/plugin.json` | `name`, `description` |
| `mcp.json` and `.mcp.json` | the `mcpServers` key |
| `skills/mcp-app-boilerplate/` | directory name and the `name` frontmatter — they must match, or the skill is rejected |
| `app/mcp/route.ts`, `package.json` | `serverInfo.name`, `name` |
The endpoint URL is the one thing you can't set until you've deployed, so leave
it and run `pnpm set-endpoint` afterwards.
## Build your own plugin
After that, the work happens in three places:
1. **`app/mcp/route.ts`** — replace `greet`/`set_tone` with your tools. Bump
`UI_VERSION` whenever the widget changes, or hosts will serve a cached copy.
2. **`app/page.tsx`** — your widget. Read state from `useMcpApp()`.
3. **`skills/<name>/SKILL.md`** — teach agents when to call your tools. The
`description` is all an agent sees before loading the file, so it has to name
concrete triggers.
## Develop
```sh
pnpm install
pnpm dev
pnpm test:client # verifies the protocol against localhost:3000
```
`pnpm 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, and that the resource is real HTML.
To try it inside a real host while developing, expose a public HTTPS origin and
tell the app about it, since hosts must be able to fetch the widget's assets:
```sh
cloudflared tunnel --url http://localhost:3000 # or ngrok
BASE_URL=https://your-tunnel.example pnpm dev
```
## Deploy
```sh
vercel deploy --prod
pnpm set-endpoint https://your-project.vercel.app # updates both manifests
pnpm test:client https://your-project.vercel.app
```
`baseUrl.ts` derives the public origin from Vercel's environment variables, so
the resource CSP and asset prefix resolve correctly with no configuration — the
only URL you state by hand is the one in the manifests, and `set-endpoint`
writes that.
## 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`:
```sh
codex plugin marketplace add <your-org>/<your-repo>
codex plugin add <plugin-name>@<marketplace>
codex plugin list --json
```
**Claude 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:
```sh
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:
```sh
claude mcp add --transport http my-plugin https://your-project.vercel.app/mcp
```
## Stack
- `mcp-handler` 2 — framework-agnostic MCP HTTP adapter, serves the 2026-07-28
protocol natively with a fallback for 2025-era Streamable HTTP clients
- `@modelcontextprotocol/server` 2 and `@modelcontextprotocol/ext-apps` 2 —
MCP SDK v2 plus the MCP Apps helpers (`registerAppTool`, `registerAppResource`)
- Next.js 16 on Vercel Fluid Compute
Derived from [`vercel-labs/mcp-apps-nextjs-starter`](https://github.com/vercel-labs/mcp-apps-nextjs-starter),
upgraded to the v2 MCP stack and packaged as an Agent Plugin.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues