Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive guide for building production-ready MCP servers with tools, resources, prompts, and React widgets using mcp-use.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: mcp-apps-builder3description: |4**MANDATORY for ALL MCP server work** - mcp-use framework best practices and patterns.56**READ THIS FIRST** before any MCP server work, including:7- Creating new MCP servers8- Modifying existing MCP servers (adding/updating tools, resources, prompts, widgets)9- Debugging MCP server issues or errors10- Reviewing MCP server code for quality, security, or performance11- Answering questions about MCP development or mcp-use patterns12- Making ANY changes to server.tool(), server.resource(), server.prompt(), or widgets1314This skill contains critical architecture decisions, security patterns, and common pitfalls.15Always consult the relevant reference files BEFORE implementing MCP features.16---1718# IMPORTANT: How to Use This Skill1920This file provides a NAVIGATION GUIDE ONLY. Before implementing any MCP server features, you MUST:21221. Read this overview to understand which reference files are relevant232. **ALWAYS read the specific reference file(s)** for the features you're implementing243. Apply the detailed patterns from those files to your implementation2526**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.2728---2930# MCP Server Best Practices3132Comprehensive guide for building production-ready MCP servers with tools, resources, prompts, and widgets using mcp-use.3334## ⚠️ FIRST: New Project or Existing Project?3536**Before doing anything else, determine whether you are inside an existing mcp-use project.**3738**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"`.3940```41├─ mcp-use project FOUND → Do NOT scaffold. You are already in a project.42│ └─ Skip to "Quick Navigation" below to add features.43│44├─ NO mcp-use project (empty dir, unrelated project, or greenfield)45│ └─ Scaffold first with npx create-mcp-use-app, then add features.46│ See "Scaffolding a New Project" below.47│48└─ Inside an UNRELATED project (e.g. Next.js app) and user wants an MCP server49└─ Ask the user where to create it, then scaffold in that directory.50Do NOT scaffold inside an existing unrelated project root.51```5253**NEVER manually create `MCPServer` boilerplate, `package.json`, 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.5455---5657### Scaffolding a New Project5859```bash60npx create-mcp-use-app my-server61cd my-server62npm run dev63```6465For full scaffolding details and CLI flags, see **[quickstart.md](references/foundations/quickstart.md)**.6667---6869## Quick Navigation7071**Choose your path based on what you're building:**7273### 🚀 Foundations74**When:** ALWAYS read these first when starting MCP work in a new conversation. Reference later for architecture/concept clarification.75761. **[concepts.md](references/foundations/concepts.md)** - MCP primitives (Tool, Resource, Prompt, Widget) and when to use each772. **[architecture.md](references/foundations/architecture.md)** - Server structure (Hono-based), middleware system, server.use() vs server.app783. **[quickstart.md](references/foundations/quickstart.md)** - Scaffolding, setup, and first tool example794. **[deployment.md](references/foundations/deployment.md)** - Deploying to Manufact Cloud, self-hosting, Docker, managing deployments8081Load these before diving into tools/resources/widgets sections.8283---8485### 🔐 Adding Authentication?86**When:** Protecting your server with OAuth (Auth0, Better Auth, WorkOS, Supabase, Keycloak, or any other provider)8788- **[overview.md](references/authentication/overview.md)**89- When: First time adding auth, understanding `ctx.auth`, or choosing a provider / integration mode90- Covers: Remote auth vs OAuth proxy, `oauth` config, `ctx.auth` shape, provider comparison, common mistakes9192- **[auth0.md](references/authentication/auth0.md)**93- When: Using Auth0 — DCR (Early Access) or a standard Regular Web App via `oauthProxy`94- Covers: Setup for both modes, `extraAuthorizeParams.audience`, permissions via `rfc9068_profile_authz`9596- **[better-auth.md](references/authentication/better-auth.md)**97- When: Using Better Auth with the `@better-auth/oauth-provider` plugin (self-hosted OAuth 2.1)98- Covers: `oauthBetterAuthProvider`, auth URL / metadata routes, login and consent flows99100- **[workos.md](references/authentication/workos.md)**101- When: Using WorkOS AuthKit (DCR only)102- Covers: Setup, env vars, roles/permissions, multi-tenant org filtering, WorkOS API calls103104- **[supabase.md](references/authentication/supabase.md)**105- When: Using Supabase's OAuth 2.1 server106- Covers: Setup, publishable keys, ES256 vs HS256, hosting the consent UI, RLS-aware SDK calls107108- **[keycloak.md](references/authentication/keycloak.md)**109- When: Using Keycloak via native DCR110- Covers: DCR trusted hosts + web origins, audience enforcement, realm vs resource roles, userinfo111112- **[custom.md](references/authentication/custom.md)**113- When: Any other provider — DCR-capable via `oauthCustomProvider`, or pre-registered (Google, GitHub, Okta, Azure AD) via `oauthProxy`114- Covers: `oauthCustomProvider`, `oauthProxy` + `jwksVerifier`, provider examples, opaque-token verification115116---117118### 🔧 Building Server Backend (No UI)?119**When:** Implementing MCP features (actions, data, templates). Read the specific file for the primitive you're building.120121- **[tools.md](references/server/tools.md)**122- When: Creating backend actions the AI can call (send-email, fetch-data, create-user)123- Covers: Tool definition, schemas, annotations, context, error handling124125- **[resources.md](references/server/resources.md)**126- When: Exposing read-only data clients can fetch (config, user profiles, documentation)127- Covers: Static resources, dynamic resources, parameterized resource templates, URI completion128129- **[prompts.md](references/server/prompts.md)**130- When: Creating reusable message templates for AI interactions (code-review, summarize)131- Covers: Prompt definition, parameterization, argument completion, prompt best practices132133- **[response-helpers.md](references/server/response-helpers.md)**134- When: Formatting responses from tools/resources (text, JSON, markdown, images, errors)135- Covers: `text()`, `object()`, `markdown()`, `image()`, `error()`, `mix()`136137- **[proxy.md](references/server/proxy.md)**138- When: Composing multiple MCP servers into one unified aggregator server139- Covers: `server.proxy()`, config API, explicit sessions, sampling routing140141- **[architecture.md](references/foundations/architecture.md)**142- When: Adding cross-cutting logic (logging, auth checks, rate limiting, tool filtering) that spans multiple tools/resources143- Covers: `server.use('mcp:...')` middleware, `MiddlewareContext` (method, params, auth, state), pattern matching, HTTP vs MCP middleware144145---146147### 🎨 Building Visual Widgets (Interactive UI)?148**When:** Creating React-based visual interfaces for browsing, comparing, or selecting data149150- **[basics.md](references/widgets/basics.md)**151- When: Creating your first widget or adding UI to an existing tool152- Covers: Widget setup, `useWidget()` hook, `isPending` checks, props handling153154- **[state.md](references/widgets/state.md)**155- When: Managing UI state (selections, filters, tabs) within widgets156- Covers: `useState`, `setState`, state persistence, when to use tool vs widget state157158- **[interactivity.md](references/widgets/interactivity.md)**159- When: Adding buttons, forms, or calling tools from within widgets160- Covers: `useCallTool()`, form handling, action buttons, optimistic updates161162- **[ui-guidelines.md](references/widgets/ui-guidelines.md)**163- When: Styling widgets to support themes, responsive layouts, or accessibility164- Covers: `useWidgetTheme()`, light/dark mode, `autoSize`, layout patterns, CSS best practices165166- **[advanced.md](references/widgets/advanced.md)**167- When: Building complex widgets with async data, error boundaries, or performance optimizations168- Covers: Loading states, error handling, memoization, code splitting169170- **[model-context.md](references/widgets/model-context.md)**171- When: Keeping the AI model aware of what the user is currently seeing (active tab, hovered item, selected product) without requiring tool calls172- Covers: `<ModelContext>` component, `modelContext.set/remove` imperative API, nesting, tree serialization, lifecycle rules173- **[files.md](references/widgets/files.md)**174- When: Uploading or downloading files from within a widget (ChatGPT Apps SDK only)175- Covers: `useFiles()` hook, `isSupported` guard, model visibility (`modelVisible`), storing `fileId`, temporary download URLs176177---178179### 📚 Need Complete Examples?180**When:** You want to see full implementations of common use cases181182- **[common-patterns.md](references/patterns/common-patterns.md)**183- End-to-end examples: weather app, todo list, recipe browser184- Shows: Server code + widget code + best practices in context185186---187188## Decision Tree189190```191What do you need?192193├─ New project from scratch194│ └─> quickstart.md (scaffolding + setup)195│196├─ OAuth / user authentication197│ └─> authentication/overview.md → provider-specific guide198│199├─ Simple backend action (no UI)200│ └─> Use Tool: server/tools.md201│202├─ Read-only data for clients203│ └─> Use Resource: server/resources.md204│205├─ Reusable prompt template206│ └─> Use Prompt: server/prompts.md207│208├─ Cross-cutting logic (logging, auth checks, rate limiting, tool filtering)209│ └─> Use Middleware: architecture.md#mcp-middleware210│211├─ Visual/interactive UI212│ └─> Use Widget: widgets/basics.md213│214├─ Keep model aware of what user is seeing in widget215│ └─> widgets/model-context.md216├─ Upload/download files in a widget217│ └─> widgets/files.md (ChatGPT Apps SDK only)218│219└─ Deploy to production220└─> deployment.md (cloud deploy, self-hosting, Docker)221```222223---224225## Core Principles2262271. **Tools for actions** - Backend operations with input/output2282. **Resources for data** - Read-only data clients can fetch2293. **Prompts for templates** - Reusable message templates2304. **Widgets for UI** - Visual interfaces when helpful2315. **Mock data first** - Prototype quickly, connect APIs later232233---234235## ❌ Common Mistakes236237Avoid these anti-patterns found in production MCP servers:238239### Tool Definition240- ❌ Returning raw objects instead of using response helpers241- ✅ Use `text()`, `object()`, `widget()`, `error()` helpers242- ❌ Skipping Zod schema `.describe()` on every field243- ✅ Add descriptions to all schema fields for better AI understanding244- ❌ No input validation or sanitization245- ✅ Validate inputs with Zod, sanitize user-provided data246- ❌ Throwing errors instead of returning `error()` helper247- ✅ Use `error("message")` for graceful error responses248249### Widget Development250- ❌ Accessing `props` without checking `isPending`251- ✅ Always check `if (isPending) return <Loading/>`252- ❌ Widget handles server state (filters, selections)253- ✅ Widgets manage their own UI state with `useState`254- ❌ Missing `McpUseProvider` wrapper or `autoSize`255- ✅ Wrap root component: `<McpUseProvider autoSize>`256- ❌ Inline styles without theme awareness257- ✅ Use `useWidgetTheme()` for light/dark mode support258259### Security & Production260- ❌ Hardcoded API keys or secrets in code261- ✅ Use `process.env.API_KEY`, document in `.env.example`262- ❌ No error handling in tool handlers263- ✅ Wrap in try/catch, return `error()` on failure264- ❌ Expensive operations without caching265- ✅ Cache API calls, computations with TTL266- ❌ Missing CORS configuration267- ✅ Configure CORS for production deployments268269---270271## 🔒 Golden Rules272273**Opinionated architectural guidelines:**274275### 1. One Tool = One Capability276Split broad actions into focused tools:277- ❌ `manage-users` (too vague)278- ✅ `create-user`, `delete-user`, `list-users`279280### 2. Return Complete Data Upfront281Tool calls are expensive. Avoid lazy-loading:282- ❌ `list-products` + `get-product-details` (2 calls)283- ✅ `list-products` returns full data including details284285### 3. Widgets Own Their State286UI state lives in the widget, not in separate tools:287- ❌ `select-item` tool, `set-filter` tool288- ✅ Widget manages with `useState` or `setState`289290### 4. `exposeAsTool` Defaults to `false`291Widgets are registered as resources only by default. Use a custom tool (recommended) or set `exposeAsTool: true` to expose a widget to the model:292293```typescript294// ✅ ALL 4 STEPS REQUIRED for proper type inference:295296// Step 1: Define schema separately297const propsSchema = z.object({298title: z.string(),299items: z.array(z.string())300});301302// Step 2: Reference schema variable in metadata303export const widgetMetadata: WidgetMetadata = {304description: "...",305props: propsSchema, // ← NOT inline z.object()306exposeAsTool: false307};308309// Step 3: Infer Props type from schema variable310type Props = z.infer<typeof propsSchema>;311312// Step 4: Use typed Props with useWidget313export default function MyWidget() {314const { props, isPending } = useWidget<Props>(); // ← Add <Props>315// ...316}317```318319⚠️ **Common mistake:** Only doing steps 1-2 but skipping 3-4 (loses type safety)320321### 5. Validate at Boundaries Only322- Trust internal code and framework guarantees323- Validate user input, external API responses324- Don't add error handling for scenarios that can't happen325326### 6. Prefer Widgets for Browsing/Comparing327When in doubt, add a widget. Visual UI improves:328- Browsing multiple items329- Comparing data side-by-side330- Interactive selection workflows331332---333334## Quick Reference335336### Minimal Server337```typescript338import { MCPServer, text } from "mcp-use/server";339import { z } from "zod";340341const server = new MCPServer({342name: "my-server",343title: "My Server",344version: "1.0.0"345});346347server.tool(348{349name: "greet",350description: "Greet a user",351schema: z.object({ name: z.string().describe("User's name") })352},353async ({ name }) => text("Hello " + name + "!"),354);355356server.listen();357```358359---360361## Response Helpers362363| Helper | Use When | Example |364|--------|----------|---------|365| `text()` | Simple string response | `text("Success!")` |366| `object()` | Structured data | `object({ status: "ok" })` |367| `markdown()` | Formatted text | `markdown("# Title\nContent")` |368| `widget()` | Visual UI | `widget({ props: {...}, output: text(...) })` |369| `mix()` | Multiple contents | `mix(text("Hi"), image(url))` |370| `error()` | Error responses | `error("Failed to fetch data")` |371| `resource()` | Embed resource refs | `resource("docs://guide", "text/markdown")` |372373**Server methods:**374- `server.tool()` - Define executable tool375- `server.resource()` - Define static/dynamic resource376- `server.resourceTemplate()` - Define parameterized resource377- `server.prompt()` - Define prompt template378- `server.proxy()` - Compose/Proxy multiple MCP servers379- `server.uiResource()` - Define widget resource380- `server.listen()` - Start server381- `server.use('mcp:tools/call', fn)` - MCP middleware (tools, resources, prompts, list ops)382- `server.use('mcp:*', fn)` - Catch-all MCP middleware383- `server.use(fn)` - HTTP middleware (Hono)384385386