IMPORTANT: 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.
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 - Basic server setup patterns and first tool example
Load these before diving into tools/resources/widgets sections.
🔧 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: , , , , ,
🎨 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, hook, checks, props handling
-
state.md
- When: Managing UI state (selections, filters, tabs) within widgets
- Covers: , , state persistence, when to use tool vs widget state
-
interactivity.md
- When: Adding buttons, forms, or calling tools from within widgets
- Covers: , form handling, action buttons, optimistic updates
-
ui-guidelines.md
- When: Styling widgets to support themes, responsive layouts, or accessibility
- Covers: , light/dark mode, , 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 to build?
├─ 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
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
- ❌ Skipping Zod schema 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 helper
- ✅ Use for graceful error responses
Widget Development
- ❌ Accessing without checking
- ✅ Always check
if (isPending) return <Loading/>
- ❌ Widget handles server state (filters, selections)
- ✅ Widgets manage their own UI state with
- ❌ Missing wrapper or
- ✅ Wrap root component:
<McpUseProvider autoSize>
- ❌ Inline styles without theme awareness
- ✅ Use for light/dark mode support
Security & Production
- ❌ Hardcoded API keys or secrets in code
- ❌ No error handling in tool handlers
- ✅ Wrap in try/catch, return 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:
1. One Tool = One Capability
Split broad actions into focused tools:
2. Return Complete Data Upfront
Tool calls are expensive. Avoid lazy-loading:
- ❌ + (2 calls)
- ✅ returns full data including details
3. Widgets Own Their State
UI state lives in the widget, not in separate tools:
- ❌ tool, tool
- ✅ Widget manages with or
4. Use for Custom Widget Tools
Prevent duplicate tool registration:
typescript
// ✅ 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
typescript
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 | markdown("# Title\nContent")
|
| Visual UI | widget({ props: {...}, output: text(...) })
|
| Multiple contents | mix(text("Hi"), image(url))
|
| Error responses | error("Failed to fetch data")
|
| Embed resource refs | resource("docs://guide", "text/markdown")
|