Mcp-use mcp-apps-builder
git clone https://github.com/mcp-use/mcp-use
T=$(mktemp -d) && git clone --depth=1 https://github.com/mcp-use/mcp-use "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/mcp-apps-builder" ~/.claude/skills/mcp-use-mcp-use-mcp-apps-builder && rm -rf "$T"
skills/mcp-apps-builder/SKILL.mdIMPORTANT: How to Use This Skill
This file provides a NAVIGATION GUIDE ONLY. Before implementing any MCP server features, you MUST:
- Read this overview to understand which reference files are relevant
- ALWAYS read the specific reference file(s) for the features you're implementing
- Apply the detailed patterns from those files to your implementation
Do NOT rely solely on the quick reference examples in this file - they are minimal examples only. The reference files contain critical best practices, security considerations, and advanced patterns.
MCP Server Best Practices
Comprehensive guide for building production-ready MCP servers with tools, resources, prompts, and widgets using mcp-use.
⚠️ FIRST: New Project or Existing Project?
Before doing anything else, determine whether you are inside an existing mcp-use project.
Detection: Check the workspace for a
package.json that lists "mcp-use" as a dependency, OR any .ts file that imports from "mcp-use/server".
├─ mcp-use project FOUND → Do NOT scaffold. You are already in a project. │ └─ Skip to "Quick Navigation" below to add features. │ ├─ NO mcp-use project (empty dir, unrelated project, or greenfield) │ └─ Scaffold first with npx create-mcp-use-app, then add features. │ See "Scaffolding a New Project" below. │ └─ Inside an UNRELATED project (e.g. Next.js app) and user wants an MCP server └─ Ask the user where to create it, then scaffold in that directory. Do NOT scaffold inside an existing unrelated project root.
NEVER manually create
boilerplate, MCPServer
, or project structure by hand. The CLI sets up TypeScript config, dev scripts, inspector integration, hot reload, and widget compilation that are difficult to replicate manually.package.json
Scaffolding a New Project
npx create-mcp-use-app my-server cd my-server npm run dev
For full scaffolding details and CLI flags, see quickstart.md.
Quick Navigation
Choose your path based on what you're building:
🚀 Foundations
When: ALWAYS read these first when starting MCP work in a new conversation. Reference later for architecture/concept clarification.
- concepts.md - MCP primitives (Tool, Resource, Prompt, Widget) and when to use each
- architecture.md - Server structure (Hono-based), middleware system, server.use() vs server.app
- quickstart.md - Scaffolding, setup, and first tool example
- deployment.md - Deploying to Manufact Cloud, self-hosting, Docker, managing deployments
Load these before diving into tools/resources/widgets sections.
🔐 Adding Authentication?
When: Protecting your server with OAuth (WorkOS, Supabase, Better Auth, or custom)
-
- When: First time adding auth, understanding
, or choosing a providerctx.auth - Covers:
config, user context shape, provider comparison, common mistakesoauth
- When: First time adding auth, understanding
-
- When: Using WorkOS AuthKit for authentication
- Covers: Setup, env vars, DCR vs pre-registered, roles/permissions, WorkOS API calls
-
- When: Using Supabase for authentication
- Covers: Setup, env vars, HS256 vs ES256, RLS-aware API calls
-
- When: Using Better Auth with the
plugin (self-hosted OAuth 2.1)@better-auth/oauth-provider - Covers:
, auth URL / metadata routes, login and consent flowsoauthBetterAuthProvider
- When: Using Better Auth with the
-
- When: Using any other identity provider (GitHub, Okta, Azure AD, Google, etc.)
- Covers: Custom verification, user info extraction, provider examples
🔧 Building Server Backend (No UI)?
When: Implementing MCP features (actions, data, templates). Read the specific file for the primitive you're building.
-
- When: Creating backend actions the AI can call (send-email, fetch-data, create-user)
- Covers: Tool definition, schemas, annotations, context, error handling
-
- When: Exposing read-only data clients can fetch (config, user profiles, documentation)
- Covers: Static resources, dynamic resources, parameterized resource templates, URI completion
-
- When: Creating reusable message templates for AI interactions (code-review, summarize)
- Covers: Prompt definition, parameterization, argument completion, prompt best practices
-
- When: Formatting responses from tools/resources (text, JSON, markdown, images, errors)
- Covers:
,text()
,object()
,markdown()
,image()
,error()mix()
-
- When: Composing multiple MCP servers into one unified aggregator server
- Covers:
, config API, explicit sessions, sampling routingserver.proxy()
-
- When: Adding cross-cutting logic (logging, auth checks, rate limiting, tool filtering) that spans multiple tools/resources
- Covers:
middleware,server.use('mcp:...')
(method, params, auth, state), pattern matching, HTTP vs MCP middlewareMiddlewareContext
🎨 Building Visual Widgets (Interactive UI)?
When: Creating React-based visual interfaces for browsing, comparing, or selecting data
-
- When: Creating your first widget or adding UI to an existing tool
- Covers: Widget setup,
hook,useWidget()
checks, props handlingisPending
-
- When: Managing UI state (selections, filters, tabs) within widgets
- Covers:
,useState
, state persistence, when to use tool vs widget statesetState
-
- When: Adding buttons, forms, or calling tools from within widgets
- Covers:
, form handling, action buttons, optimistic updatesuseCallTool()
-
- When: Styling widgets to support themes, responsive layouts, or accessibility
- Covers:
, light/dark mode,useWidgetTheme()
, layout patterns, CSS best practicesautoSize
-
- When: Building complex widgets with async data, error boundaries, or performance optimizations
- Covers: Loading states, error handling, memoization, code splitting
-
- When: Keeping the AI model aware of what the user is currently seeing (active tab, hovered item, selected product) without requiring tool calls
- Covers:
component,<ModelContext>
imperative API, nesting, tree serialization, lifecycle rulesmodelContext.set/remove
-
- When: Uploading or downloading files from within a widget (ChatGPT Apps SDK only)
- Covers:
hook,useFiles()
guard, model visibility (isSupported
), storingmodelVisible
, temporary download URLsfileId
📚 Need Complete Examples?
When: You want to see full implementations of common use cases
- common-patterns.md
- End-to-end examples: weather app, todo list, recipe browser
- Shows: Server code + widget code + best practices in context
Decision Tree
What do you need? ├─ New project from scratch │ └─> quickstart.md (scaffolding + setup) │ ├─ OAuth / user authentication │ └─> authentication/overview.md → provider-specific guide │ ├─ Simple backend action (no UI) │ └─> Use Tool: server/tools.md │ ├─ Read-only data for clients │ └─> Use Resource: server/resources.md │ ├─ Reusable prompt template │ └─> Use Prompt: server/prompts.md │ ├─ Cross-cutting logic (logging, auth checks, rate limiting, tool filtering) │ └─> Use Middleware: architecture.md#mcp-middleware │ ├─ Visual/interactive UI │ └─> Use Widget: widgets/basics.md │ ├─ Keep model aware of what user is seeing in widget │ └─> widgets/model-context.md ├─ Upload/download files in a widget │ └─> widgets/files.md (ChatGPT Apps SDK only) │ └─ Deploy to production └─> deployment.md (cloud deploy, self-hosting, Docker)
Core Principles
- Tools for actions - Backend operations with input/output
- Resources for data - Read-only data clients can fetch
- Prompts for templates - Reusable message templates
- Widgets for UI - Visual interfaces when helpful
- Mock data first - Prototype quickly, connect APIs later
❌ Common Mistakes
Avoid these anti-patterns found in production MCP servers:
Tool Definition
- ❌ Returning raw objects instead of using response helpers
- ✅ Use
,text()
,object()
,widget()
helperserror()
- ✅ Use
- ❌ Skipping Zod schema
on every field.describe()- ✅ Add descriptions to all schema fields for better AI understanding
- ❌ No input validation or sanitization
- ✅ Validate inputs with Zod, sanitize user-provided data
- ❌ Throwing errors instead of returning
helpererror()- ✅ Use
for graceful error responseserror("message")
- ✅ Use
Widget Development
- ❌ Accessing
without checkingpropsisPending- ✅ Always check
if (isPending) return <Loading/>
- ✅ Always check
- ❌ Widget handles server state (filters, selections)
- ✅ Widgets manage their own UI state with
useState
- ✅ Widgets manage their own UI state with
- ❌ Missing
wrapper orMcpUseProviderautoSize- ✅ Wrap root component:
<McpUseProvider autoSize>
- ✅ Wrap root component:
- ❌ Inline styles without theme awareness
- ✅ Use
for light/dark mode supportuseWidgetTheme()
- ✅ Use
Security & Production
- ❌ Hardcoded API keys or secrets in code
- ✅ Use
, document inprocess.env.API_KEY.env.example
- ✅ Use
- ❌ No error handling in tool handlers
- ✅ Wrap in try/catch, return
on failureerror()
- ✅ Wrap in try/catch, return
- ❌ Expensive operations without caching
- ✅ Cache API calls, computations with TTL
- ❌ Missing CORS configuration
- ✅ Configure CORS for production deployments
🔒 Golden Rules
Opinionated architectural guidelines:
1. One Tool = One Capability
Split broad actions into focused tools:
- ❌
(too vague)manage-users - ✅
,create-user
,delete-userlist-users
2. Return Complete Data Upfront
Tool calls are expensive. Avoid lazy-loading:
- ❌
+list-products
(2 calls)get-product-details - ✅
returns full data including detailslist-products
3. Widgets Own Their State
UI state lives in the widget, not in separate tools:
- ❌
tool,select-item
toolset-filter - ✅ Widget manages with
oruseStatesetState
4. exposeAsTool
Defaults to false
exposeAsToolfalseWidgets are registered as resources only by default. Use a custom tool (recommended) or set
exposeAsTool: true to expose a widget to the model:
// ✅ ALL 4 STEPS REQUIRED for proper type inference: // Step 1: Define schema separately const propsSchema = z.object({ title: z.string(), items: z.array(z.string()) }); // Step 2: Reference schema variable in metadata export const widgetMetadata: WidgetMetadata = { description: "...", props: propsSchema, // ← NOT inline z.object() exposeAsTool: false }; // Step 3: Infer Props type from schema variable type Props = z.infer<typeof propsSchema>; // Step 4: Use typed Props with useWidget export default function MyWidget() { const { props, isPending } = useWidget<Props>(); // ← Add <Props> // ... }
⚠️ Common mistake: Only doing steps 1-2 but skipping 3-4 (loses type safety)
5. Validate at Boundaries Only
- Trust internal code and framework guarantees
- Validate user input, external API responses
- Don't add error handling for scenarios that can't happen
6. Prefer Widgets for Browsing/Comparing
When in doubt, add a widget. Visual UI improves:
- Browsing multiple items
- Comparing data side-by-side
- Interactive selection workflows
Quick Reference
Minimal Server
import { MCPServer, text } from "mcp-use/server"; import { z } from "zod"; const server = new MCPServer({ name: "my-server", title: "My Server", version: "1.0.0" }); server.tool( { name: "greet", description: "Greet a user", schema: z.object({ name: z.string().describe("User's name") }) }, async ({ name }) => text("Hello " + name + "!"), ); server.listen();
Response Helpers
| Helper | Use When | Example |
|---|---|---|
| Simple string response | |
| Structured data | |
| Formatted text | |
| Visual UI | |
| Multiple contents | |
| Error responses | |
| Embed resource refs | |
Server methods:
- Define executable toolserver.tool()
- Define static/dynamic resourceserver.resource()
- Define parameterized resourceserver.resourceTemplate()
- Define prompt templateserver.prompt()
- Compose/Proxy multiple MCP serversserver.proxy()
- Define widget resourceserver.uiResource()
- Start serverserver.listen()
- MCP middleware (tools, resources, prompts, list ops)server.use('mcp:tools/call', fn)
- Catch-all MCP middlewareserver.use('mcp:*', fn)
- HTTP middleware (Hono)server.use(fn)