DEV Community

Cover image for πŸš€ Do you want to have your own MCP Server can be used anywhere from VSCode Copilot, n8n ?

πŸš€ Do you want to have your own MCP Server can be used anywhere from VSCode Copilot, n8n ?

πŸš€ Complete Guide: Deploying MCP Server on AWS EC2 with SSE Transport and HTTPS

Model Context Protocol (MCP) is revolutionizing how AI applications interact with external data sources and tools. In this comprehensive guide, we'll walk through deploying a production-ready MCP server on AWS EC2 with Server-Sent Events (SSE) transport, complete with HTTPS SSL certificates and proper Nginx configuration.

πŸ“‹ What You'll Learn

  • Understanding MCP and SSE transport protocol
  • Building and pushing Docker images to AWS ECR
  • Deploying MCP server on EC2 with proper security
  • Configuring Nginx for SSE streaming with SSL
  • Troubleshooting common MCP deployment issues
  • Testing with n8n and other MCP clients

πŸ” Understanding MCP and SSE Transport

What is Model Context Protocol (MCP)?

Model Context Protocol is an open standard that enables AI applications to securely connect to external data sources, databases, and tools. It provides a standardized way for Large Language Models (LLMs) to access real-time information and execute actions.

Key Benefits:

  • Standardized Interface: Consistent API across different tools and services
  • Security: Controlled access to external resources
  • Real-time Data: Live connections to databases, APIs, and file systems
  • Extensibility: Easy to add new tools and capabilities

Why SSE (Server-Sent Events) Transport?

Server-Sent Events provide unidirectional, real-time communication from server to client over HTTP. Unlike WebSockets, SSE is simpler and perfect for MCP's use case:

SSE Advantages for MCP:

  • HTTP-based: Works with existing infrastructure (load balancers, proxies)
  • Auto-reconnection: Built-in connection recovery
  • Simpler than WebSockets: Less overhead for one-way communication
  • Firewall-friendly: Uses standard HTTP/HTTPS ports

MCP Protocol Flow:

1. Client β†’ GET /sse β†’ Server (Establish SSE stream for responses)
2. Client β†’ POST /messages β†’ Server (Send MCP commands)  
3. Server β†’ SSE Stream β†’ Client (Send responses back)
Enter fullscreen mode Exit fullscreen mode

πŸ—οΈ Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    HTTPS    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    HTTP    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   MCP       β”‚ ──────────► β”‚   Nginx     β”‚ ─────────► β”‚   Docker    β”‚
β”‚   Client    β”‚             β”‚   Proxy     β”‚            β”‚   Container β”‚
β”‚  (n8n/VSC)  β”‚             β”‚   + SSL     β”‚            β”‚ (MCP Server)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                   β”‚
                            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                            β”‚ Let's       β”‚
                            β”‚ Encrypt     β”‚
                            β”‚ SSL Cert    β”‚
                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

πŸ› οΈ Prerequisites

  • AWS Account with CLI configured
  • Docker installed locally
  • Domain name with DNS management access
  • Basic knowledge of Linux/Ubuntu commands

πŸ“¦ Step 1: Build and Push Docker Image to ECR

1.1 Create ECR Repository

# Create ECR repository
aws ecr create-repository --repository-name mcp-server --region us-east-1

# Get login token
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com
Enter fullscreen mode Exit fullscreen mode

1.2 Build and Tag Docker Image

# Build your MCP server image
docker build -t mcp-server:latest .

# Tag for ECR
docker tag mcp-server:latest <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/mcp-server:latest

# Push to ECR
docker push <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/mcp-server:latest
Enter fullscreen mode Exit fullscreen mode

πŸ–₯️ Step 2: Launch and Configure EC2 Instance

2.1 Launch EC2 Instance

Via AWS Console:

  1. AMI: Ubuntu Server 22.04 LTS
  2. Instance Type: t3.small (or t2.micro for testing)
  3. Key Pair: Create or select existing
  4. Security Group: Create with these ports:
    • SSH (22): Your IP
    • HTTP (80): 0.0.0.0/0
    • HTTPS (443): 0.0.0.0/0
    • Custom (8000): 0.0.0.0/0 (for testing)

2.2 Create IAM Role for ECR Access

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Attach Policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage"
      ],
      "Resource": "*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

🐳 Step 3: Setup Docker and Deploy Container

3.1 SSH into EC2 and Install Dependencies

# SSH into your instance
ssh -i your-key.pem ubuntu@<EC2_PUBLIC_IP>

# Update system
sudo apt update && sudo apt upgrade -y

# Install Docker
sudo apt install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -a -G docker ubuntu

# Apply docker group (avoid logout)
newgrp docker

# Install AWS CLI
sudo apt install -y awscli
Enter fullscreen mode Exit fullscreen mode

3.2 Pull and Run MCP Server Container

# Login to ECR
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com

