Official agent skill

MCP App Builder

by anthropics in anthropics/claude-plugins-official

Guides building an MCP app: an MCP server that also serves interactive UI widgets such as forms, pickers and confirm dialogs, rendered inline in chat hosts like Claude and ChatGPT.

OfficialApache-2.0Auto-check passedAgent Workflows

Install MCP App Builder

skills CLI
$ npx skills add anthropics/claude-plugins-official --skill build-mcp-app -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install anthropics/claude-plugins-official build-mcp-app --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/anthropics/claude-plugins-official.git skills-src && mkdir -p .claude/skills && cp -r skills-src/plugins/mcp-server-dev/skills/build-mcp-app .claude/skills/build-mcp-app && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
build-mcp-app
GitHub stars
38k
Token cost
~4.7k tokens
SKILL.md length
1,497 words
Files
7 (incl. references)
Skills in repo
29
Repo updated
First seen
Licence
Apache-2.0

At a glance

Guides building an MCP app: an MCP server that also serves interactive UI widgets such as forms, pickers and confirm dialogs, rendered inline in chat hosts like Claude and ChatGPT.

  • Works in 2 steps: The tool declares a UI resource via… → The resource is registered separately…
  • Building an MCP server whose tools show a form, picker or dashboard in chat
  • SKILL.md covers Claude host specifics, When a widget beats plain text, Widgets vs Elicitation — route… and Architecture: two deployment…, plus 6 more sections
  • Calls npm and npx; reaches esm.sh

What it does

An MCP app is a standard MCP server that additionally serves UI resources, so one build runs in Claude, ChatGPT and any other host that implements the apps surface. The widget layer sits on top of ordinary tools and resources, and the companion `build-mcp-server` skill covers the base layer. The skill lists the Claude-specific `_meta.ui` keys: `resourceUri` for the `ui://` resource a tool renders, `visibility` for widget-only helper tools, `prefersBorder`, and `csp` origin declarations that default to blocking everything.

It stresses that a widget has to earn its place. Add one when a tool needs structured input, the user must choose from a list, a destructive or billable action needs confirmation, the output is visual, or a long job should show progress; otherwise return text. It also asks you to check whether elicitation already covers the need. Reference files cover abuse protection, apps SDK messages, the iframe sandbox, payload budgeting, widget templates and a directory checklist.

When your agent uses it

  • Building an MCP server whose tools show a form, picker or dashboard in chat
  • Adding a confirmation dialog before a destructive or billable tool action
  • Deciding whether a widget or plain elicitation fits a tool
  • Preparing an MCP app for directory submission

Example prompts

  • “Add a file picker widget to my MCP server so users can choose which report to open.”
  • “Build an MCP app with a confirmation dialog before the delete tool runs.”
  • “Does my MCP tool need a widget, or would elicitation be enough?”

Requirements

  • An existing or planned MCP server
  • A custom connector in claude.ai to test, with a Cloudflare tunnel for local dev

Workflow steps

2 steps, taken from the first numbered list in SKILL.md.

  1. The tool declares a UI resource via _meta.ui.resourceUri. Its handler returns plain text/JSON — NOT the HTML.
  2. The resource is registered separately and serves the HTML.

What it can do on your machine

Read from SKILL.md and the folder at commit b8e53f1. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • npm
    • npx

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • esm.sh

    Also links to:

    • claude.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

MCP App Builder loads about 4.7k tokens when it runs, and up to ~12k if it reads all its reference files. Until then it costs about 117 tokens; SKILL.md has 1,497 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~117
When it runs · the whole SKILL.md, loaded when a task matches
~4.7k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~12k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from anthropics/claude-plugins-official at commit b8e53f1, republished under its Apache-2.0 licence (© anthropics). 1,497 words, ~4,743 tokens.

