Anthropic’s Model Context Protocol (MCP) has become one of the most exciting developments in the AI ecosystem in 2026. It provides a standardized way for AI assistants like Claude to connect to external tools, databases, APIs, and services — turning a conversational AI into a powerful automation engine.
In this tutorial, you’ll learn how to build your very own MCP server using Node.js and TypeScript. By the end, you’ll have a working server that lets Claude interact with custom tools you define. Whether you want to automate workflows, query databases, or integrate third-party APIs, MCP makes it possible.
What Is MCP and Why Should You Care?
Before we start coding, let’s understand what MCP actually does. Think of it as a universal adapter between AI models and external services. Without MCP, every AI assistant needs custom integrations for every tool. With MCP, you build a server once, and any MCP-compatible AI client can use it.
The architecture is simple:
- MCP Host — The AI application (e.g., Claude Desktop, Claude Code)
- MCP Client — Built into the host, manages server connections
- MCP Server — Your code, exposing tools and resources
This is similar to how USB-C standardizes hardware connections. MCP standardizes AI-to-tool connections.
Prerequisites
Before you begin, make sure you have the following installed:
- Node.js 18+ — Download from nodejs.org
- npm (comes with Node.js)
- TypeScript — Install globally:
npm install -g typescript - A code editor — VS Code recommended
Verify your setup:
node --version # v18.x or higher
npm --version # 9.x or higher
tsc --version # 5.x or higher
Step 1: Initialize Your Project
Create a new directory for your MCP server and initialize a Node.js project:
mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @anthropic-ai/sdk @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
Here’s what each package does:
@modelcontextprotocol/sdk— The official MCP TypeScript SDKzod— Schema validation for tool parameterstsx— TypeScript execution without pre-compiling
Next, create a tsconfig.json file:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
Step 2: Create Your MCP Server
Create a src directory and add your main server file:
mkdir src
touch src/index.ts
Now, let’s build the server. Open src/index.ts and add the following code:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create the MCP server instance
const server = new McpServer({
name: "my-toolbox",
version: "1.0.0",
description: "A custom MCP server with practical tools",
});
// Tool 1: Get current weather data
server.tool(
"get_weather",
"Get the current weather for a given city",
{
city: z.string().describe("City name, e.g., 'San Francisco'"),
unit: z.enum(["celsius", "fahrenheit"]).default("celsius")
.describe("Temperature unit"),
},
async ({ city, unit }) => {
const temp = unit === "celsius" ? 22 : 72;
return {
content: [
{
type: "text",
text: "Weather in " + city + ": " + temp
+ "°" + (unit === "celsius" ? "C" : "F")
+ ", Partly Cloudy",
},
],
};
}
);
// Tool 2: Calculate math expressions safely
server.tool(
"calculate",
"Evaluate a mathematical expression safely",
{
expression: z.string().describe("Math expression, e.g., '2 + 3 * 4'"),
},
async ({ expression }) => {
try {
const sanitized = expression.replace(/[^0-9+\-*/().%\s]/g, "");
const result = Function('"use strict"; return (' + sanitized + ')')();
return {
content: [{ type: "text", text: expression + " = " + result }],
};
} catch (error) {
return {
content: [{ type: "text", text: "Error: Invalid expression" }],
isError: true,
};
}
}
);
// Tool 3: Generate a random password
server.tool(
"generate_password",
"Generate a secure random password",
{
length: z.number().min(8).max(128).default(16)
.describe("Password length"),
includeSymbols: z.boolean().default(true)
.describe("Include special characters"),
},
async ({ length, includeSymbols }) => {
const lowercase = "abcdefghijklmnopqrstuvwxyz";
const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const numbers = "0123456789";
const symbols = "!@#$%^&*()_+-=[]{}|;:',.<>?";
let chars = lowercase + uppercase + numbers;
if (includeSymbols) chars += symbols;
let password = "";
const array = new Uint32Array(length);
crypto.getRandomValues(array);
for (let i = 0; i < length; i++) {
password += chars[array[i] % chars.length];
}
return {
content: [{
type: "text",
text: "Generated password: " + password
+ "\nLength: " + length + " | Symbols: " + includeSymbols,
}],
};
}
);
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Server running on stdio");
}
main().catch(console.error);
Step 3: Configure Claude Desktop to Use Your Server
To test your MCP server, you need to connect it to Claude Desktop. Open (or create) the Claude configuration file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Add your server configuration:
{
"mcpServers": {
"my-toolbox": {
"command": "npx",
"args": ["tsx", "/path/to/my-mcp-server/src/index.ts"]
}
}
}
Replace the path with the actual location of your project. Restart Claude Desktop, and you should see a small hammer icon in the chat input area, indicating your tools are available.
Step 4: Test Your Tools
Open Claude Desktop and try these prompts:
- "What's the weather in Tokyo?" — Claude will call your
get_weathertool - "Calculate (1234 * 567) + 89" — Claude will use
calculate - "Generate a 20-character password with symbols" — Claude will invoke
generate_password
You'll notice Claude automatically selects the right tool based on your request. This is the power of MCP — the AI understands your intent and routes to the correct tool with the appropriate parameters.
Step 5: Add a Real-World API Integration
Let's add a more practical tool that fetches real data from an external API. Add this to your src/index.ts:
server.tool(
"get_github_user",
"Fetch GitHub user profile information",
{
username: z.string().describe("GitHub username"),
},
async ({ username }) => {
try {
const url = "https://api.github.com/users/" + username;
const response = await fetch(url, {
headers: { "User-Agent": "mcp-server/1.0" },
});
if (!response.ok) {
return {
content: [{ type: "text", text: "User not found: " + username }],
isError: true,
};
}
const data = await response.json();
return {
content: [{
type: "text",
text: "GitHub Profile: " + (data.name || data.login)
+ "\n- Bio: " + (data.bio || "No bio")
+ "\n- Public Repos: " + data.public_repos
+ "\n- Followers: " + data.followers
+ "\n- Following: " + data.following
+ "\n- Profile: " + data.html_url,
}],
};
} catch (error) {
return {
content: [{ type: "text", text: "Failed to fetch GitHub data." }],
isError: true,
};
}
}
);
Now you can ask Claude things like "Look up the GitHub profile for torvalds" and it will fetch real, live data from GitHub's API.
Step 6: Add Resources (Not Just Tools)
MCP servers can also expose resources — static or dynamic data that the AI can read. Let's add a resource that provides server status:
server.resource(
"server-status",
"status://server",
{
description: "Current MCP server status and available tools",
mimeType: "application/json",
},
async () => ({
contents: [{
uri: "status://server",
mimeType: "application/json",
text: JSON.stringify({
status: "running",
version: "1.0.0",
uptime: process.uptime(),
tools: ["get_weather", "calculate",
"generate_password", "get_github_user"],
}, null, 2),
}],
})
);
Resources are different from tools — they provide data that Claude can reference without needing to make a function call. Think of tools as actions and resources as context.
Best Practices for Building MCP Servers
After building several MCP servers, here are key patterns to follow:
1. Always Validate Input with Zod
Never trust raw input from the AI. Use Zod schemas to validate all parameters. This prevents injection attacks and ensures your tools receive clean data.
2. Return Structured Error Messages
When something goes wrong, return isError: true with a clear message. Claude uses these error messages to adjust its approach and communicate the issue to the user.
3. Keep Tools Focused
Each tool should do one thing well. Instead of a monolithic "do_everything" tool, create small, composable tools that Claude can chain together.
4. Use Descriptive Names and Descriptions
The tool name and description are what Claude uses to decide when to call your tool. Be specific: "Fetch the latest 10 commits from a GitHub repository" is better than "Get GitHub data."
5. Handle Rate Limits Gracefully
If your tools call external APIs, implement rate limiting and return meaningful retry information. Claude can then inform the user about rate limit constraints.
Debugging Tips
Debugging MCP servers can be tricky since they communicate over stdio. Here are some strategies:
- Use console.error for debug logs — stdout is reserved for MCP protocol messages
- Test tools independently by calling your tool handler functions directly in a test file
- Enable MCP inspector by setting
DEBUG=mcpas an environment variable - Use the MCP Inspector UI — run
npx @anthropic-ai/mcp-inspectorto get a visual debugging interface
What's Next?
Now that you have a working MCP server, here are some ideas to extend it:
- Connect to databases — Build tools that query PostgreSQL, MongoDB, or Supabase
- File system operations — Read, write, and search files on your system
- Web scraping — Fetch and parse web pages for Claude to analyze
- Slack/Email integration — Send messages, read inboxes, manage notifications
- CI/CD automation — Trigger builds, check deployment status, manage GitHub Actions
The MCP ecosystem is growing rapidly. Anthropic, Google, and other major players have adopted the protocol, making it the de facto standard for AI-tool communication in 2026. Building MCP servers today is like building REST APIs in 2010 — you're getting in early on a paradigm that will define the next decade of software development.
Conclusion
Building an MCP server is surprisingly straightforward. With just a few dozen lines of TypeScript, you've created a server that lets Claude fetch weather data, perform calculations, generate passwords, and query GitHub profiles. The protocol handles all the complex communication between the AI and your tools — you just focus on building great tool implementations.
The key takeaway: MCP turns AI assistants from chatbots into automation platforms. By building MCP servers, you're not just creating tools — you're expanding what AI can do in the real world. Start simple, iterate fast, and you'll be building production-grade AI automation in no time.
Have questions or want to share your MCP server? Drop a comment below. Happy building!