# Pull image
docker pull <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/mcp-server:latest

# Create logs directory
mkdir -p ~/mcp-logs

# Run container
docker run -d \
  --name mcp-server \
  --restart unless-stopped \
  -p 8000:8000 \
  -v ~/mcp-logs:/app/logs \
  <ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/mcp-server:latest

# Verify container is running
docker ps
docker logs mcp-server

# Test endpoint
curl http://localhost:8000/sse
Enter fullscreen mode Exit fullscreen mode

🌐 Step 4: Configure DNS

Add an A record in your DNS provider:

Type: A
Name: mcp-server
Value: <EC2_PUBLIC_IP>
TTL: 300
Enter fullscreen mode Exit fullscreen mode

Verify DNS propagation:

nslookup mcp-server.yourdomain.com
dig mcp-server.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

πŸ”§ Step 5: Install and Configure Nginx

5.1 Install Nginx

sudo apt install -y nginx
sudo systemctl start nginx
sudo systemctl enable nginx
Enter fullscreen mode Exit fullscreen mode

5.2 Configure Nginx for MCP SSE Transport

⚠️ Critical Configuration for MCP Protocol:

sudo tee /etc/nginx/sites-available/mcp-server.conf > /dev/null << 'EOF'
server {
    listen 80;
    server_name mcp-server.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name mcp-server.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/mcp-server.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mcp-server.yourdomain.com/privkey.pem;

    # SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;

    # CRITICAL: SSE endpoint - ONLY GET requests
    location = /sse {
        if ($request_method != GET) {
            return 405;
        }

        proxy_pass http://127.0.0.1:8000/sse;

        # Essential for SSE streaming
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Standard proxy headers
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # SSE specific - NO BUFFERING
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;

        # CORS headers
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, OPTIONS';
        add_header Access-Control-Allow-Headers 'Content-Type, Authorization';
    }

    # CRITICAL: Messages endpoint for POST requests
    location /messages {
        proxy_pass http://127.0.0.1:8000;

        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Content-Type $content_type;

        # CORS headers
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        add_header Access-Control-Allow-Headers 'Content-Type, Authorization';
    }

    # Handle CORS preflight requests
    location / {
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
            add_header Access-Control-Allow-Headers 'Content-Type, Authorization';
            add_header Content-Length 0;
            add_header Content-Type text/plain;
            return 200;
        }

        return 301 /sse;
    }
}
EOF

# Enable the configuration
sudo ln -s /etc/nginx/sites-available/mcp-server.conf /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

πŸ”’ Step 6: Setup SSL Certificate with Let's Encrypt

6.1 Install Certbot

sudo apt install -y certbot python3-certbot-nginx
Enter fullscreen mode Exit fullscreen mode

6.2 Obtain SSL Certificate

sudo certbot --nginx -d mcp-server.yourdomain.com --email your@email.com --agree-tos --non-interactive
Enter fullscreen mode Exit fullscreen mode

6.3 Setup Auto-renewal

echo "0 12 * * * /usr/bin/certbot renew --quiet" | sudo tee -a /etc/crontab > /dev/null
Enter fullscreen mode Exit fullscreen mode

6.4 Verify SSL

sudo certbot certificates
curl -v https://mcp-server.yourdomain.com/sse
Enter fullscreen mode Exit fullscreen mode

🚨 Critical: Understanding the Nginx Configuration

Why This Configuration is Essential for MCP

❌ Common Mistake - Single Endpoint:

# This WILL NOT WORK for MCP
location /sse {
    proxy_pass http://localhost:8000/sse;
    # Tries to handle both GET and POST on same endpoint
}
Enter fullscreen mode Exit fullscreen mode

βœ… Correct Configuration - Separate Endpoints:

# GET /sse - For SSE streaming (responses)
location = /sse {
    if ($request_method != GET) { return 405; }
    proxy_pass http://127.0.0.1:8000/sse;
}

# POST /messages - For MCP commands
location /messages {
    proxy_pass http://127.0.0.1:8000;
}
Enter fullscreen mode Exit fullscreen mode

Why MCP Needs Two Endpoints

MCP Protocol Requirements:

  1. SSE Stream (GET /sse): Persistent connection for receiving responses
  2. Command Channel (POST /messages): Send MCP commands and requests

What Happens:

Client establishes: GET /sse β†’ Long-lived SSE connection
Client sends command: POST /messages/?session_id=xxx β†’ JSON-RPC request
Server responds via: SSE stream β†’ Real-time response
Enter fullscreen mode Exit fullscreen mode

Common Errors and Solutions

Error: RuntimeError: Expected ASGI message 'http.response.body'

  • Cause: POST requests sent to SSE endpoint
  • Solution: Separate GET and POST endpoints as shown above

Error: 405 Method Not Allowed

  • Cause: Wrong HTTP method for endpoint
  • Solution: Ensure GET for /sse, POST for /messages