Download SKILL.mdSave it as .claude/skills/build-mcp-app/SKILL.md (or your agent's skills folder). This skill also uses 6 other files; get the full folder from GitHub.
name
build-mcp-app
description
This skill should be used when the user wants to build an "MCP app", add "interactive UI" or "widgets" to an MCP server, "render components in chat", build "MCP UI resources", make a tool that shows a "form", "picker", "dashboard" or "confirmation dialog" inline in the conversation, or mentions "apps SDK" in the context of MCP. Use AFTER the build-mcp-server skill has settled the deployment model, or when the user already knows they want UI widgets.
version
0.1.0

Build an MCP App (Interactive UI Widgets)

An MCP app is a standard MCP server that also serves UI resources — interactive components rendered inline in the chat surface. Build once, runs in Claude and ChatGPT and any other host that implements the apps surface.

The UI layer is additive. Under the hood it's still tools, resources, and the same wire protocol. If you haven't built a plain MCP server before, the build-mcp-server skill covers the base layer. This skill adds widgets on top.

Testing in Claude: Add the server as a custom connector in claude.ai (via a Cloudflare tunnel for local dev) — this exercises the real iframe sandbox and hostContext. See https://claude.com/docs/connectors/building/testing.

Claude host specifics

_meta.ui.* keyWhereEffect
resourceUritoolWhich ui:// resource the host renders for this tool's results.
visibility: ["app"]toolHide a widget-only helper tool (e.g. geometry/image fetcher called via callServerTool) from Claude's tool list.
prefersBorder: falseresourceDrop the host's outer card border (mobile).
csp.{connectDomains, resourceDomains, baseUriDomains}resourceDeclare external origins; default is block-all. frameDomains is currently restricted in Claude.
  • hostContext.safeAreaInsets: {top, right, bottom, left} (px) — honor these for notches and the composer overlay.
  • Directory submission requires OAuth or authless (none) — static bearer is private-deploy only and blocks listing — plus tool annotations and 3–5 PNG screenshots; see references/directory-checklist.md.

When a widget beats plain text

Don't add UI for its own sake — most tools are fine returning text or JSON. Add a widget when one of these is true:

SignalWidget type
Tool needs structured input Claude can't reliably inferForm
User must pick from a list Claude can't rank (files, contacts, records)Picker / table
Destructive or billable action needs explicit confirmationConfirm dialog
Output is spatial or visual (charts, maps, diffs, previews)Display widget
Long-running job the user wants to watchProgress / live status

If none apply, skip the widget. Text is faster to build and faster for the user.


Widgets vs Elicitation — route correctly

Before building a widget, check if elicitation covers it. Elicitation is spec-native, zero UI code, works in any compliant host.

NeedElicitationWidget
Confirm yes/no✅overkill
Pick from short enum✅overkill
Fill a flat form (name, email, date)✅overkill
Pick from a large/searchable list❌ (no scroll/search)✅
Visual preview before choosing❌✅
Chart / map / diff view❌✅
Live-updating progress❌✅

If elicitation covers it, use it. See ../build-mcp-server/references/elicitation.md.


Architecture: two deployment shapes

Remote MCP app (most common)

Hosted streamable-HTTP server. Widget templates are served as resources; tool results reference them. The host fetches the resource, renders it in an iframe sandbox, and brokers messages between the widget and Claude.

┌──────────┐  tools/call   ┌────────────┐
│  Claude  │─────────────> │ MCP server │
│   host   │<── result ────│  (remote)  │
│          │  + widget ref │            │
│          │               │            │
│          │ resources/read│            │
│          │─────────────> │  widget    │
│ ┌──────┐ │<── template ──│  HTML/JS   │
│ │iframe│ │               └────────────┘
│ │widget│ │
│ └──────┘ │
└──────────┘
MCPB-packaged MCP app (local + UI)

Same widget mechanism, but the server runs locally inside an MCPB bundle. Use this when the widget needs to drive a local application — e.g., a file picker that browses the actual local disk, a dialog that controls a desktop app.

For MCPB packaging mechanics, defer to the build-mcpb skill. Everything below applies to both shapes.


How widgets attach to tools

A widget-enabled tool has two separate registrations:

  1. The tool declares a UI resource via _meta.ui.resourceUri. Its handler returns plain text/JSON — NOT the HTML.
  2. The resource is registered separately and serves the HTML.

When Claude calls the tool, the host sees _meta.ui.resourceUri, fetches that resource, renders it in an iframe, and pipes the tool's return value into the iframe via the ontoolresult event.

typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE }
  from "@modelcontextprotocol/ext-apps/server";
