IMPORTANT: How to Use This Skill
This file provides a navigation guide. Before implementing mcp-use features, you should:
-
Read this overview to understand which reference files are relevant
-
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. The reference files contain the deeper framework conventions, production patterns, and common pitfalls.
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 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.
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, or custom)
overview.md
-
When: First time adding auth, understanding ctx.auth , or choosing a provider
-
Covers: oauth config, user context shape, provider comparison, common mistakes
workos.md
-
When: Using WorkOS AuthKit for authentication
-
Covers: Setup, env vars, DCR vs pre-registered, roles/permissions, WorkOS API calls
supabase.md
-
When: Using Supabase for authentication
-
Covers: Setup, env vars, HS256 vs ES256, RLS-aware API calls
custom.md
-
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.
tools.md
-
When: Creating backend actions the AI can call (send-email, fetch-data, create-user)
-
Covers: Tool definition, schemas, annotations, context, error handling
resources.md
-
When: Exposing read-only data clients can fetch (config, user profiles, documentation)
-
Covers: Static resources, dynamic resources, parameterized resource templates, URI completion
prompts.md
-
When: Creating reusable message templates for AI interactions (code-review, summarize)
-
Covers: Prompt definition, parameterization, argument completion, prompt best practices
response-helpers.md
-
When: Formatting responses from tools/resources (text, JSON, markdown, images, errors)
-
Covers: text() , object() , markdown() , image() , error() , mix()
proxy.md
-
When: Composing multiple MCP servers into one unified aggregator server
-
Covers: server.proxy() , config API, explicit sessions, sampling routing
🎨 Building Visual Widgets (Interactive UI)?
When: Creating React-based visual interfaces for browsing, comparing, or selecting data
basics.md
-
When: Creating your first widget or adding UI to an existing tool
-
Covers: Widget setup, useWidget() hook, isPending checks, props handling
state.md
-
When: Managing UI state (selections, filters, tabs) within widgets
-
Covers: useState , setState , state persistence, when to use tool vs widget state
interactivity.md
-
When: Adding buttons, forms, or calling tools from within widgets
-
Covers: useCallTool() , form handling, action buttons, optimistic updates
ui-guidelines.md
-
When: Styling widgets to support themes, responsive layouts, or accessibility
-
Covers: useWidgetTheme() , light/dark mode, autoSize , layout patterns, CSS best practices
advanced.md
-
When: Building complex widgets with async data, error boundaries, or performance optimizations
-
Covers: Loading states, error handling, memoization, code splitting
📚 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 │ ├─ Visual/interactive UI │ └─> Use Widget: widgets/basics.md │ └─ 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() , error() helpers
-
❌ Skipping Zod schema .describe() on every field
-
✅ 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 error() helper
-
✅ Use error("message") for graceful error responses
Widget Development
-
❌ Accessing props without checking isPending
-
✅ Always check if (isPending) return <Loading/>
-
❌ Widget handles server state (filters, selections)
-
✅ Widgets manage their own UI state with useState
-
❌ Missing McpUseProvider wrapper or autoSize
-
✅ Wrap root component: <McpUseProvider autoSize>
-
❌ Inline styles without theme awareness
-
✅ Use useWidgetTheme() for light/dark mode support
Security & Production
-
❌ Hardcoded API keys or secrets in code
-
✅ Use process.env.API_KEY , document in .env.example
-
❌ No error handling in tool handlers
-
✅ Wrap in try/catch, return error() on failure
-
❌ Expensive operations without caching
-
✅ Cache API calls, computations with TTL
-
❌ Missing CORS configuration
-
✅ Configure CORS for production deployments
🔒 Golden Rules
Opinionated architectural guidelines:
- One Tool = One Capability
Split broad actions into focused tools:
-
❌ manage-users (too vague)
-
✅ create-user , delete-user , list-users
- Return Complete Data Upfront
Tool calls are expensive. Avoid lazy-loading:
- ❌ list-products
- get-product-details (2 calls)
- ✅ list-products returns full data including details
- Widgets Own Their State
UI state lives in the widget, not in separate tools:
-
❌ select-item tool, set-filter tool
-
✅ Widget manages with useState or setState
- exposeAsTool Defaults to false
Widgets 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)
- 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
- 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
text()
Simple string response text("Success!")
object()
Structured data object({ status: "ok" })
markdown()
Formatted text markdown("# Title\nContent")
widget()
Visual UI widget({ props: {...}, output: text(...) })
mix()
Multiple contents mix(text("Hi"), image(url))
error()
Error responses error("Failed to fetch data")
resource()
Embed resource refs resource("docs://guide", "text/markdown")
Server methods:
-
server.tool()
-
Define executable tool
-
server.resource()
-
Define static/dynamic resource
-
server.resourceTemplate()
-
Define parameterized resource
-
server.prompt()
-
Define prompt template
-
server.proxy()
-
Compose/Proxy multiple MCP servers
-
server.uiResource()
-
Define widget resource
-
server.listen()
-
Start server