Complete MCP Setup Guide: From Installation to Production
This comprehensive guide walks you through setting up Model Context Protocol (MCP) servers and clients from initial development environment to production deployment. Whether you're building your first MCP implementation or deploying to enterprise environments, this guide covers all the essential steps.
Table of Contents
- Prerequisites and Environment Setup
- Development Environment
- Creating Your First MCP Server
- Setting Up MCP Clients
- Testing and Validation
- Production Deployment
- Monitoring and Maintenance
- Troubleshooting Common Issues
Prerequisites and Environment Setup
System Requirements
Development Machine:
- CPU: 2+ cores recommended
- RAM: 8GB minimum, 16GB recommended
- Storage: 5GB free space
- OS: Windows 10+, macOS 10.15+, or Linux (Ubuntu 18.04+)
Production Environment:
- CPU: 4+ cores for high-traffic servers
- RAM: 16GB minimum, 32GB+ for enterprise
- Storage: SSD recommended, 50GB+ available
- Network: Stable internet connection with low latency
Required Software
# Node.js (v18 or later)
# Check version
node --version
npm --version
# Python (v3.8 or later) - if using Python SDK
python --version
pip --version
# Git
git --version
# Docker (optional but recommended)
docker --version
docker-compose --version
Installing Prerequisites
Option 1: Node.js Setup
Using Node Version Manager (recommended):
# Install nvm (macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# Windows users: Download and install from nodejs.org
# or use nvm-windows: https://github.com/coreybutler/nvm-windows
# Restart terminal, then:
nvm install 18
nvm use 18
nvm alias default 18
Direct Installation:
- Download from nodejs.org
- Choose LTS version (v18 or later)
- Follow platform-specific installation instructions
Option 2: Python Setup
# Using pyenv (recommended)
curl https://pyenv.run | bash
# Add to your shell profile (~/.bashrc, ~/.zshrc, etc.)
export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
# Install Python
pyenv install 3.11.0
pyenv global 3.11.0
# Verify installation
python --version
pip --version
Development Environment
1. Setting Up Your Workspace
# Create project directory
mkdir mcp-projects
cd mcp-projects
# Create directory structure
mkdir -p {servers,clients,docs,tests,configs}
# Initialize workspace
echo "# MCP Workspace" > README.md
git init
2. Editor Configuration
VS Code Setup (recommended):
# Install VS Code extensions
code --install-extension ms-vscode.vscode-typescript-next
code --install-extension bradlc.vscode-tailwindcss
code --install-extension esbenp.prettier-vscode
code --install-extension ms-python.python
# Create workspace settings
mkdir .vscode
cat > .vscode/settings.json << EOF
{
"typescript.preferences.importModuleSpecifier": "relative",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"python.defaultInterpreterPath": "./venv/bin/python"
}
EOF
3. Environment Variables Setup
Create environment configuration files:
# Development environment
cat > .env.development << EOF
# MCP Server Configuration
MCP_SERVER_PORT=3000
MCP_SERVER_HOST=localhost
NODE_ENV=development
# Logging
LOG_LEVEL=debug
LOG_FORMAT=pretty
# Database (if applicable)
DATABASE_URL=sqlite:./dev.db
# Security
JWT_SECRET=your-development-secret-key
API_KEY=dev-api-key-12345
# External Services
EXTERNAL_API_URL=https://api.example.com
EXTERNAL_API_KEY=your-api-key
EOF
# Production template
cat > .env.production.template << EOF
# MCP Server Configuration
MCP_SERVER_PORT=8080
MCP_SERVER_HOST=0.0.0.0
NODE_ENV=production
# Logging
LOG_LEVEL=info
LOG_FORMAT=json
# Database
DATABASE_URL=postgresql://user:pass@host:5432/dbname
# Security (CHANGE THESE!)
JWT_SECRET=CHANGE_THIS_IN_PRODUCTION
API_KEY=GENERATE_SECURE_API_KEY
# External Services
EXTERNAL_API_URL=https://api.production.com
EXTERNAL_API_KEY=PRODUCTION_API_KEY
EOF
# Add to .gitignore
cat > .gitignore << EOF
# Environment files
.env
.env.local
.env.development
.env.production
# Dependencies
node_modules/
venv/
__pycache__/
# Build outputs
dist/
build/
*.tsbuildinfo
# IDE
.vscode/settings.json
.idea/
# OS
.DS_Store
Thumbs.db
# Logs
*.log
logs/
# Runtime
pids/
*.pid
*.seed
*.pid.lock
EOF
Creating Your First MCP Server
1. TypeScript/Node.js Server
Initialize Project
# Create server project
mkdir servers/my-first-mcp-server
cd servers/my-first-mcp-server
# Initialize package.json
npm init -y
# Install dependencies
npm install @modelcontextprotocol/sdk
npm install --save-dev typescript @types/node tsx nodemon
# Create TypeScript configuration
cat > tsconfig.json << EOF
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
EOF
# Update package.json scripts
cat > package.json << EOF
{
"name": "my-first-mcp-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"lint": "tsc --noEmit"
},
"dependencies": {
"@modelcontextprotocol/sdk": "latest"
},
"devDependencies": {
"@types/node": "^18.0.0",
"tsx": "^4.0.0",
"typescript": "^5.0.0",
"nodemon": "^3.0.0"
}
}
EOF
Create Basic Server
// src/index.ts
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
class MyFirstMCPServer {
private server: Server;
constructor() {
this.server = new Server(
{
name: "my-first-mcp-server",
version: "1.0.0",
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupHandlers();
}
private setupHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "hello",
description: "Say hello with a personalized message",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name to greet",
},
},
required: ["name"],
},
},
{
name: "calculate",
description: "Perform basic arithmetic calculations",
inputSchema: {
type: "object",
properties: {
expression: {
type: "string",
description: "Mathematical expression (e.g., '2 + 2')",
},
},
required: ["expression"],
},
},
],
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "hello":
return {
content: [
{
type: "text",
text: `Hello, ${args.name}! Welcome to your first MCP server.`,
},
],
};
case "calculate":
const result = this.safeCalculate(args.expression);
return {
content: [
{
type: "text",
text: `${args.expression} = ${result}`,
},
],
};
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
});
// List available resources
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "info://server",
name: "Server Information",
description: "Basic information about this MCP server",
mimeType: "application/json",
},
],
};
});
// Handle resource reads
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === "info://server") {
const serverInfo = {
name: "My First MCP Server",
version: "1.0.0",
description: "A simple MCP server for learning",
capabilities: ["tools", "resources"],
uptime: process.uptime(),
memoryUsage: process.memoryUsage(),
};
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify(serverInfo, null, 2),
},
],
};
}
throw new Error(`Unknown resource: ${uri}`);
});
}
private safeCalculate(expression: string): number {
// Simple calculator - only allow basic operations
const sanitized = expression.replace(/[^0-9+\-*/().\s]/g, "");
if (sanitized !== expression) {
throw new Error("Invalid characters in expression");
}
try {
// Use Function constructor for safer evaluation
const result = Function(`"use strict"; return (${sanitized})`)();
if (typeof result !== "number" || !isFinite(result)) {
throw new Error("Invalid calculation result");
}
return result;
} catch (error) {
throw new Error("Failed to evaluate expression");
}
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("My First MCP Server is running on stdio");
}
}
// Start the server
if (import.meta.url === `file://${process.argv[1]}`) {
const server = new MyFirstMCPServer();
server.start().catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});
}
Test Your Server
# Run in development mode
npm run dev
# In another terminal, test with a simple client
echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}' | node dist/index.js
# Build for production
npm run build
npm start
2. Python Server Alternative
# Create Python server project
cd ../..
mkdir servers/my-python-mcp-server
cd servers/my-python-mcp-server
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install mcp
# Create requirements.txt
pip freeze > requirements.txt
# src/server.py
#!/usr/bin/env python3
import asyncio
import json
from typing import Any, Sequence
from mcp.server import Server
from mcp.types import (
Resource,
Tool,
TextContent,
CallToolResult,
ListResourcesResult,
ReadResourceResult,
ListToolsResult,
)
import mcp.server.stdio
class MyPythonMCPServer:
def __init__(self):
self.server = Server("my-python-mcp-server")
self.setup_handlers()
def setup_handlers(self):
@self.server.list_tools()
async def list_tools() -> ListToolsResult:
return ListToolsResult(
tools=[
Tool(
name="greet",
description="Greet someone with a message",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string"},
"language": {"type": "string", "enum": ["en", "es", "fr"], "default": "en"}
},
"required": ["name"]
}
),
Tool(
name="reverse_text",
description="Reverse the given text",
inputSchema={
"type": "object",
"properties": {
"text": {"type": "string"}
},
"required": ["text"]
}
)
]
)
@self.server.call_tool()
async def call_tool(name: str, arguments: dict) -> CallToolResult:
if name == "greet":
return await self.handle_greet(arguments)
elif name == "reverse_text":
return await self.handle_reverse_text(arguments)
else:
raise ValueError(f"Unknown tool: {name}")
@self.server.list_resources()
async def list_resources() -> ListResourcesResult:
return ListResourcesResult(
resources=[
Resource(
uri="python://info",
name="Python Server Info",
description="Information about this Python MCP server",
mimeType="application/json"
)
]
)
@self.server.read_resource()
async def read_resource(uri: str) -> ReadResourceResult:
if uri == "python://info":
info = {
"name": "My Python MCP Server",
"version": "1.0.0",
"language": "Python",
"framework": "MCP Python SDK"
}
return ReadResourceResult(
contents=[
TextContent(
type="text",
text=json.dumps(info, indent=2)
)
]
)
else:
raise ValueError(f"Unknown resource: {uri}")
async def handle_greet(self, arguments: dict) -> CallToolResult:
name = arguments.get("name", "World")
language = arguments.get("language", "en")
greetings = {
"en": f"Hello, {name}!",
"es": f"Β‘Hola, {name}!",
"fr": f"Bonjour, {name}!"
}
greeting = greetings.get(language, greetings["en"])
return CallToolResult(
content=[
TextContent(
type="text",
text=greeting
)
]
)
async def handle_reverse_text(self, arguments: dict) -> CallToolResult:
text = arguments.get("text", "")
reversed_text = text[::-1]
return CallToolResult(
content=[
TextContent(
type="text",
text=f"Original: {text}\nReversed: {reversed_text}"
)
]
)
async def main():
server = MyPythonMCPServer()
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.server.run(
read_stream,
write_stream,
server.server.create_initialization_options()
)
if __name__ == "__main__":
asyncio.run(main())
Setting Up MCP Clients
1. Basic Test Client
// clients/test-client/src/index.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { spawn } from "child_process";
class MCPTestClient {
private client: Client;
private transport: StdioClientTransport;
constructor() {
this.client = new Client(
{
name: "test-client",
version: "1.0.0",
},
{
capabilities: {},
}
);
}
async connect(serverCommand: string, serverArgs: string[] = []) {
// Spawn server process
const serverProcess = spawn(serverCommand, serverArgs, {
stdio: ["pipe", "pipe", "inherit"],
});
// Create transport
this.transport = new StdioClientTransport({
stdin: serverProcess.stdin!,
stdout: serverProcess.stdout!,
});
// Connect client
await this.client.connect(this.transport);
console.log("Connected to MCP server");
}
async listTools() {
const result = await this.client.listTools();
console.log("Available tools:", JSON.stringify(result.tools, null, 2));
return result.tools;
}
async callTool(name: string, args: any) {
const result = await this.client.callTool({ name, arguments: args });
console.log(`Tool '${name}' result:`, result);
return result;
}
async listResources() {
const result = await this.client.listResources();
console.log("Available resources:", JSON.stringify(result.resources, null, 2));
return result.resources;
}
async readResource(uri: string) {
const result = await this.client.readResource({ uri });
console.log(`Resource '${uri}':`, result);
return result;
}
async disconnect() {
await this.client.close();
console.log("Disconnected from MCP server");
}
}
// Example usage
async function demo() {
const client = new MCPTestClient();
try {
// Connect to TypeScript server
await client.connect("node", ["../../servers/my-first-mcp-server/dist/index.js"]);
// Test tools
await client.listTools();
await client.callTool("hello", { name: "Developer" });
await client.callTool("calculate", { expression: "2 + 2 * 3" });
// Test resources
await client.listResources();
await client.readResource("info://server");
} catch (error) {
console.error("Error:", error);
} finally {
await client.disconnect();
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
demo();
}
2. Interactive CLI Client
// clients/cli-client/src/interactive.ts
import readline from "readline";
import { MCPTestClient } from "./test-client.js";
class InteractiveMCPClient {
private client: MCPTestClient;
private rl: readline.Interface;
private isConnected = false;
constructor() {
this.client = new MCPTestClient();
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
}
async start() {
console.log("Welcome to MCP Interactive Client!");
console.log("Type 'help' for available commands.");
this.showPrompt();
this.rl.on("line", async (input) => {
await this.handleCommand(input.trim());
this.showPrompt();
});
}
private showPrompt() {
const status = this.isConnected ? "connected" : "disconnected";
this.rl.setPrompt(`mcp-cli (${status})> `);
this.rl.prompt();
}
private async handleCommand(command: string) {
const [cmd, ...args] = command.split(" ");
try {
switch (cmd.toLowerCase()) {
case "help":
this.showHelp();
break;
case "connect":
await this.connectCommand(args);
break;
case "disconnect":
await this.disconnectCommand();
break;
case "tools":
await this.listToolsCommand();
break;
case "call":
await this.callToolCommand(args);
break;
case "resources":
await this.listResourcesCommand();
break;
case "read":
await this.readResourceCommand(args);
break;
case "exit":
case "quit":
await this.exitCommand();
break;
default:
console.log(`Unknown command: ${cmd}. Type 'help' for available commands.`);
}
} catch (error) {
console.error("Error:", error.message);
}
}
private showHelp() {
console.log(`
Available commands:
connect <server-command> [args...] - Connect to MCP server
disconnect - Disconnect from server
tools - List available tools
call <tool-name> <json-args> - Call a tool
resources - List available resources
read <resource-uri> - Read a resource
help - Show this help
exit - Exit the client
`);
}
private async connectCommand(args: string[]) {
if (args.length === 0) {
console.log("Usage: connect <server-command> [args...]");
return;
}
const [serverCommand, ...serverArgs] = args;
await this.client.connect(serverCommand, serverArgs);
this.isConnected = true;
console.log("Successfully connected to MCP server");
}
private async disconnectCommand() {
if (!this.isConnected) {
console.log("Not connected to any server");
return;
}
await this.client.disconnect();
this.isConnected = false;
console.log("Disconnected from server");
}
private async listToolsCommand() {
if (!this.isConnected) {
console.log("Not connected to any server");
return;
}
await this.client.listTools();
}
private async callToolCommand(args: string[]) {
if (!this.isConnected) {
console.log("Not connected to any server");
return;
}
if (args.length < 2) {
console.log("Usage: call <tool-name> <json-args>");
return;
}
const toolName = args[0];
const argsJson = args.slice(1).join(" ");
try {
const toolArgs = JSON.parse(argsJson);
await this.client.callTool(toolName, toolArgs);
} catch (error) {
console.error("Invalid JSON arguments:", error.message);
}
}
private async listResourcesCommand() {
if (!this.isConnected) {
console.log("Not connected to any server");
return;
}
await this.client.listResources();
}
private async readResourceCommand(args: string[]) {
if (!this.isConnected) {
console.log("Not connected to any server");
return;
}
if (args.length === 0) {
console.log("Usage: read <resource-uri>");
return;
}
await this.client.readResource(args[0]);
}
private async exitCommand() {
if (this.isConnected) {
await this.client.disconnect();
}
console.log("Goodbye!");
this.rl.close();
process.exit(0);
}
}
// Start interactive client
if (import.meta.url === `file://${process.argv[1]}`) {
const client = new InteractiveMCPClient();
client.start();
}
Testing and Validation
1. Automated Testing Setup
// tests/server.test.ts
import { describe, test, expect, beforeAll, afterAll } from "@jest/globals";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { spawn, ChildProcess } from "child_process";
describe("MCP Server Tests", () => {
let client: Client;
let serverProcess: ChildProcess;
let transport: StdioClientTransport;
beforeAll(async () => {
// Start server
serverProcess = spawn("node", ["../servers/my-first-mcp-server/dist/index.js"], {
stdio: ["pipe", "pipe", "inherit"],
});
// Create client
client = new Client(
{ name: "test-client", version: "1.0.0" },
{ capabilities: {} }
);
// Create transport and connect
transport = new StdioClientTransport({
stdin: serverProcess.stdin!,
stdout: serverProcess.stdout!,
});
await client.connect(transport);
});
afterAll(async () => {
await client.close();
serverProcess.kill();
});
test("should list available tools", async () => {
const result = await client.listTools();
expect(result.tools).toHaveLength(2);
expect(result.tools[0].name).toBe("hello");
expect(result.tools[1].name).toBe("calculate");
});
test("should execute hello tool", async () => {
const result = await client.callTool({
name: "hello",
arguments: { name: "Test" },
});
expect(result.content).toHaveLength(1);
expect(result.content[0].text).toContain("Hello, Test!");
});
test("should execute calculate tool", async () => {
const result = await client.callTool({
name: "calculate",
arguments: { expression: "2 + 2" },
});
expect(result.content).toHaveLength(1);
expect(result.content[0].text).toContain("= 4");
});
test("should list available resources", async () => {
const result = await client.listResources();
expect(result.resources).toHaveLength(1);
expect(result.resources[0].uri).toBe("info://server");
});
test("should read server info resource", async () => {
const result = await client.readResource({ uri: "info://server" });
expect(result.contents).toHaveLength(1);
const content = JSON.parse(result.contents[0].text);
expect(content.name).toBe("My First MCP Server");
expect(content.version).toBe("1.0.0");
});
});
2. Integration Test Script
#!/bin/bash
# tests/integration-test.sh
set -e
echo "π§ͺ Running MCP Integration Tests"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Test function
run_test() {
local test_name="$1"
local command="$2"
echo -e "${YELLOW}Testing: $test_name${NC}"
if eval "$command"; then
echo -e "${GREEN}β
$test_name passed${NC}"
return 0
else
echo -e "${RED}β $test_name failed${NC}"
return 1
fi
}
# Build servers
echo "ποΈ Building servers..."
cd servers/my-first-mcp-server
npm run build
cd ../..
# Start server in background
echo "π Starting server..."
node servers/my-first-mcp-server/dist/index.js &
SERVER_PID=$!
sleep 2
# Function to cleanup
cleanup() {
echo "π§Ή Cleaning up..."
kill $SERVER_PID 2>/dev/null || true
}
trap cleanup EXIT
# Run tests
TESTS_PASSED=0
TOTAL_TESTS=0
# Test 1: Server responds to list tools
TOTAL_TESTS=$((TOTAL_TESTS + 1))
if run_test "Server lists tools" 'echo '\''{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'\'' | timeout 5 node servers/my-first-mcp-server/dist/index.js | grep -q "hello"'; then
TESTS_PASSED=$((TESTS_PASSED + 1))
fi
# Test 2: Tool execution
TOTAL_TESTS=$((TOTAL_TESTS + 1))
if run_test "Hello tool execution" 'echo '\''{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "hello", "arguments": {"name": "Test"}}}'\'' | timeout 5 node servers/my-first-mcp-server/dist/index.js | grep -q "Hello, Test"'; then
TESTS_PASSED=$((TESTS_PASSED + 1))
fi
# Test 3: Resource listing
TOTAL_TESTS=$((TOTAL_TESTS + 1))
if run_test "Resource listing" 'echo '\''{"jsonrpc": "2.0", "id": 3, "method": "resources/list"}'\'' | timeout 5 node servers/my-first-mcp-server/dist/index.js | grep -q "info://server"'; then
TESTS_PASSED=$((TESTS_PASSED + 1))
fi
# Summary
echo ""
echo "π Test Results:"
echo "Passed: $TESTS_PASSED/$TOTAL_TESTS"
if [ $TESTS_PASSED -eq $TOTAL_TESTS ]; then
echo -e "${GREEN}π All tests passed!${NC}"
exit 0
else
echo -e "${RED}β Some tests failed${NC}"
exit 1
fi
3. Performance Testing
// tests/performance.js
import { performance } from 'perf_hooks';
import { MCPTestClient } from '../clients/test-client/src/index.js';
async function performanceTest() {
const client = new MCPTestClient();
try {
await client.connect('node', ['../servers/my-first-mcp-server/dist/index.js']);
console.log('π Running performance tests...\n');
// Test 1: Tool call latency
const iterations = 100;
const startTime = performance.now();
for (let i = 0; i < iterations; i++) {
await client.callTool('hello', { name: `User${i}` });
}
const endTime = performance.now();
const totalTime = endTime - startTime;
const avgLatency = totalTime / iterations;
console.log(`π Tool Call Performance:`);
console.log(` Total time: ${totalTime.toFixed(2)}ms`);
console.log(` Average latency: ${avgLatency.toFixed(2)}ms`);
console.log(` Requests per second: ${(1000 / avgLatency).toFixed(2)}`);
// Test 2: Resource read performance
const resourceStartTime = performance.now();
for (let i = 0; i < 50; i++) {
await client.readResource('info://server');
}
const resourceEndTime = performance.now();
const resourceTotalTime = resourceEndTime - resourceStartTime;
const avgResourceLatency = resourceTotalTime / 50;
console.log(`\nπ Resource Read Performance:`);
console.log(` Total time: ${resourceTotalTime.toFixed(2)}ms`);
console.log(` Average latency: ${avgResourceLatency.toFixed(2)}ms`);
// Memory usage
const memUsage = process.memoryUsage();
console.log(`\nπΎ Memory Usage:`);
console.log(` RSS: ${(memUsage.rss / 1024 / 1024).toFixed(2)} MB`);
console.log(` Heap Used: ${(memUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`);
} catch (error) {
console.error('Performance test failed:', error);
} finally {
await client.disconnect();
}
}
performanceTest();
Production Deployment
1. Docker Configuration
# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY tsconfig.json ./
# Install dependencies
RUN npm ci --only=production && npm cache clean --force
# Copy source code
COPY src/ ./src/
# Build application
RUN npm run build
# Production stage
FROM node:18-alpine AS production
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S mcpserver -u 1001
WORKDIR /app
# Copy built application
COPY --from=builder --chown=mcpserver:nodejs /app/dist ./dist
COPY --from=builder --chown=mcpserver:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=mcpserver:nodejs /app/package.json ./
# Set up logging directory
RUN mkdir -p /app/logs && chown mcpserver:nodejs /app/logs
USER mcpserver
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
CMD ["node", "dist/index.js"]
# docker-compose.yml
version: '3.8'
services:
mcp-server:
build: .
ports:
- "8080:8080"
environment:
- NODE_ENV=production
- LOG_LEVEL=info
volumes:
- ./logs:/app/logs
- ./config:/app/config:ro
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- mcp-network
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/ssl:ro
depends_on:
- mcp-server
networks:
- mcp-network
networks:
mcp-network:
driver: bridge
2. Production Server Configuration
// src/production-server.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { HTTPServerTransport } from "@modelcontextprotocol/sdk/server/http.js";
import express from "express";
import helmet from "helmet";
import rateLimit from "express-rate-limit";
import compression from "compression";
import cors from "cors";
class ProductionMCPServer {
private server: Server;
private app: express.Application;
constructor() {
this.server = new Server(
{
name: "production-mcp-server",
version: process.env.npm_package_version || "1.0.0",
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.app = express();
this.setupMiddleware();
this.setupHandlers();
}
private setupMiddleware() {
// Security middleware
this.app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
},
},
}));
// CORS
this.app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
credentials: true,
}));
// Compression
this.app.use(compression());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP',
});
this.app.use('/api/', limiter);
// Request parsing
this.app.use(express.json({ limit: '10mb' }));
this.app.use(express.urlencoded({ extended: true }));
}
private setupHandlers() {
// Health check endpoint
this.app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
version: process.env.npm_package_version || "1.0.0",
});
});
// Metrics endpoint
this.app.get('/metrics', (req, res) => {
res.json({
uptime: process.uptime(),
memory: process.memoryUsage(),
cpu: process.cpuUsage(),
timestamp: new Date().toISOString(),
});
});
// MCP handlers (same as before)
this.setupMCPHandlers();
}
private setupMCPHandlers() {
// [Include the same MCP handlers from the basic server]
}
async start() {
const port = parseInt(process.env.PORT || '8080');
const host = process.env.HOST || '0.0.0.0';
// Create HTTP transport
const transport = new HTTPServerTransport({
app: this.app,
path: '/mcp',
});
// Connect MCP server
await this.server.connect(transport);
// Start HTTP server
this.app.listen(port, host, () => {
console.log(`Production MCP Server running on http://${host}:${port}`);
console.log(`Health check: http://${host}:${port}/health`);
console.log(`MCP endpoint: http://${host}:${port}/mcp`);
});
// Graceful shutdown
process.on('SIGTERM', this.gracefulShutdown.bind(this));
process.on('SIGINT', this.gracefulShutdown.bind(this));
}
private async gracefulShutdown() {
console.log('Received shutdown signal, closing server gracefully...');
// Close MCP server
await this.server.close();
console.log('Server closed successfully');
process.exit(0);
}
}
// Start production server
if (import.meta.url === `file://${process.argv[1]}`) {
const server = new ProductionMCPServer();
server.start().catch((error) => {
console.error('Failed to start production server:', error);
process.exit(1);
});
}
3. Deployment Scripts
#!/bin/bash
# scripts/deploy.sh
set -e
echo "π Deploying MCP Server to Production"
# Load environment variables
if [ -f .env.production ]; then
export $(cat .env.production | grep -v '#' | xargs)
fi
# Build and test
echo "π¦ Building application..."
npm run build
echo "π§ͺ Running tests..."
npm test
# Docker build and deploy
echo "π³ Building Docker image..."
docker build -t mcp-server:latest .
echo "π Updating production containers..."
docker-compose -f docker-compose.production.yml down
docker-compose -f docker-compose.production.yml up -d
# Health check
echo "π₯ Performing health check..."
sleep 10
if curl -f http://localhost:8080/health; then
echo "β
Deployment successful!"
else
echo "β Health check failed!"
docker-compose -f docker-compose.production.yml logs
exit 1
fi
echo "π Checking metrics..."
curl -s http://localhost:8080/metrics | jq '.'
echo "π Deployment completed successfully!"
Monitoring and Maintenance
1. Logging Configuration
// src/utils/logger.ts
import winston from 'winston';
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
process.env.NODE_ENV === 'production'
? winston.format.json()
: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
),
defaultMeta: { service: 'mcp-server' },
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' }),
],
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
export default logger;
2. Monitoring Setup
# monitoring/docker-compose.monitoring.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/dashboards:/etc/grafana/provisioning/dashboards
- ./grafana/datasources:/etc/grafana/provisioning/datasources
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
node-exporter:
image: prom/node-exporter:latest
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
volumes:
prometheus_data:
grafana_data:
3. Backup and Recovery
#!/bin/bash
# scripts/backup.sh
BACKUP_DIR="/backups/mcp-server"
DATE=$(date +%Y%m%d_%H%M%S)
echo "π¦ Creating backup: $DATE"
# Create backup directory
mkdir -p "$BACKUP_DIR/$DATE"
# Backup configuration files
cp -r config/ "$BACKUP_DIR/$DATE/"
# Backup logs
cp -r logs/ "$BACKUP_DIR/$DATE/"
# Backup database (if applicable)
if [ -f "data/database.db" ]; then
cp data/database.db "$BACKUP_DIR/$DATE/"
fi
# Create archive
cd "$BACKUP_DIR"
tar -czf "mcp-server-backup-$DATE.tar.gz" "$DATE/"
rm -rf "$DATE/"
echo "β
Backup completed: mcp-server-backup-$DATE.tar.gz"
# Cleanup old backups (keep last 7 days)
find "$BACKUP_DIR" -name "mcp-server-backup-*.tar.gz" -mtime +7 -delete
Troubleshooting Common Issues
1. Connection Issues
Problem: Client cannot connect to server
# Check if server is running
ps aux | grep mcp-server
# Check port availability
netstat -tlnp | grep :8080
# Test connectivity
curl -f http://localhost:8080/health
# Check logs
tail -f logs/combined.log
Solution Steps:
- Verify server is running and listening on correct port
- Check firewall settings
- Verify network connectivity
- Review server logs for errors
2. Performance Issues
Problem: Slow response times
# Monitor resource usage
top -p $(pgrep -f mcp-server)
# Check memory usage
free -h
# Network connectivity
ping target-server
# Response time testing
curl -w "@curl-format.txt" -o /dev/null -s http://localhost:8080/health
Solution Steps:
- Check CPU and memory usage
- Review database query performance
- Optimize caching strategies
- Scale resources if needed
3. Common Error Messages
Error: "EADDRINUSE: address already in use"
# Find process using the port
lsof -i :8080
kill -9 <PID>
Error: "Module not found"
# Reinstall dependencies
rm -rf node_modules package-lock.json
npm install
Error: "Permission denied"
# Fix file permissions
chmod +x scripts/deploy.sh
chown -R $USER:$USER .
4. Debug Mode Setup
// Enable debug logging
process.env.DEBUG = 'mcp:*';
process.env.LOG_LEVEL = 'debug';
// Add debug middleware
this.app.use((req, res, next) => {
console.log(`${new Date().toISOString()} ${req.method} ${req.path}`);
next();
});
Next Steps
After completing this setup guide:
- Explore Advanced Features: Review advanced MCP patterns for enterprise implementations
- Implement Security: Follow security best practices for production deployments
- Optimize Performance: Apply performance optimization techniques
- Join the Community: Connect with other developers in the MCP community
- Build Custom Solutions: Use custom server development patterns for specific use cases
This comprehensive setup guide provides everything you need to go from zero to production with Model Context Protocol implementations. Start with the development environment, build and test your server, then deploy to production with confidence.