πŸ“šguidebeginner

Complete MCP Setup Guide: From Installation to Production

Step-by-step guide to setting up Model Context Protocol servers and clients from development to production deployment.

ByMCP Directory Team
Published
⏱️30 minutes
setupinstallationconfigurationdeploymentgetting-started

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

  1. Prerequisites and Environment Setup
  2. Development Environment
  3. Creating Your First MCP Server
  4. Setting Up MCP Clients
  5. Testing and Validation
  6. Production Deployment
  7. Monitoring and Maintenance
  8. 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:

  1. Verify server is running and listening on correct port
  2. Check firewall settings
  3. Verify network connectivity
  4. 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:

  1. Check CPU and memory usage
  2. Review database query performance
  3. Optimize caching strategies
  4. 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:

  1. Explore Advanced Features: Review advanced MCP patterns for enterprise implementations
  2. Implement Security: Follow security best practices for production deployments
  3. Optimize Performance: Apply performance optimization techniques
  4. Join the Community: Connect with other developers in the MCP community
  5. 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.