import { z } from "zod";

const server = new McpServer({ name: "contacts", version: "1.0.0" });

// 1. The tool — returns DATA, declares which UI to show
registerAppTool(server, "pick_contact", {
  description: "Open an interactive contact picker",
  annotations: { title: "Pick Contact", readOnlyHint: true },
  inputSchema: { filter: z.string().optional() },
  _meta: { ui: { resourceUri: "ui://widgets/contact-picker.html" } },
}, async ({ filter }) => {
  const contacts = await db.contacts.search(filter);
  // Plain JSON — the widget receives this via ontoolresult
  return { content: [{ type: "text", text: JSON.stringify(contacts) }] };
});

// 2. The resource — serves the HTML
registerAppResource(
  server,
  "Contact Picker",
  "ui://widgets/contact-picker.html",
  {},
  async () => ({
    contents: [{
      uri: "ui://widgets/contact-picker.html",
      mimeType: RESOURCE_MIME_TYPE,
      text: pickerHtml,  // your HTML string
    }],
  }),
);

The URI scheme ui:// is convention. The mime type MUST be RESOURCE_MIME_TYPE ("text/html;profile=mcp-app") — this is how the host knows to render it as an interactive iframe, not just display the source.


Widget runtime — the App class

Inside the iframe, your script talks to the host via the App class from @modelcontextprotocol/ext-apps. This is a persistent bidirectional connection — the widget stays alive as long as the conversation is active, receiving new tool results and sending user actions.

html
<script type="module">
  /* ext-apps bundle inlined at build time → globalThis.ExtApps */
  /*__EXT_APPS_BUNDLE__*/
  const { App } = globalThis.ExtApps;

  const app = new App({ name: "ContactPicker", version: "1.0.0" }, {});

  // Set handlers BEFORE connecting
  app.ontoolresult = ({ content }) => {
    const contacts = JSON.parse(content[0].text);
    render(contacts);
  };

  await app.connect();

  // Later, when the user clicks something:
  function onPick(contact) {
    app.sendMessage({
      role: "user",
      content: [{ type: "text", text: `Selected contact: ${contact.id}` }],
    });
  }
</script>

The /*__EXT_APPS_BUNDLE__*/ placeholder gets replaced by the server at startup with the contents of @modelcontextprotocol/ext-apps/app-with-deps — see references/iframe-sandbox.md for why this is necessary and the rewrite snippet. Do not import { App } from "https://esm.sh/..."; the iframe's CSP blocks the transitive dependency fetches and the widget renders blank.

MethodDirectionUse for
app.ontoolresult = fnHost → widgetReceive the tool's return value
app.ontoolinput = fnHost → widgetReceive the tool's input args (what Claude passed)
app.sendMessage({...})Widget → hostInject a message into the conversation
app.updateModelContext({...})Widget → hostUpdate context silently (no visible message)
app.callServerTool({name, arguments})Widget → serverCall another tool on your server
app.openLink({url})Widget → hostOpen a URL in a new tab (sandbox blocks window.open)
app.getHostContext() / app.onhostcontextchangedHost → widgetTheme, host CSS vars, containerDimensions, displayMode, deviceCapabilities
app.requestDisplayMode({mode})Widget → hostAsk for inline / pip / fullscreen
app.downloadFile({name, mimeType, content})Widget → hostHost-mediated download (base64 content)
new App(info, caps, {autoResize: true})—Iframe height tracks rendered content

sendMessage is the typical "user picked something, tell Claude" path. updateModelContext is for state that Claude should know about but shouldn't clutter the chat. openLink is required for any outbound navigation — window.open and <a target="_blank"> are blocked by the sandbox attribute.

What widgets cannot do:

  • Access the host page's DOM, cookies, or storage
  • Make network calls to arbitrary origins (CSP-restricted — route through callServerTool)
  • Open popups or navigate directly — use app.openLink({url})
  • Load remote images reliably — inline as data: URLs server-side

