MCP in Production: Building Enterprise-Grade AI Integrations
The Model Context Protocol (MCP) has emerged as the standard for connecting AI assistants to external data sources and tools. But moving from prototype to production requires more than just implementing the protocol—it demands robust error handling, security considerations, and scalable architecture.
What is MCP?
MCP is an open protocol that standardizes how applications provide context to LLMs. Think of it as a USB-C port for AI applications—universal, standardized, and extensible.
Production Considerations
1. Authentication & Security
// Implement API key validation
const validateRequest = (req: Request): boolean => {
const apiKey = req.headers.get(x27x-api-keyx27);
return apiKey === process.env.MCP_API_KEY;
};
// Rate limiting
const rateLimiter = new Map<string, number>();
const checkRateLimit = (clientId: string): boolean => {
const now = Date.now();
const lastRequest = rateLimiter.get(clientId) || 0;
if (now - lastRequest < 1000) return false;
rateLimiter.set(clientId, now);
return true;
};
2. Error Handling
class MCPError extends Error {
constructor(
public code: string,
message: string,
public details?: unknown
) {
super(message);
}
}
// Structured error responses
const handleError = (error: unknown): MCErrorResponse => {
if (error instanceof MCPError) {
return {
error: {
code: error.code,
message: error.message,
details: error.details
}
};
}
return {
error: {
code: x27INTERNAL_ERRORx27,
message: x27An unexpected error occurredx27
}
};
};
3. Resource Management
// Connection pooling for database MCP servers
class ConnectionPool {
private pool: Pool;
constructor() {
this.pool = new Pool({
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
}
async query(sql: string, params: unknown[]): Promise<QueryResult> {
const client = await this.pool.connect();
try {
return await client.query(sql, params);
} finally {
client.release();
}
}
}
Real-World Implementation
Here is a complete MCP server for a PostgreSQL database:
import { Server } from x27@modelcontextprotocol/sdk/server/index.jsx27;
import { StdioServerTransport } from x27@modelcontextprotocol/sdk/server/stdio.jsx27;
import { Pool } from x27pgx27;
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const server = new Server(
{
name: x27postgres-mcp-serverx27,
version: x271.0.0x27
},
{
capabilities: {
resources: {},
tools: {}
}
}
);
// List available tables as resources
server.setRequestHandler(ListResourcesRequestSchema, async () => {
const result = await pool.query(
"SELECT table_name FROM information_schema.tables WHERE table_schema = x27publicx27"
);
return {
resources: result.rows.map(row => ({
uri: `postgres:///${row.table_name}`,
name: row.table_name,
mimeType: x27application/jsonx27
}))
};
});
// Execute read-only queries via tools
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== x27queryx27) {
throw new Error(`Unknown tool: ${request.params.name}`);
}
const sql = request.params.arguments?.sql as string;
// Security: Only allow SELECT statements
if (!sql.trim().toLowerCase().startsWith(x27selectx27)) {
throw new Error(x27Only SELECT queries are allowedx27);
}
const result = await pool.query(sql);
return {
content: [{
type: x27textx27,
text: JSON.stringify(result.rows, null, 2)
}]
};
});
const transport = new StdioServerTransport();
await server.connect(transport);
Monitoring & Observability
// Add structured logging
import { logger } from x27./loggerx27;
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const startTime = Date.now();
logger.info({
event: x27tool_call_startedx27,
tool: request.params.name,
timestamp: new Date().toISOString()
});
try {
const result = await executeTool(request);
logger.info({
event: x27tool_call_completedx27,
tool: request.params.name,
duration_ms: Date.now() - startTime,
status: x27successx27
});
return result;
} catch (error) {
logger.error({
event: x27tool_call_failedx27,
tool: request.params.name,
duration_ms: Date.now() - startTime,
error: error.message
});
throw error;
}
});
Deployment Patterns
Docker Containerization
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
USER node
CMD [x27nodex27, x27dist/server.jsx27]
Environment Configuration
# docker-compose.yml
version: x273.8x27
services:
mcp-server:
build: .
environment:
- DATABASE_URL=${DATABASE_URL}
- MCP_API_KEY=${MCP_API_KEY}
- NODE_ENV=production
restart: unless-stopped
healthcheck:
test: [x27CMDx27, x27curlx27, x27-fx27, x27http://localhost:3000/healthx27]
interval: 30s
timeout: 10s
retries: 3
Testing Your MCP Server
import { Client } from x27@modelcontextprotocol/sdk/client/index.jsx27;
import { InMemoryTransport } from x27@modelcontextprotocol/sdk/inMemory.jsx27;
describe(x27MCP Serverx27, () => {
it(x27should list resourcesx27, async () => {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const client = new Client({ name: x27test-clientx27, version: x271.0.0x27 });
await client.connect(clientTransport);
const resources = await client.listResources();
expect(resources.resources).toBeDefined();
});
});
Conclusion
MCP is transforming how we build AI-powered applications. By following production best practices—security, error handling, monitoring, and testing—you can build robust integrations that scale with your needs.
The protocolx27s simplicity is its strength, but production deployment requires the same rigor as any enterprise system. Start with the basics, add observability early, and iterate based on real usage patterns.
Have you implemented MCP in production? Share your experience in the comments.
Top comments (0)