Step-by-step tutorial for building MCP (Model Context Protocol) servers for Claude Code. TypeScript implementation with examples for database, API, and file sys
The Model Context Protocol (MCP) is what makes Claude Code extensible. It’s the standard that lets you give Claude access to your databases, APIs, file systems, and any custom tool you can imagine.
Building an MCP server is surprisingly straightforward. Let’s build one from scratch.
What is MCP?
MCP is a protocol that standardizes how AI models communicate with external tools. Think of it as USB for AI — a universal connector that lets any AI model use any tool.
An MCP server:
- Defines available tools (what can Claude do?)
- Handles tool calls (Claude says “query the database,” the server executes it)
- Returns results (sends data back to Claude)
Setting Up Your First MCP Server
Step 1: Project Setup
mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
npx tsc --init
Step 2: Basic Server Structure
// src/index.ts
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-custom-tools',
version: '1.0.0',
});
// Define a simple tool
server.tool(
'greet',
'Greet a user by name',
{
name: z.string().describe('The name to greet'),
},
async ({ name }) => ({
content: [
{
type: 'text',
text: `Hello, ${name}! Welcome to my MCP server.`,
},
],
})
);
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);
Step 3: Register with Claude Code
// In your project's .claude/settings.json or CLAUDE.md:
// Add the MCP server configuration
{
"mcpServers": {
"my-tools": {
"command": "npx",
"args": ["tsx", "/path/to/my-mcp-server/src/index.ts"]
}
}
}
Building a Database MCP Server
The most common use case: giving Claude access to your database.
// src/database-server.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
const server = new McpServer({
name: 'database-tools',
version: '1.0.0',
});
// Read-only query tool
server.tool(
'query_database',
'Execute a read-only SQL query against the database',
{
sql: z.string().describe('The SQL SELECT query to execute'),
},
async ({ sql }) => {
// Safety: only allow SELECT queries
if (!sql.trim().toUpperCase().startsWith('SELECT')) {
return {
content: [{
type: 'text',
text: 'Error: Only SELECT queries are allowed.',
}],
isError: true,
};
}
try {
const result = await pool.query(sql);
return {
content: [{
type: 'text',
text: JSON.stringify(result.rows, null, 2),
}],
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Query error: ${error.message}`,
}],
isError: true,
};
}
}
);
// Table schema tool
server.tool(
'list_tables',
'List all tables in the database with their columns',
{},
async () => {
const result = await pool.query(`
SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position
`);
return {
content: [{
type: 'text',
text: JSON.stringify(result.rows, null, 2),
}],
};
}
);
Building an API Integration Server
// src/api-server.ts
server.tool(
'search_github',
'Search GitHub repositories',
{
query: z.string().describe('Search query for repositories'),
language: z.string().optional().describe('Filter by programming language'),
},
async ({ query, language }) => {
const params = new URLSearchParams({
q: language ? `${query} language:${language}` : query,
sort: 'stars',
order: 'desc',
});
const response = await fetch(
`https://api.github.com/search/repositories?${params}`,
{ headers: { 'Accept': 'application/vnd.github.v3+json' } }
);
const data = await response.json();
const repos = data.items.slice(0, 10).map(repo => ({
name: repo.full_name,
description: repo.description,
stars: repo.stargazers_count,
url: repo.html_url,
}));
return {
content: [{
type: 'text',
text: JSON.stringify(repos, null, 2),
}],
};
}
);
Security Best Practices
- Never allow write operations without confirmation — read-only by default
- Validate and sanitize all inputs — use Zod schemas for type safety
- Limit query scope — restrict database access to specific schemas/tables
- Log all tool calls — audit trail for debugging and security
- Use environment variables for credentials — never hardcode secrets
- Rate limit tool calls — prevent runaway agent behavior
People Also Ask
What languages can MCP servers be built in?
The official SDK supports TypeScript/JavaScript and Python. Community SDKs exist for Go, Rust, and other languages.
Want to skip months of trial and error? We’ve distilled thousands of hours of prompt engineering into ready-to-use prompt packs that deliver results on day one. Our packs at wowhow.cloud include battle-tested prompts for marketing, coding, business, writing, and more — each one refined until it consistently produces professional-grade output.
Blog reader exclusive: Use code
BLOGREADER20for 20% off your entire cart. No minimum, no catch.
Related reading
Written by
WOWHOW
The WOWHOW team brings 14+ years of production engineering experience. Every tool and product in the catalog is built, tested, and curated in-house.
Monday memo · free
One insight, every Monday. 7am IST.
1 field report, 3 links, 1 tool we actually use. No fluff, no spam.
Beyond the writing
Need production-ready templates?
Free browser tools with no signup, plus premium dev templates and starter kits.



Comments · 0
Beta: comments are stored locally on your device and not visible to other readers.
No comments yet. Be the first to share your thoughts.