Keep widgets small and single-purpose. A picker picks. A chart displays. Don't build a whole sub-app inside the iframe — split it into multiple tools with focused widgets.


Show full SKILL.md (612 more words)Show less

Scaffold: minimal picker widget

Install:

bash
npm install @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps zod express

Server (src/server.ts):

typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE }
  from "@modelcontextprotocol/ext-apps/server";
import express from "express";
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { z } from "zod";

const require = createRequire(import.meta.url);
const server = new McpServer({ name: "contact-picker", version: "1.0.0" });

// Inline the ext-apps browser bundle into the widget HTML.
// The iframe CSP blocks CDN script fetches — bundling is mandatory.
const bundle = readFileSync(
  require.resolve("@modelcontextprotocol/ext-apps/app-with-deps"), "utf8",
).replace(/export\{([^}]+)\};?\s*$/, (_, body) =>
  "globalThis.ExtApps={" +
  body.split(",").map((p) => {
    const [local, exported] = p.split(" as ").map((s) => s.trim());
    return `${exported ?? local}:${local}`;
  }).join(",") + "};",
);
const pickerHtml = readFileSync("./widgets/picker.html", "utf8")
  .replace("/*__EXT_APPS_BUNDLE__*/", () => bundle);

registerAppTool(server, "pick_contact", {
  description: "Open an interactive contact picker. User selects one contact.",
  annotations: { title: "Pick Contact", readOnlyHint: true },
  inputSchema: { filter: z.string().optional().describe("Name/email prefix filter") },
  _meta: { ui: { resourceUri: "ui://widgets/picker.html" } },
}, async ({ filter }) => {
  const contacts = await db.contacts.search(filter ?? "");
  return { content: [{ type: "text", text: JSON.stringify(contacts) }] };
});

registerAppResource(server, "Contact Picker", "ui://widgets/picker.html", {},
  async () => ({
    contents: [{ uri: "ui://widgets/picker.html", mimeType: RESOURCE_MIME_TYPE, text: pickerHtml }],
  }),
);

const app = express();
app.use(express.json());
app.post("/mcp", async (req, res) => {
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
  res.on("close", () => transport.close());
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});
app.listen(process.env.PORT ?? 3000);

For local-only widget apps (driving a desktop app, reading local files), swap the transport to StdioServerTransport and package via the build-mcpb skill.

Widget (widgets/picker.html):

