mcp-builderoleh mcp-use

Build production-ready MCP servers with the mcp-use framework and automatic widget registration. Bootstrap with npx create-mcp-use-app and choose from three templates: starter (full-featured), mcp-apps (ChatGPT-optimized), or blank (minimal) Define tools, resources, and prompts using Zod schemas with automatic validation and clear parameter descriptions Automatically register React widgets from the resources/ folder as MCP Tools and Resources with dual-protocol support for MCP Apps and...

npx skills add https://github.com/mcp-use/skills --skill mcp-builder

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:

  1. Server scans resources/ folder at startup
  2. Finds .tsx files or widget.tsx in folders
  3. Extracts widgetMetadata from each component
  4. Registers as MCP Tool (e.g., weather-display)
  5. Registers as MCP Resource (e.g., ui://widget/weather-display.html)
  6. 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 login if 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-app with _meta.ui.*
  • ChatGPT receives: text/html+skybridge with _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

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

Lebih banyak skill dari mcp-use

chatgpt-app-builder
by mcp-use
DEPRECATED: Use mcp-app-builder instead. Build ChatGPT apps with interactive widgets and zero-config React development. This skill is deprecated; migrate to mcp-app-builder for continued support and updates Enables collaborative UI between human users and LLM through shared interactive widgets Provides server handlers, React widget scaffolding, state management, and built-in hooks for tool calls and follow-up messages Covers display modes (inline, fullscreen, picture-in-picture), theming,...
mcp-apps-builder
by mcp-use
Mandatory reference guide for building production MCP servers with tools, resources, prompts, and widgets. Covers foundational concepts (Tool, Resource, Prompt, Widget primitives), server architecture, authentication patterns (OAuth, Supabase, custom), and deployment strategies Includes detailed guides for implementing tools, resources, prompts, response formatting, and widget-based UIs with state management and theming Documents common anti-patterns (missing validation, improper error...
mcp-builder
by mcp-use
DEPRECATED: Build MCP servers with tools, resources, prompts, and interactive widgets using mcp-use. This skill is deprecated; use mcp-app-builder instead Supports defining tools, resources, prompts, and interactive React widgets within a single MCP server Includes response helpers for formatting outputs as text, markdown, HTML, images, objects, and custom widgets Provides server composition and proxying to aggregate multiple MCP servers into a unified interface
chatgpt-app-builder
by mcp-use
Build ChatGPT apps with interactive widgets using mcp-use and OpenAI Apps SDK. Use when creating ChatGPT apps, building MCP servers with widgets, defining…

NotebookLM Web Importer

Impor halaman web dan video YouTube ke NotebookLM dengan satu klik. Dipercaya oleh 200.000+ pengguna.

Instal Ekstensi Chrome