Building Production Model Context Protocol (MCP) Servers: A Developer's Practical Guide
Anthropic's open-source Model Context Protocol (MCP) has rapidly become the USB-C of the artificial intelligence era. Instead of building brittle, custom tool-calling wrappers for every LLM, MCP establishes a universal contract. Here is how to build production-grade servers in TypeScript and Python.
Cursor AI & Claude 3.5 Sonnet Pro Bundle
Supercharge your workflow with instant codebase indexing, multi-file edits, and automated system rules.
β‘ MCP Core Architecture: The 3 Core Primitives
Functions the LLM can execute with side-effects: querying SQL databases, sending Slack alerts, creating GitHub PRs.
Static or dynamic data streams the model can attach as context: API docs, system telemetry logs, file trees.
Pre-engineered behavioral workflows exposed to the user as slash commands or automated agent sub-routines.
1. Building an MCP Server in TypeScript with Zod
The official @modelcontextprotocol/sdk provides first-class support for Stdio transport and Zod runtime schema validation. Here is a complete, deployable PostgreSQL query inspector tool:
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
const server = new Server({
name: 'postgres-analytical-mcp',
version: '1.0.0',
}, {
capabilities: { tools: {} }
});
// Define tool schema
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'execute_safe_sql',
description: 'Executes a read-only SELECT query against the analytics database.',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'SQL query beginning with SELECT or EXPLAIN' },
limit: { type: 'number', description: 'Maximum rows (max 500)', default: 50 }
},
required: ['query']
}
}
]
}));
// Handle execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'execute_safe_sql') {
const { query, limit } = request.params.arguments as any;
// Security Guardrail: Reject any mutations
if (/^\s*(DROP|DELETE|UPDATE|INSERT|ALTER|TRUNCATE)/i.test(query)) {
throw new Error('Forbidden: Only read-only SELECT queries are allowed.');
}
// Execute query safely...
return {
content: [{ type: 'text', text: JSON.stringify({ status: 'SUCCESS', rows: [] }) }]
};
}
throw new Error('Tool not found');
});
const transport = new StdioServerTransport();
await server.connect(transport); 2. Connecting Your MCP Server to Claude Desktop and Cursor
To make your custom MCP server accessible to Claude Desktop, add it to your claude_desktop_config.json:
{
"mcpServers": {
"postgres-analyst": {
"command": "node",
"args": ["/absolute/path/to/dist/server.js"],
"env": {
"DATABASE_URL": "postgresql://user:pass@localhost:5432/analytics"
}
}
}
} Once restarted, Claude will display a hammer icon in the prompt input indicating your database tool is live and available for autonomous execution.
Cursor AI & Claude 3.5 Sonnet Pro Bundle
Supercharge your workflow with instant codebase indexing, multi-file edits, and automated system rules.
3. Production Security & Injection Defense
π‘οΈ Defense 1: Read-Only Database Credentials
Never rely solely on application regex to block `DROP TABLE` statements. Always configure the database user connection with GRANT SELECT ON ALL TABLES and revoke insert/update/delete permissions at the PostgreSQL role level.
π‘οΈ Defense 2: Parameterized Inputs & Schema Hardening
Treat all arguments passed by the model as untrusted input. Validate types using Zod schemas with strict boundaries on array lengths and string characters.