html
<!doctype html>
<meta charset="utf-8" />
<style>
  body { font: 14px system-ui; margin: 0; }
  ul { list-style: none; padding: 0; margin: 0; max-height: 300px; overflow-y: auto; }
  li { padding: 10px 14px; cursor: pointer; border-bottom: 1px solid #eee; }
  li:hover { background: #f5f5f5; }
  .sub { color: #666; font-size: 12px; }
</style>
<ul id="list"></ul>
<script type="module">
/*__EXT_APPS_BUNDLE__*/
const { App } = globalThis.ExtApps;
(async () => {
  const app = new App({ name: "ContactPicker", version: "1.0.0" }, {});
  const ul = document.getElementById("list");

  app.ontoolresult = ({ content }) => {
    const contacts = JSON.parse(content[0].text);
    ul.innerHTML = "";
    for (const c of contacts) {
      const li = document.createElement("li");
      li.innerHTML = `<div>${c.name}</div><div class="sub">${c.email}</div>`;
      li.addEventListener("click", () => {
        app.sendMessage({
          role: "user",
          content: [{ type: "text", text: `Selected contact: ${c.id} (${c.name})` }],
        });
      });
      ul.append(li);
    }
  };

  await app.connect();
})();
</script>

See references/widget-templates.md for more widget shapes.


Design notes that save you a rewrite

One widget per tool. Resist the urge to build one mega-widget that does everything. One tool → one focused widget → one clear result shape. Claude reasons about these far better.

Tool description must mention the widget. Claude only sees the tool description when deciding what to call. "Opens an interactive picker" in the description is what makes Claude reach for it instead of guessing an ID.

Widgets are optional at runtime. Hosts that don't support the apps surface simply ignore _meta.ui and render the tool's text content normally. Since your tool handler already returns meaningful text/JSON (the widget's data), degradation is automatic — Claude sees the data directly instead of via the widget.

Don't block on widget results for read-only tools. A widget that just displays data (chart, preview) shouldn't require a user action to complete. Return the display widget and a text summary in the same result so Claude can continue reasoning without waiting.

Layout-fork by item count, not by tool count. If one use case is "show one result in detail" and another is "show many results side-by-side", don't make two tools — make one tool that accepts items[], and let the widget pick a layout: items.length === 1 → detail view, > 1 → carousel. Keeps the server schema simple and lets Claude decide count naturally.

Put Claude's reasoning in the payload. A short note field on each item (why Claude picked it) rendered as a callout on the card gives users the reasoning inline with the choice. Mention this field in the tool description so Claude populates it.

Normalize image shapes server-side. If your data source returns images with wildly varying aspect ratios, rewrite to a predictable variant (e.g. square-bounded) before fetching for the data-URL inline. Then give the widget's image container a fixed aspect-ratio + object-fit: contain so everything sits centered.

Follow host theme. app.getHostContext()?.theme (after connect()) plus app.onhostcontextchanged for live updates. Toggle a .dark class on <html>, keep colors in CSS custom props with a :root.dark {} override block, set color-scheme. Disable mix-blend-mode: multiply in dark — it makes images vanish.


Testing

Claude Desktop — current builds still require the command/args config shape (no native "type": "http"). Wrap with mcp-remote and force http-only transport so the SSE probe doesn't swallow widget-capability negotiation:

json
{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:3000/mcp",
               "--allow-http", "--transport", "http-only"]
    }
  }
}

Desktop caches UI resources aggressively. After editing widget HTML, fully quit (⌘Q / Alt+F4, not window-close) and relaunch to force a cold resource re-fetch.

Headless JSON-RPC loop — fast iteration without clicking through Desktop:

bash
# test.jsonl — one JSON-RPC message per line
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"your_tool","arguments":{...}}}

(cat test.jsonl; sleep 10) | npx mcp-remote http://localhost:3000/mcp --allow-http

The sleep keeps stdin open long enough to collect all responses. Parse the jsonl output with jq or a Python one-liner.

Widget dev loop — avoid the ⌘Q-relaunch cycle entirely by serving the inlined widget HTML at a plain GET route with a fake ExtApps shim that fires ontoolresult from a query param:

ts
app.get("/widget-preview", (_req, res) => {
  const shim = `globalThis.ExtApps={applyHostStyleVariables:()=>{},App:class{
    constructor(){this.h={}} ontoolresult;onhostcontextchanged;
    async connect(){const p=new URLSearchParams(location.search).get("payload");
      if(p)this.ontoolresult?.({content:[{type:"text",text:p}]});}
    getHostContext(){return{theme:"light"}}
    sendMessage(m){console.log("sendMessage",m)} updateModelContext(){}
    callServerTool(){return Promise.resolve({content:[]})} openLink(){} downloadFile(){}
  }};`;
  res.type("html").send(widgetHtml.replace("/*__EXT_APPS_BUNDLE__*/", shim));
});

Open http://localhost:3000/widget-preview?payload={"rows":[...]} in a normal browser tab and iterate with ordinary devtools.

Host fallback — use a host without the apps surface (or MCP Inspector) and confirm the tool's text content degrades gracefully.

CSP debugging — open the iframe's own devtools console. CSP violations are the #1 reason widgets silently fail (blank rectangle, no error in the main console). See references/iframe-sandbox.md.


Reference files

  • references/iframe-sandbox.md — CSP/sandbox constraints, the bundle-inlining pattern, image handling, host theming
  • references/widget-templates.md — reusable HTML scaffolds for picker / confirm / progress / display
  • references/apps-sdk-messages.md — the App class API: widget ↔ host ↔ server messaging, lifecycle & supersession
  • references/payload-budgeting.md — host tool-result size caps, prune-then-truncate, heavy assets via callServerTool
  • references/abuse-protection.md — Anthropic egress CIDRs, tiered rate limiting, trust proxy, response caching
  • references/directory-checklist.md — pre-flight for connector-directory submission

