Expert MCP Server Builder Toolkit
Professional-grade skill designed for guide for creating Model Context Protocol servers. Built for Claude Code with best practices and real-world patterns.
MCP Server Builder Toolkit
A development skill for building Model Context Protocol (MCP) servers that extend AI assistants with custom tools, resources, and prompts using the official MCP SDK.
When to Use
Choose MCP Server Builder when:
- Creating custom tools that AI assistants can invoke during conversations
- Building integrations that expose databases, APIs, or services to AI agents
- Implementing resource providers that give AI assistants access to dynamic data
- Developing MCP servers for local development tools, cloud services, or data sources
Consider alternatives when:
- Building a standalone API — use a standard web framework
- Creating a chatbot — use a chatbot framework directly
- Simple prompt engineering — use system prompts without MCP
Quick Start
# Create MCP server with TypeScript npx @modelcontextprotocol/create-server my-mcp-server cd my-mcp-server && npm install
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; const server = new McpServer({ name: 'my-tools', version: '1.0.0' }); // Define a tool server.tool( 'search-database', 'Search the product database by query', { query: z.string().describe('Search query string'), limit: z.number().optional().default(10).describe('Max results'), category: z.enum(['electronics', 'clothing', 'books']).optional() }, async ({ query, limit, category }) => { const results = await searchProducts(query, { limit, category }); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } ); // Define a resource server.resource( 'product://{id}', 'Get product details by ID', async (uri) => { const id = uri.pathname.split('/').pop(); const product = await getProduct(id); return { contents: [ { uri: uri.toString(), mimeType: 'application/json', text: JSON.stringify(product) } ] }; } ); // Define a prompt template server.prompt( 'analyze-sales', 'Analyze sales data for a given period', { startDate: z.string().describe('Start date (YYYY-MM-DD)'), endDate: z.string().describe('End date (YYYY-MM-DD)') }, async ({ startDate, endDate }) => ({ messages: [ { role: 'user', content: { type: 'text', text: `Analyze sales data from ${startDate} to ${endDate}. Focus on trends, top products, and anomalies.` } } ] }) ); // Start server const transport = new StdioServerTransport(); await server.connect(transport);
Core Concepts
MCP Architecture
| Component | Purpose | Protocol |
|---|---|---|
| Server | Provides tools, resources, prompts | stdio or HTTP/SSE |
| Client | AI assistant that connects to servers | Sends requests |
| Tools | Functions the AI can invoke | JSON-RPC |
| Resources | Data the AI can read | URI-based |
| Prompts | Template prompts with parameters | Pre-defined |
| Transport | Communication layer | stdio / HTTP |
Building Complex Tools
// Tool with error handling and streaming server.tool( 'run-query', 'Execute a read-only SQL query against the database', { sql: z.string().describe('SQL SELECT query to execute'), database: z.enum(['analytics', 'products']).describe('Target database') }, async ({ sql, database }) => { // Validate query is read-only const normalized = sql.trim().toUpperCase(); if (!normalized.startsWith('SELECT')) { return { content: [{ type: 'text', text: 'Error: Only SELECT queries are allowed' }], isError: true }; } try { const db = getConnection(database); const results = await db.query(sql); return { content: [{ type: 'text', text: `Query returned ${results.length} rows:\n\n` + JSON.stringify(results.slice(0, 100), null, 2) + (results.length > 100 ? `\n\n... and ${results.length - 100} more rows` : '') }] }; } catch (error) { return { content: [{ type: 'text', text: `Query error: ${error.message}` }], isError: true }; } } ); // Resource with dynamic listing server.resource( 'config://{key}', 'Application configuration values', async (uri) => { const key = uri.pathname.split('/').pop(); const value = await getConfig(key); return { contents: [{ uri: uri.toString(), mimeType: 'text/plain', text: String(value) }] }; } );
Configuration
| Option | Description | Default |
|---|---|---|
name | Server name for identification | Required |
version | Server version string | "1.0.0" |
transport | Communication: stdio, http | "stdio" |
capabilities | Enabled features: tools, resources, prompts | All enabled |
tool_timeout | Maximum tool execution time (ms) | 30000 |
max_response_size | Maximum response content size | 1MB |
logging | Log level: debug, info, warn, error | "info" |
auth | Authentication configuration | None |
Best Practices
- Make tools focused and single-purpose — a tool that "searches, filters, and exports data" should be three separate tools so the AI can compose them flexibly based on what the user actually needs
- Return structured data in tool responses so the AI can parse, filter, and present results appropriately; raw text dumps force the AI to guess at data structure
- Validate all inputs with Zod schemas that include
.describe()on every parameter so the AI understands what values are expected and can provide accurate arguments - Handle errors gracefully by returning
isError: truewith a descriptive error message rather than throwing exceptions that crash the server - Use resources for read-only data access and tools for actions that have side effects — this separation helps the AI understand when it is reading data versus performing an action
Common Issues
Server not discovered by AI assistant: The MCP server must be registered in the assistant's configuration file (e.g., Claude's claude_desktop_config.json). Verify the server command path is correct, test the server standalone with stdio, and check that the server outputs valid JSON-RPC on stdout.
Tool responses too large: Returning entire database tables or large file contents exceeds context limits. Implement pagination in tools, truncate results with a clear indicator of omitted data, and let the AI request specific subsets rather than returning everything.
Transport connection dropping: Stdio-based servers that write debug output to stdout corrupt the JSON-RPC protocol. Always write debug and log messages to stderr, never to stdout, and handle SIGPIPE gracefully for long-running servers.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.