Build Your Own MCP Server: The 2026 Developer Guide
The Model Context Protocol (MCP) is the standard interface between AI coding agents and external tools. When Claude Code queries your database, reads a Notion page, or searches your internal documentation, it does so through MCP servers. While hundreds of pre-built MCP servers exist, the real power comes from building your own -- connecting Claude Code to your specific APIs, databases, and internal systems.
This guide walks through building a custom MCP server from scratch, using a database query server as the running example. By the end, you will have a working MCP server that gives Claude Code the ability to query your PostgreSQL database with natural language.
What Is MCP?
MCP (Model Context Protocol) is an open standard created by Anthropic that defines how AI agents communicate with external tools and data sources. Think of it as a USB port for AI: any agent that speaks MCP can connect to any tool that implements the MCP server interface, without custom integration code on either side.
Why Build a Custom MCP Server?
Pre-built MCP servers cover common use cases: filesystem access, GitHub, Slack, web search. But every engineering team has internal systems that no public MCP server covers. Your internal API, your specific database schema, your custom deployment pipeline -- these require custom MCP servers.
Common Custom MCP Server Use Cases
- Database queries: Let Claude Code query your production or staging database directly. Ask "how many users signed up this week?" and get the answer without writing SQL yourself
- Internal API access: Expose your internal microservices to Claude Code. "Deploy the staging build" or "Get the latest error logs from the payment service"
- Documentation search: Connect Claude Code to your Confluence, Notion, or internal wiki. The agent can reference your team's documentation while writing code
- CI/CD integration: Trigger builds, check pipeline status, and review deployment logs through Claude Code
- Monitoring and alerting: Query Datadog, Grafana, or your custom monitoring system. "What's the p99 latency for the checkout endpoint?"
Step 1: Project Setup
We will build the MCP server in TypeScript using the official @modelcontextprotocol/sdk package. This is the reference implementation maintained by Anthropic.
# Create project directory
mkdir mcp-database-server
cd mcp-database-server
# Initialize project
npm init -y
npm install @modelcontextprotocol/sdk pg zod
npm install -D typescript @types/node @types/pg tsx
# Create tsconfig
npx tsc --init --target es2022 --module nodenext \
--moduleResolution nodenext --outDir dist
Step 2: Define Your Tools
MCP tools are the functions your server exposes to AI agents. Each tool has a name, description, and input schema (defined using JSON Schema). The agent reads the description to understand when and how to use the tool.
// src/index.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-query",
version: "1.0.0",
});
// Tool 1: Execute a read-only SQL query
server.tool(
"query_database",
"Execute a read-only SQL query against the PostgreSQL database. " +
"Returns results as JSON. Only SELECT queries are allowed.",
{
sql: z.string().describe("The SQL SELECT query to execute"),
params: z.array(z.string()).optional()
.describe("Optional parameterized query values"),
},
async ({ sql, params }) => {
// Security: only allow SELECT queries
const trimmed = sql.trim().toUpperCase();
if (!trimmed.startsWith("SELECT")) {
return {
content: [{
type: "text",
text: "Error: Only SELECT queries are allowed.",
}],
};
}
try {
const result = await pool.query(sql, params || []);
return {
content: [{
type: "text",
text: JSON.stringify(result.rows, null, 2),
}],
};
} catch (error) {
return {
content: [{
type: "text",
text: `Query error: ${(error as Error).message}`,
}],
};
}
}
);
// Tool 2: List all tables and their columns
server.tool(
"list_tables",
"List all tables in the database with their column names and types.",
{},
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),
}],
};
}
);
Step 3: Implement the Transport
MCP servers communicate over a transport layer. The most common is stdio (standard input/output), where the AI agent spawns your server as a child process and communicates via stdin/stdout. This is what Claude Code uses by default.
// Add to src/index.ts
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Database MCP server running on stdio");
}
main().catch(console.error);
Step 4: Configure Claude Code
To use your MCP server with Claude Code, add it to your project's .claude/mcp.json configuration file.
// .claude/mcp.json
{
"mcpServers": {
"database": {
"command": "npx",
"args": ["tsx", "/path/to/mcp-database-server/src/index.ts"],
"env": {
"DATABASE_URL": "postgresql://user:pass@localhost:5432/mydb"
}
}
}
}
After adding this configuration, restart Claude Code. It will discover your MCP server and its tools automatically. You can then ask Claude Code to query your database in natural language.
Example Interactions After Setup
- "How many users signed up in the last 7 days?" -- Claude Code calls
query_databasewith the appropriate SQL - "What tables exist in the database?" -- Claude Code calls
list_tables - "Show me the top 10 orders by revenue this month" -- Claude Code generates a SQL query, executes it, and presents the results
Step 5: Add Resources
Resources are read-only data that the agent can reference. For a database server, exposing the schema as a resource helps the agent write better queries.
// Add to src/index.ts
server.resource(
"schema",
"database://schema",
async (uri) => {
const result = await pool.query(`
SELECT table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position
`);
return {
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(result.rows, null, 2),
}],
};
}
);
Security Considerations
MCP servers execute code on your machine with access to your systems. Security is not optional.
- Input validation: Never pass user input directly to SQL, shell commands, or API calls. Use parameterized queries and validate all inputs against expected schemas
- Read-only by default: Start with read-only access. Only add write capabilities when you have a specific use case and understand the risks
- Rate limiting: AI agents can make many rapid tool calls. Implement rate limiting to prevent accidental resource exhaustion
- Authentication: Use environment variables for credentials. Never hardcode secrets in your MCP server code
- Principle of least privilege: Create a dedicated database user with minimal permissions for your MCP server. It should not use your admin credentials
- Logging: Log every tool call for audit purposes. You need to know what the agent did with the access you gave it
// Rate limiting example
const rateLimiter = new Map<string, number[]>();
const RATE_LIMIT = 30; // max calls per minute
function checkRateLimit(toolName: string): boolean {
const now = Date.now();
const calls = rateLimiter.get(toolName) || [];
const recentCalls = calls.filter(t => now - t < 60000);
if (recentCalls.length >= RATE_LIMIT) return false;
recentCalls.push(now);
rateLimiter.set(toolName, recentCalls);
return true;
}
Testing Your MCP Server
Test your MCP server before connecting it to Claude Code. The MCP SDK includes an inspector tool for interactive testing.
# Run the MCP inspector
npx @modelcontextprotocol/inspector npx tsx src/index.ts
# This opens a web UI where you can:
# - See all registered tools and their schemas
# - Call tools with test inputs
# - Inspect responses
# - Debug issues before connecting to Claude Code
Publishing and Sharing
Once your MCP server works, you can share it with your team or the community.
- Internal sharing: Publish to your team's private npm registry or share the git repository. Include the
.claude/mcp.jsonsnippet in the README so teammates can configure it quickly - Public sharing: Publish to npm with a clear README. Submit to the MCP server registry at
github.com/modelcontextprotocol/serversfor visibility - Docker packaging: For servers that depend on external services (databases, APIs), provide a Docker Compose configuration that sets up the server and its dependencies together
Beyond Database Queries
The database query server is a starting point. The same pattern applies to any system you want to expose to AI agents. Replace the PostgreSQL calls with API requests to your internal services, file operations on your document store, or commands to your deployment pipeline. The MCP interface remains the same -- define tools, validate inputs, return results.
The most impactful custom MCP servers are the ones that eliminate context switching. Every time a developer would normally open a browser tab, log into a dashboard, or SSH into a server to check something, that is a candidate for an MCP tool. Build the server once, and every AI agent session from that point forward can access that information directly.
Build and Test MCP Servers with Beam
Use Beam's split panes to run your MCP server, inspector, and Claude Code side by side during development.
Download Beam Free