This guide walks you through creating a complete MCP server that supports MCP Apps widgets, enabling rich interactive experiences in ChatGPT or Claude.
What You'll Build
By the end of this guide, you'll have:
A fully functional MCP server with MCP Apps support
Automatic widget registration from React components
The easiest way to start is using the MCP Apps template:
npx create-mcp-use-app my-mcp-apps-server --template mcp-apps
cd my-mcp-apps-server
This creates a project structure:
my-mcp-apps-server/
├── resources/ # React widgets go here
│ └── display-weather.tsx # Example widget
├── index.ts # Server entry point
├── package.json
├── tsconfig.json
└── README.md
Step 2: Understanding the Server Setup
Let's examine the server entry point (index.ts):
import { createMCPServer } from "mcp-use/server";
const server = createMCPServer("my-mcp-apps-server", {
version: "1.0.0",
description: "MCP server with MCP Apps integration",
// Optional: Set baseUrl for production CSP configuration
baseUrl: process.env.MCP_URL || "http://localhost:3000",
});
// Add your tools, resources, and prompts here
// ...
// Start the server
server.listen().then(() => {
console.log("Server running on http://localhost:3000");
});
Key Configuration Options
baseUrl: Required for production. Used to configure Content Security Policy (CSP) for MCP Apps widgets
version: Server version for client discovery
description: Human-readable server description
Step 3: Create Your First Widget
Widgets are React components in the resources/ folder. They're automatically registered as both MCP tools and resources.
Registers a tool with the filename as the name (e.g., user-profile)
Registers a resource at ui://widget/user-profile.html
Builds the widget for MCP Apps compatibility
No manual registration needed!
Step 4: Add Traditional MCP Tools
You can mix automatic widgets with traditional tools:
// Fetch user data from an API
server.tool({
name: "get-user-data",
description: "Fetch user information from the database",
inputs: [{ name: "userId", type: "string", required: true }],
cb: async ({ userId }) => {
// Simulate API call
const userData = {
name: "John Doe",
email: "[email protected]",
avatar: "https://api.example.com/avatars/john.jpg",
role: "user",
bio: "Software developer passionate about AI",
};
return {
content: [
{
type: "text",
text: `User data retrieved for ${userId}`,
},
],
structuredContent: userData,
};
},
});
// Display user profile using the widget
// The LLM can now call 'user-profile' tool with the data
Step 5: Configure MCP Apps Metadata
For production widgets, you may want to customize MCP Apps metadata. You can do this manually:
server.uiResource({
type: "appsSdk",
name: "custom-widget",
title: "Custom Widget",
description: "A custom widget with specific configuration",
htmlTemplate: `<!DOCTYPE html>...`, // Your HTML
appsSdkMetadata: {
"openai/widgetDescription": "Interactive data visualization",
"openai/widgetCSP": {
connect_domains: ["https://api.example.com"],
resource_domains: ["https://cdn.example.com"],
},
"openai/toolInvocation/invoking": "Loading widget...",
"openai/toolInvocation/invoked": "Widget ready",
"openai/widgetAccessible": true,
"openai/resultCanProduceWidget": true,
},
});
However, with automatic registration, metadata is generated automatically based on your widgetMetadata.
Step 6: Testing Your Server
Start the Development Server
npm run dev
This starts:
MCP server on port 3000
Widget development server with Hot Module Replacement (HMR)