Building Your Own MCP Server - Getting Started
TL;DR: Learn to build Model Context Protocol (MCP) servers that enable seamless AI integration with external systems. This comprehensive tutorial covers everything from setup to deployment, with working code examples and practical tools you can use immediately. Perfect for developers looking to extend AI capabilities with custom integrations.
What is MCP?
The Model Context Protocol (MCP) is an open standard that enables seamless integration between Large Language Model (LLM) applications and external data sources and tools. Think of it as a universal adapter that allows AI assistants to securely interact with your local files, databases, APIs, and business tools.
Key Benefits
- Standardized Integration: One protocol for connecting to any system
- Security: Controlled access with granular permissions
- Bi-directional Communication: Real-time interaction between AI and external systems
- Extensible: Support for tools, resources, and prompt templates
Architecture Overview
MCP follows a client-server architecture inspired by the Language Server Protocol (LSP):

Core Components
- MCP Host: The AI application (e.g., Claude Desktop, Cursor, Zed)
- MCP Client: Manages connections and protocol communication
- MCP Server: Your custom server exposing tools and resources
- Transport Layer: Communication mechanism (stdio, Streamable HTTP)
Setting Up Your Development Environment
Prerequisites
# Node.js 18+ and npm
node --version # Should be 18+
npm --version
# TypeScript (recommended)
npm install -g typescript
# MCP Inspector for testing
npm install -g @modelcontextprotocol/inspector
Project Initialization
# Create new project
mkdir my-mcp-server
cd my-mcp-server
# Initialize package.json
npm init -y
# Install core dependencies
npm install @modelcontextprotocol/sdk
# Install development dependencies
npm install -D typescript @types/node ts-node
TypeScript Configuration
Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"types": ["node"]
},
"include": ["src/**/*"]
}
Package.json Scripts
Update your package.json:
{
"name": "my-mcp-server",
"version": "1.0.0",
"type": "module",
"main": "dist/server.js",
"scripts": {
"build": "tsc",
"start": "node dist/server.js",
"dev": "ts-node --esm src/server.ts",
"inspect": "npx @modelcontextprotocol/inspector node dist/server.js"
}
}
Building Your First MCP Server
Basic Server Structure
Create src/server.ts:
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create the MCP server
const server = new McpServer({
name: "My First MCP Server",
version: "1.0.0",
});
// Add your first tool (using modern register API)
server.registerTool(
"hello_world",
{
description: "Say hello with a custom message",
inputSchema: {
message: z.string().describe("Message to display")
}
},
async ({ message }: { message: string }) => {
return {
content: [
{
type: "text",
text: `Hello, ${message}! This is your first MCP tool.`,
},
],
};
}
);
// Add a simple file reader tool
server.registerTool(
"read_file",
{
description: "Read content from a file",
inputSchema: {
path: z.string().describe("File path to read")
}
},
async ({ path }: { path: string }) => {
try {
const fs = await import('fs/promises');
const content = await fs.readFile(path, 'utf-8');
return {
content: [
{
type: "text",
text: content,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error reading file: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
};
}
}
);
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("🚀 MCP Server started successfully!");
}
// Error handling
process.on('uncaughtException', (error) => {
console.error('❌ Uncaught exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('❌ Unhandled rejection:', reason);
process.exit(1);
});
main().catch((error) => {
console.error('❌ Failed to start server:', error);
process.exit(1);
});
Testing Your Server
# Build and test
npm run build
npm run inspect
# This opens the MCP Inspector web interface
# where you can test your tools interactively
Connecting to Claude Desktop
To use your server with Claude Desktop, add it to your configuration:
// Claude Desktop config
// macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
// Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"my-first-server": {
"command": "node",
"args": ["/absolute/path/to/your/dist/server.js"],
"env": {}
}
}
}
After adding this configuration:
- Restart Claude Desktop
- You should see your tools available in the interface
- Try asking Claude to use your
hello_worldtool!
Key Concepts Learned
- MCP Server Structure: Basic setup with
McpServerand transport - Modern APIs: Using
registerToolwith JSON Schema validation - Error Handling: Proper async error handling and process management
- Testing: Using MCP Inspector for development and debugging
- Integration: Connecting your server to Claude Desktop