© anthropics, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 6 other files (references) in plugins/mcp-server-dev/skills/build-mcp-app of anthropics/claude-plugins-official.

  • SKILL.md
  • references/abuse-protection.md
  • references/apps-sdk-messages.md
  • references/directory-checklist.md
  • references/iframe-sandbox.md
  • references/payload-budgeting.md
  • references/widget-templates.md

Open the folder on GitHubat commit b8e53f1

Compare with similar skills

MCP App Builder next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

MCP App Builder compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
MCP App Builder this skillanthropics/claude-plugins-official38k—~4.7kAutomated safety check: PassApache-2.0
Control Chromewxtsky/byob143—~1.5kAutomated safety check: WarnMIT
Codex with ChatGPT Planning LoopXiaoDuoYa/codex-with-chatgpt7.2k—~11kAutomated safety check: NotesMIT
Cao MCP Appsawslabs/cli-agent-orchestrator1.4k—~1.9kAutomated safety check: PassApache-2.0
Chatgpt AppsHaohao-end/openagent7911 repos~4.9kAutomated safety check: PassApache-2.0
Chatgpt App Builderalpic-ai/skybridge2.2k—~1kAutomated safety check: PassMIT

Similar skills

  • Control Chrome

    wxtsky/byob

    Control and inspect the user's real Google Chrome through byob's local MCP tools.

    143 GitHub stars~1.5k tokensUpdated 11 days ago
    Agent WorkflowsAuto-check: warnings
  • Codex with ChatGPT Planning Loop

    XiaoDuoYa/codex-with-chatgpt

    Uses ChatGPT in the browser as the planning and review brain for a Codex session, with Codex keeping all execution and ChatGPT reading the workspace through a bridge.

    7.2k GitHub stars~11k tokensUpdated 9 days ago
    Agent WorkflowsAuto-check: notes
  • Cao MCP Apps

    awslabs/cli-agent-orchestrator

    Official

    Enable, operate, and extend CAO's MCP Apps surface — the host-rendered fleet dashboard visible inside MCP App hosts (Claude Desktop, ChatGPT, VS Code Copilot, Goose, Postman).

    1.4k GitHub stars~1.9k tokensUpdated today
    Agent WorkflowsAuto-check passed
  • Chatgpt Apps

    Haohao-end/openagent

    Build, scaffold, refactor, and troubleshoot ChatGPT Apps SDK applications that combine an MCP server and widget UI.

    791 GitHub starsUsed in 1 repo~4.9k tokens
    Agent WorkflowsAuto-check passed
  • Chatgpt App Builder

    alpic-ai/skybridge

    Guide developers through creating and updating ChatGPT plugins.

    2.2k GitHub stars~1k tokensUpdated 2 days ago
    Agent WorkflowsAuto-check passed
  • Agent QA Authoring

    vostride/agent-qa

    A skill your agent uses when creating, editing, validating, or running agent-qa tests, suites, or hooks.

    904 GitHub stars~569 tokensUpdated 2 mo ago
    Agent WorkflowsAuto-check passed

More from anthropics/claude-plugins-official