βœ… Step 7: Testing Your MCP Server

7.1 Basic Connectivity Tests

# Test SSE endpoint
curl -v -H "Accept: text/event-stream" https://mcp-server.yourdomain.com/sse

# Expected output:
# event: endpoint
# data: /messages/?session_id=xxx
# : ping - 2024-01-01T12:00:00+00:00

# Test HTTPS redirect
curl -v http://mcp-server.yourdomain.com/sse
# Should redirect to HTTPS
Enter fullscreen mode Exit fullscreen mode

7.2 Monitor Server Logs

# Watch container logs
docker logs mcp-server -f

# Expected successful logs:
# INFO: 172.17.0.1:40138 - "GET /sse HTTP/1.1" 200 OK
# INFO: 172.17.0.1:40152 - "POST /messages/?session_id=xxx HTTP/1.1" 202 Accepted
Enter fullscreen mode Exit fullscreen mode

7.3 Test with VSCode Copilot, n8n

Image description

Add MCP Tool in n8n:

{
  "url": "https://mcp-server.yourdomain.com/sse",
  "transport": "SSE"
}
Enter fullscreen mode Exit fullscreen mode

Successful Connection Indicators:

  • n8n shows "Connected" status
  • Server logs show both GET and POST requests
  • Tools are listed and callable

πŸ”§ Troubleshooting Common Issues

Issue 1: DNS Not Resolving

Symptoms:

nslookup mcp-server.yourdomain.com
# Returns NXDOMAIN
Enter fullscreen mode Exit fullscreen mode

Solutions:

  • Verify A record is created correctly
  • Wait for DNS propagation (up to 48 hours)
  • Use online DNS checkers to verify propagation

Issue 2: SSL Certificate Fails

Symptoms:

Certbot failed to authenticate some domains
DNS problem: NXDOMAIN looking up A for mcp-server.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Solutions:

  • Ensure DNS is fully propagated before running certbot
  • Verify domain points to correct IP
  • Check firewall allows HTTP (port 80) for validation

Issue 3: MCP Client Can't Connect

Symptoms:

  • Client shows "Connection failed"
  • 405 Method Not Allowed errors
  • ASGI protocol errors

Solutions:

  • Verify Nginx configuration separates GET and POST endpoints
  • Check CORS headers are present
  • Ensure container is running and accessible on port 8000

Issue 4: SSE Stream Disconnects

Symptoms:

  • Connection drops after few seconds
  • Client keeps reconnecting

Solutions:

  • Verify proxy_buffering off in Nginx
  • Check proxy_read_timeout is set high (3600s)
  • Ensure container handles SSE properly

πŸš€ Production Considerations

Security Hardening

# Restrict SSH access
sudo ufw enable
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'

# Update security group to restrict SSH to your IP only
Enter fullscreen mode Exit fullscreen mode

Monitoring and Logging

# Setup log rotation
sudo tee /etc/logrotate.d/mcp-server << EOF
~/mcp-logs/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
}
EOF

# Monitor with CloudWatch (optional)
# Install CloudWatch agent for detailed monitoring
Enter fullscreen mode Exit fullscreen mode

Backup Strategy

# Backup container logs
tar -czf mcp-logs-backup-$(date +%Y%m%d).tar.gz ~/mcp-logs/

# Backup Nginx configuration
sudo cp /etc/nginx/sites-available/mcp-server.conf ~/nginx-backup.conf
Enter fullscreen mode Exit fullscreen mode

🎯 Final Verification Checklist

  • [ ] Container running and accessible on port 8000
  • [ ] DNS resolves to correct IP address
  • [ ] HTTPS certificate valid and auto-renewing
  • [ ] GET /sse returns SSE stream
  • [ ] POST /messages accepts JSON requests
  • [ ] MCP client (n8n/VS Code) connects successfully
  • [ ] Server logs show both GET and POST requests
  • [ ] Tools are discoverable and executable

πŸŽ‰ Conclusion

You now have a production-ready MCP server running on AWS EC2 with:

  • Secure HTTPS with automatic SSL certificate renewal
  • Proper SSE transport configuration for real-time communication
  • Scalable architecture ready for production workloads
  • Comprehensive monitoring and troubleshooting capabilities

The key insight from this deployment is understanding that MCP requires two separate endpoints: one for SSE streaming (GET /sse) and another for command messages (POST /messages). This separation is crucial for the protocol to work correctly with clients like n8n, VS Code, and other MCP-compatible tools.

Next Steps

  • Scale horizontally: Use Application Load Balancer for multiple instances
  • Add monitoring: Implement CloudWatch dashboards and alerts
  • Enhance security: Add API authentication and rate limiting
  • Optimize performance: Implement caching and connection pooling

πŸ“š Additional Resources


Happy deploying! πŸš€ If you found this guide helpful, please share it with others building MCP servers.

Top comments (0)