MCP Server Builder
Build production-ready MCP servers with the mcp-use framework. This Skill provides quick-start instructions and best practices for creating MCP servers.
Quick Start
Always bootstrap with npx create-mcp-use-app:
npx create-mcp-use-app my-mcp-server cd my-mcp-server
Choose template based on needs:
- •
--template starter- Full-featured with all MCP primitives (tools, resources, prompts) + example widgets - •
--template mcp-apps- Optimized for ChatGPT widgets with product search example - •
--template blank- Minimal starting point for custom implementation
# Example: MCP Apps template npx create-mcp-use-app my-server --template mcp-apps cd my-server yarn install
Template Details:
- •starter: Best for learning - includes all MCP features plus widgets
- •mcp-apps: Best for ChatGPT apps - includes product carousel/accordion example
- •blank: Best for experts - minimal boilerplate
MCP Apps Structure
Automatic Widget Registration
The mcp-apps and starter templates automatically discover and register React widgets from the resources/ folder:
Single-file widget pattern:
resources/ └── weather-display.tsx # Widget name becomes "weather-display"
Folder-based widget pattern:
resources/
└── product-search/ # Widget name becomes "product-search"
├── widget.tsx # Entry point (required name!)
├── components/ # Sub-components
├── hooks/ # Custom hooks
├── types.ts
└── constants.ts
What happens automatically:
- •Server scans
resources/folder at startup - •Finds
.tsxfiles orwidget.tsxin folders - •Extracts
widgetMetadatafrom each component - •Registers as MCP Tool (e.g.,
weather-display) - •Registers as MCP Resource (e.g.,
ui://widget/weather-display.html) - •Builds widget bundles with Vite
No manual registration needed! Just export widgetMetadata and a default component.
Defining Tools
Tools are executable functions that AI models can call:
import { MCPServer, text, object } from "mcp-use/server";
import { z } from "zod";
const server = new MCPServer({
name: "my-server",
version: "1.0.0",
description: "My MCP server"
});
// Simple tool
server.tool(
{
name: "greet-user",
description: "Greet a user by name",
schema: z.object({
name: z.string().describe("The user's name"),
formal: z.boolean().optional().describe("Use formal greeting")
})
},
async ({ name, formal }) => {
const greeting = formal ? `Good day, ${name}` : `Hey ${name}!`;
return text(greeting);
}
);
Key points:
- •Use Zod for schema validation
- •Add
.describe()to all parameters - •Return appropriate response types (text, object, widget)
Defining Resources
Resources expose data that clients can read:
import { object, text, markdown } from "mcp-use/server";
// Static resource
server.resource(
{
uri: "config://settings",
name: "Application Settings",
description: "Current configuration",
mimeType: "application/json"
},
async () => {
return object({
theme: "dark",
version: "1.0.0"
});
}
);
// Dynamic resource
server.resource(
{
uri: "stats://current",
name: "Current Stats",
description: "Real-time statistics",
mimeType: "application/json"
},
async () => {
const stats = await getStats();
return object(stats);
}
);
// Markdown resource
server.resource(
{
uri: "docs://guide",
name: "User Guide",
description: "Documentation",
mimeType: "text/markdown"
},
async () => {
return markdown("# Guide\n\nWelcome!");
}
);
Response helpers available:
- •
text(string)- Plain text - •
object(data)- JSON objects - •
markdown(string)- Markdown content - •
html(string)- HTML content - •
image(buffer, mimeType)- Binary images - •
audio(buffer, mimeType)- Audio files - •
binary(buffer, mimeType)- Binary data - •
mix(...contents)- Combine multiple content types
Advanced response examples:
// Audio response
import { audio } from 'mcp-use/server';
// From base64 data
return audio(base64Data, "audio/wav");
// From file path (async)
return await audio("/path/to/audio.mp3");
// Binary data (PDFs, etc.)
import { binary } from 'mcp-use/server';
return binary(pdfBuffer, "application/pdf");
// Mix multiple content types
import { mix, text, object, resource } from 'mcp-use/server';
return mix(
text("Analysis complete:"),
object({ score: 95, status: "pass" }),
resource("report://analysis-123", text("Full report..."))
);
Defining Prompts
Prompts are reusable templates for AI interactions:
server.prompt(
{
name: "code-review",
description: "Generate a code review template",
schema: z.object({
language: z.string().describe("Programming language"),
focusArea: z.string().optional().describe("Specific focus area")
})
},
async ({ language, focusArea }) => {
const focus = focusArea ? ` with focus on ${focusArea}` : "";
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Please review this ${language} code${focus}.`
}
}
]
};
}
);
Testing Locally
Development mode (hot reload):
yarn dev
Production mode:
yarn build yarn start
Inspector UI:
Access at http://localhost:3000/inspector to test tools, view resources, and try prompts.
Tunneling (test with ChatGPT before deploying):
Option 1 - Auto-tunnel:
mcp-use start --port 3000 --tunnel
Option 2 - Separate tunnel:
yarn start # Terminal 1 npx @mcp-use/tunnel 3000 # Terminal 2
You'll get a public URL like https://happy-cat.local.mcp-use.run/mcp
Tunnel details:
- •Expires after 24 hours
- •Closes after 1 hour of inactivity
- •Rate limit: 10 creations/hour, max 5 active per IP
Learn more: https://mcp-use.com/docs/tunneling
Deployment
Deploy to mcp-use Cloud (recommended):
# Login first (if not already) npx mcp-use login # Deploy yarn deploy
If authentication error:
npx mcp-use login yarn deploy
After deployment:
- •Public URL provided (e.g.,
https://your-server.mcp-use.com/mcp) - •Auto-scaled and monitored
- •HTTPS enabled
- •Zero-downtime deployments
Best Practices
Tool Design:
- •One tool = one focused capability
- •Descriptive names and descriptions
- •Use
.describe()on all Zod fields - •Handle errors gracefully
- •Return helpful error messages
Resource Design:
- •Use clear URI schemes (config://, docs://, stats://)
- •Choose appropriate MIME types
- •Use response helpers for cleaner code
- •Make resources dynamic when needed
Prompt Design:
- •Keep prompts reusable
- •Use system messages for context
- •Parameterize with Zod schemas
- •Include clear instructions
Testing:
- •Test with Inspector UI first
- •Use tunneling to test with real clients before deploying
- •Verify all tools, resources, and prompts work as expected
Deployment:
- •Test locally and with tunneling first
- •Run
npx mcp-use loginif deploy fails - •Version your server semantically
- •Document breaking changes
Widget Support
Automatic Widget Registration
When using the mcp-apps or starter template, widgets in the resources/ folder are automatically registered:
// resources/weather-display.tsx
import { useWidget, McpUseProvider, type WidgetMetadata } from 'mcp-use/react';
import { z } from 'zod';
const propSchema = z.object({
city: z.string(),
temperature: z.number()
});
// Required: Export widget metadata
export const widgetMetadata: WidgetMetadata = {
description: "Display weather information",
props: propSchema, // Use 'props', not 'schema'!
};
// Required: Export default component
export default function WeatherDisplay() {
const { props, isPending } = useWidget<z.infer<typeof propSchema>>();
// Always handle loading state
if (isPending) return <div>Loading...</div>;
return (
<McpUseProvider autoSize>
<div>
<h2>{props.city}</h2>
<p>{props.temperature}°C</p>
</div>
</McpUseProvider>
);
}
Widget automatically becomes available as:
- •MCP Tool:
weather-display - •MCP Resource:
ui://widget/weather-display.html
Content Security Policy (CSP)
Control what external resources widgets can access:
export const widgetMetadata: WidgetMetadata = {
description: "Weather widget",
props: z.object({ city: z.string() }),
metadata: {
csp: {
// APIs to call
connectDomains: ["https://api.weather.com"],
// Static assets to load
resourceDomains: ["https://cdn.weather.com"],
// Iframes to embed
frameDomains: ["https://embed.weather.com"],
// Script directives
scriptDirectives: ["'unsafe-inline'"],
},
},
};
Alternatively, set at server level:
server.uiResource({
type: "mcpApps",
name: "my-widget",
htmlTemplate: `...`,
metadata: {
csp: {
connectDomains: ["https://api.example.com"],
resourceDomains: ["https://cdn.example.com"],
},
},
});
Dual-Protocol Widget Support
mcp-use supports the MCP Apps standard (SEP-1865) with automatic dual-protocol support:
import { MCPServer } from 'mcp-use/server';
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
baseUrl: process.env.MCP_URL || 'http://localhost:3000', // Required for widgets
});
// Register a dual-protocol widget
server.uiResource({
type: "mcpApps", // Works with BOTH MCP Apps clients AND ChatGPT
name: "weather-display",
htmlTemplate: `<!DOCTYPE html>...`,
metadata: {
csp: { connectDomains: ["https://api.weather.com"] },
prefersBorder: true,
autoResize: true,
},
});
What happens automatically:
- •MCP Apps clients (Claude, Goose) receive:
text/html;profile=mcp-appwith_meta.ui.* - •ChatGPT receives:
text/html+skybridgewith_meta.openai/* - •Same widget code works everywhere!
Custom OpenAI Metadata
Need ChatGPT-specific features? Combine both metadata fields:
server.uiResource({
type: "mcpApps",
name: "my-widget",
htmlTemplate: `...`,
// Unified metadata (dual-protocol)
metadata: {
csp: { connectDomains: ["https://api.example.com"] },
prefersBorder: true,
},
// ChatGPT-specific overrides
appsSdkMetadata: {
"openai/widgetDescription": "ChatGPT-specific description",
"openai/customFeature": "some-value", // Any custom OpenAI metadata
},
});
Project Structure
my-mcp-server/ ├── resources/ # React widgets (apps-sdk) │ └── widget.tsx ├── public/ # Static assets ├── index.ts # Server entry point ├── package.json ├── tsconfig.json └── README.md
Common Patterns
Tool with dual-protocol widget:
import { MCPServer, widget, text } from 'mcp-use/server';
import { z } from 'zod';
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
baseUrl: process.env.MCP_URL || 'http://localhost:3000',
});
server.tool(
{
name: "show-data",
description: "Display data with visualization",
schema: z.object({
query: z.string()
}),
widget: {
name: "data-display", // Must exist in resources/
invoking: "Loading...",
invoked: "Data loaded"
}
},
async ({ query }) => {
const data = await fetchData(query);
return widget({
props: { data },
output: text(`Found ${data.length} results`)
});
}
);
Resource template (parameterized):
server.resourceTemplate(
{
uriTemplate: "user://{userId}/profile",
name: "User Profile",
description: "Get user by ID",
mimeType: "application/json"
},
async ({ userId }) => {
const user = await fetchUser(userId);
return object(user);
}
);
Error handling:
server.tool(
{
name: "divide",
schema: z.object({
a: z.number(),
b: z.number()
})
},
async ({ a, b }) => {
if (b === 0) {
return text("Error: Cannot divide by zero");
}
return text(`Result: ${a / b}`);
}
);
Detailed Examples
For comprehensive examples and advanced patterns, connect to the mcp-use MCP server which provides:
- •Complete example resources for all primitives
- •Full working server examples
- •Detailed documentation
- •Interactive widgets showcase
Learn More
- •Documentation: https://docs.mcp-use.com
- •MCP Apps Standard: https://docs.mcp-use.com/typescript/server/mcp-apps (dual-protocol guide)
- •Templates: https://docs.mcp-use.com/typescript/server/templates (template comparison)
- •Widget Guide: https://docs.mcp-use.com/typescript/server/ui-widgets
- •Examples: https://github.com/mcp-use/mcp-use/tree/main/examples
- •Tunneling Guide: https://mcp-use.com/docs/tunneling
- •Discord: https://mcp-use.com/discord
- •GitHub: https://github.com/mcp-use/mcp-use
Quick Reference
Commands:
- •
npx create-mcp-use-app my-server- Bootstrap - •
yarn dev- Development mode - •
yarn build- Build for production - •
yarn start- Run production server - •
mcp-use start --tunnel- Start with tunnel - •
npx mcp-use login- Authenticate - •
yarn deploy- Deploy to cloud
Response helpers:
- •
text(str),object(data),markdown(str),html(str) - •
image(buf, mime),audio(buf, mime),binary(buf, mime) - •
mix(...)- Combine multiple content types - •
widget({ props, output })- Return widget with data
Server methods:
- •
server.tool()- Define executable tool - •
server.resource()- Define static/dynamic resource - •
server.resourceTemplate()- Define parameterized resource - •
server.prompt()- Define prompt template - •
server.uiResource()- Define widget resource - •
server.listen()- Start server
Widget metadata fields:
- •
description- Widget description - •
props- Zod schema for widget props - •
metadata- Unified config (dual-protocol) - •
metadata.csp- Content Security Policy - •
appsSdkMetadata- ChatGPT-specific overrides
Available templates:
- •
starter- Full-featured (tools, resources, prompts, widgets) - •
mcp-apps- ChatGPT-optimized with product example - •
blank- Minimal boilerplate