All 29 skills in this repo
  • Hook Development for Claude Code Plugins

    anthropics/claude-plugins-official

    Official

    Explains how to write Claude Code plugin hooks, both prompt-based checks and bash commands, for events such as PreToolUse, Stop and SessionStart.

    38k GitHub starsUsed in 10 repos~4.1k tokens
    Auto-check: notes
  • Claude Code Agent Development

    anthropics/claude-plugins-official

    Official

    Explains how to write agents for Claude Code plugins: the markdown file with YAML frontmatter, trigger descriptions, model and color settings, and system prompt design.

    38k GitHub starsUsed in 7 repos~2.8k tokens
    Auto-check passed
  • Plugin Settings Pattern

    anthropics/claude-plugins-official

    Official

    Shows how Claude Code plugins keep per-project settings and state in .claude/plugin-name.local.md files with YAML frontmatter and a markdown body.

    38k GitHub starsUsed in 7 repos~3k tokens
    Auto-check passed
  • MCP Integration for Plugins

    anthropics/claude-plugins-official

    Official

    Explains how to bundle Model Context Protocol servers in a Claude Code plugin, covering config files, stdio, SSE, HTTP and WebSocket server types, and authentication.

    38k GitHub starsUsed in 11 repos~3.1k tokens
    Auto-check passed
  • Claude Code Command Development

    anthropics/claude-plugins-official

    Official

    Explains how to write Claude Code slash commands: Markdown files with YAML frontmatter, arguments, file references, bash context and interactive prompts.

    38k GitHub starsUsed in 10 repos~4.8k tokens
    Auto-check passed
  • Claude Code Plugin Structure

    anthropics/claude-plugins-official

    Official

    Explains the directory layout, plugin.json manifest and component organization of a Claude Code plugin, including auto-discovery and portable paths.

    38k GitHub starsUsed in 10 repos~3.4k tokens
    Auto-check passed

Questions about MCP App Builder

What does MCP App Builder do?

Guides building an MCP app: an MCP server that also serves interactive UI widgets such as forms, pickers and confirm dialogs, rendered inline in chat hosts like Claude and ChatGPT. An MCP app is a standard MCP server that additionally serves UI resources, so one build runs in Claude, ChatGPT and any other host that implements the apps surface. The widget layer sits on top of ordinary tools and resources, and the companion `build-mcp-server` skill covers the base layer.

When should I use MCP App Builder?

MCP App Builder fits situations like: building an MCP server whose tools show a form, picker or dashboard in chat; adding a confirmation dialog before a destructive or billable tool action; deciding whether a widget or plain elicitation fits a tool; preparing an MCP app for directory submission.

How do I install MCP App Builder in Claude Code?

Run `npx skills add anthropics/claude-plugins-official --skill build-mcp-app -a claude-code`. Or copy the skill folder (plugins/mcp-server-dev/skills/build-mcp-app in anthropics/claude-plugins-official) into .claude/skills/build-mcp-app in your project. Claude Code loads it when a task matches its description.

How do I install MCP App Builder in Codex?

Run `npx skills add anthropics/claude-plugins-official --skill build-mcp-app -a codex`. Or copy the skill folder (plugins/mcp-server-dev/skills/build-mcp-app in anthropics/claude-plugins-official) into .agents/skills/build-mcp-app in your project. Codex loads it when a task matches its description.

Can I use MCP App Builder in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add anthropics/claude-plugins-official --skill build-mcp-app -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/build-mcp-app, .gemini/skills/build-mcp-app, .github/skills/build-mcp-app and .opencode/skills/build-mcp-app in your project.

What does MCP App Builder need to run?

Going by SKILL.md and its folder, MCP App Builder needs the command-line tools its instructions call (npm and npx). Our summary lists: An existing or planned MCP server; A custom connector in claude.ai to test, with a Cloudflare tunnel for local dev.

Does MCP App Builder access the network?

SKILL.md names 2 domains. In commands or code: esm.sh; the agent is likely to contact it when it follows the instructions. As links in the text: claude.com. This is read from the text; nothing was executed.

Is MCP App Builder safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does MCP App Builder use?

MCP App Builder is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does MCP App Builder use?

About 4.7k tokens (SKILL.md is roughly 19k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 7.3k tokens, read only when the agent opens those files.

What are the alternatives to MCP App Builder?

Skills that share tags, products or a category with MCP App Builder: Control Chrome (wxtsky/byob, 143 stars), Codex with ChatGPT Planning Loop (XiaoDuoYa/codex-with-chatgpt, 7.2k stars), Cao MCP Apps (awslabs/cli-agent-orchestrator, 1.4k stars) and Chatgpt Apps (Haohao-end/openagent, 791 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains MCP App Builder?

anthropics (a GitHub organization, an official publisher) maintains it in anthropics/claude-plugins-official, which has 37,597 GitHub stars. The repository holds 29 skills in this directory. The repository was last updated on October 9, 2026.

Source: anthropics/claude-plugins-official on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.