svrnty-mcp-server/docs/deployment/https-setup.md
Svrnty 516e1479c6 docs: comprehensive AI coding assistant research and MCP-first implementation plan
Research conducted on modern AI coding assistants (Cursor, GitHub Copilot, Cline,
Aider, Windsurf, Replit Agent) to understand architecture patterns, context management,
code editing workflows, and tool use protocols.

Key Decision: Pivoted from building full CLI (40-50h) to validation-driven MCP-first
approach (10-15h). Build 5 core CODEX MCP tools that work with ANY coding assistant,
validate adoption over 2-4 weeks, then decide on full CLI if demand proven.

Files:
- research/ai-systems/modern-coding-assistants-architecture.md (comprehensive research)
- research/ai-systems/codex-coding-assistant-implementation-plan.md (original CLI plan, preserved)
- research/ai-systems/codex-mcp-tools-implementation-plan.md (approved MCP-first plan)
- ideas/registry.json (updated with approved MCP tools proposal)

Architech Validation: APPROVED with pivot to MCP-first approach
Human Decision: Approved (pragmatic validation-driven development)

Next: Begin Phase 1 implementation (10-15 hours, 5 core MCP tools)

🤖 Generated with CODEX Research System

Co-Authored-By: The Archivist <archivist@codex.svrnty.io>
Co-Authored-By: The Architech <architech@codex.svrnty.io>
Co-Authored-By: Mathias Beaulieu-Duncan <mat@svrnty.io>
2025-10-22 21:00:34 -04:00

16 KiB

HTTPS/TLS Setup Guide - OpenHarbor.MCP.Server

Purpose: Production-grade HTTPS/TLS configuration for secure MCP server deployment Audience: DevOps engineers, system administrators Last Updated: 2025-10-19 Version: 1.0.0


Table of Contents

  1. Overview
  2. Prerequisites
  3. Development Setup
  4. Production Setup
  5. Certificate Management
  6. Security Headers (HSTS)
  7. Testing HTTPS
  8. Troubleshooting

Overview

OpenHarbor.MCP.Server supports HTTPS/TLS through ASP.NET Core's Kestrel web server. This guide covers configuration for both development and production environments.

Security Benefits:

  • Encrypted communication (prevents eavesdropping)
  • Client/server authentication
  • Data integrity verification
  • Compliance with security best practices

Default Behavior:

  • HTTP only (for development ease)
  • HTTPS must be explicitly configured
  • Uses Kestrel's built-in TLS support

Prerequisites

Development

  • .NET 8.0 SDK
  • OpenSSL or dotnet dev-certs tool
  • PowerShell (Windows) or bash (Linux/macOS)

Production

  • Valid TLS certificate (from CA or Let's Encrypt)
  • Private key file
  • Certificate chain (intermediate + root CA certs)
  • Reverse proxy (optional): Nginx, Traefik, or Kestrel standalone

Development Setup

Step 1: Generate Development Certificate

Windows / macOS / Linux:

# Generate and trust dev certificate
dotnet dev-certs https --trust

# Verify installation
dotnet dev-certs https --check

Step 2: Configure appsettings.Development.json

Create or update appsettings.Development.json in the Server project:

{
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://localhost:5050"
      },
      "Https": {
        "Url": "https://localhost:5051",
        "Certificate": {
          "Subject": "CN=localhost",
          "Store": "My",
          "Location": "CurrentUser",
          "AllowInvalid": true
        }
      }
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}

Step 3: Run Server with HTTPS

cd OpenHarbor.MCP.Server/samples/CodexMcpServer
dotnet run --environment Development

# Server will listen on:
# - HTTP:  http://localhost:5050
# - HTTPS: https://localhost:5051

Step 4: Test HTTPS Endpoint

# Test health endpoint
curl -k https://localhost:5051/health

# Test MCP invoke endpoint
curl -k -X POST https://localhost:5051/mcp/invoke \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/list",
    "id": "1"
  }'

Note

: -k flag skips certificate validation (dev only!)

Option 2: Custom Development Certificate with OpenSSL

Step 1: Generate Self-Signed Certificate

# Create directory for certs
mkdir -p /home/svrnty/codex/OpenHarbor.MCP.Server/certs

# Generate private key
openssl genrsa -out certs/localhost.key 2048

# Generate certificate signing request (CSR)
openssl req -new -key certs/localhost.key -out certs/localhost.csr \
  -subj "/C=CA/ST=Quebec/L=Montreal/O=Svrnty/CN=localhost"

# Generate self-signed certificate (valid for 365 days)
openssl x509 -req -days 365 -in certs/localhost.csr \
  -signkey certs/localhost.key -out certs/localhost.crt

# Convert to PFX format (required by Kestrel)
openssl pkcs12 -export -out certs/localhost.pfx \
  -inkey certs/localhost.key -in certs/localhost.crt \
  -passout pass:YourSecurePassword

Step 2: Configure appsettings.Development.json

{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://localhost:5051",
        "Certificate": {
          "Path": "certs/localhost.pfx",
          "Password": "YourSecurePassword"
        }
      }
    }
  }
}

Security: Add certs/ directory to .gitignore to avoid committing private keys.


Production Setup

Step 1: Obtain Production Certificate

Use one of:

  • Let's Encrypt (free, automated renewal)
  • Commercial CA (DigiCert, Sectigo, etc.)
  • Internal PKI (corporate certificate authority)

Step 2: Prepare Certificate Files

Ensure you have:

  • server.crt - Server certificate
  • server.key - Private key
  • ca-bundle.crt - Intermediate + root CA certificates (optional)

Step 3: Convert to PFX Format

# Combine cert + key + chain into PFX
openssl pkcs12 -export -out server.pfx \
  -inkey server.key \
  -in server.crt \
  -certfile ca-bundle.crt \
  -passout pass:ProductionSecurePassword

Step 4: Deploy Certificate

Docker/Kubernetes:

# Create Kubernetes secret
kubectl create secret generic mcp-server-tls \
  --from-file=server.pfx=server.pfx \
  --from-literal=password=ProductionSecurePassword

# Mount in deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server
spec:
  template:
    spec:
      containers:
      - name: mcp-server
        volumeMounts:
        - name: tls-cert
          mountPath: /app/certs
          readOnly: true
        env:
        - name: CERTIFICATE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mcp-server-tls
              key: password
      volumes:
      - name: tls-cert
        secret:
          secretName: mcp-server-tls

Step 5: Configure appsettings.Production.json

{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://*:5051",
        "Certificate": {
          "Path": "/app/certs/server.pfx",
          "Password": ""  // Read from environment variable
        },
        "Protocols": "Http1AndHttp2"
      }
    }
  },
  "AllowedHosts": "*"
}

Step 6: Set Environment Variable for Password

export KESTREL__CERTIFICATES__DEFAULT__PASSWORD="ProductionSecurePassword"

Or in docker-compose.yml:

services:
  mcp-server:
    environment:
      - KESTREL__CERTIFICATES__DEFAULT__PASSWORD=${CERTIFICATE_PASSWORD}
    volumes:
      - ./certs/server.pfx:/app/certs/server.pfx:ro

Option 2: Certificate from Azure Key Vault

Step 1: Store Certificate in Key Vault

az keyvault certificate import \
  --vault-name your-keyvault \
  --name mcp-server-cert \
  --file server.pfx \
  --password ProductionSecurePassword

Step 2: Configure appsettings.Production.json

{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://*:5051",
        "Certificate": {
          "KeyVaultUri": "https://your-keyvault.vault.azure.net/",
          "CertificateName": "mcp-server-cert"
        }
      }
    }
  }
}

Step 3: Grant Managed Identity Access

az keyvault set-policy \
  --name your-keyvault \
  --object-id <managed-identity-object-id> \
  --certificate-permissions get

Option 3: Reverse Proxy (Nginx/Traefik) with TLS Termination

Best for: Load balancing, multiple servers, centralized certificate management

Architecture:

Client → HTTPS → Nginx/Traefik (TLS termination) → HTTP → MCP Server (5050)

Nginx Configuration (/etc/nginx/sites-available/mcp-server):

server {
    listen 443 ssl http2;
    server_name mcp.example.com;

    ssl_certificate /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    # Modern TLS configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
    ssl_prefer_server_ciphers off;

    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        proxy_pass http://localhost:5050;
        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;
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name mcp.example.com;
    return 301 https://$server_name$request_uri;
}

Traefik Configuration (docker-compose.yml):

services:
  traefik:
    image: traefik:v2.10
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"

  mcp-server:
    image: openharbor/mcp-server:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mcp.rule=Host(`mcp.example.com`)"
      - "traefik.http.routers.mcp.entrypoints=websecure"
      - "traefik.http.routers.mcp.tls.certresolver=letsencrypt"
      - "traefik.http.services.mcp.loadbalancer.server.port=5050"

Certificate Management

Automatic Renewal with Let's Encrypt

Using Certbot:

# Install certbot
sudo apt-get install certbot

# Obtain certificate
sudo certbot certonly --standalone -d mcp.example.com

# Certificates location: /etc/letsencrypt/live/mcp.example.com/
# - fullchain.pem (certificate + chain)
# - privkey.pem (private key)

# Convert to PFX for Kestrel
sudo openssl pkcs12 -export \
  -out /etc/letsencrypt/live/mcp.example.com/server.pfx \
  -inkey /etc/letsencrypt/live/mcp.example.com/privkey.pem \
  -in /etc/letsencrypt/live/mcp.example.com/fullchain.pem \
  -passout pass:YourSecurePassword

# Auto-renewal (certbot creates cron job automatically)
sudo certbot renew --dry-run

Certificate Rotation

Step 1: Update Certificate File

Replace server.pfx with new certificate.

Step 2: Reload Without Downtime

# Docker
docker exec mcp-server kill -HUP 1

# Kubernetes
kubectl rollout restart deployment/mcp-server

# Systemd
sudo systemctl reload mcp-server

Step 3: Verify New Certificate

echo | openssl s_client -connect mcp.example.com:443 -servername mcp.example.com 2>/dev/null | openssl x509 -noout -dates

Security Headers (HSTS)

HTTP Strict Transport Security (HSTS) forces browsers to use HTTPS.

Method 1: Kestrel Middleware (Recommended)

Update Program.cs in CodexMcpServer:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

// Add HSTS middleware (production only)
if (!app.Environment.IsDevelopment())
{
    app.UseHsts();
}

// Redirect HTTP to HTTPS
app.UseHttpsRedirection();

// ... rest of configuration
app.Run();

Method 2: Custom Middleware

app.Use(async (context, next) =>
{
    if (!context.Request.IsHttps)
    {
        var httpsUrl = $"https://{context.Request.Host}{context.Request.Path}{context.Request.QueryString}";
        context.Response.Redirect(httpsUrl, permanent: true);
        return;
    }

    // Add HSTS header
    context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains");

    await next();
});

Method 3: Reverse Proxy (Nginx)

Already shown in Option 3 above.


Testing HTTPS

1. Health Check

curl https://mcp.example.com/health

Expected output:

{
  "status": "Healthy",
  "service": "MCP Server",
  "timestamp": "2025-10-19T12:00:00Z"
}

2. MCP Invoke Endpoint

curl -X POST https://mcp.example.com/mcp/invoke \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/list",
    "id": "test-1"
  }'

3. TLS Configuration Check

# Check TLS version and cipher suite
nmap --script ssl-enum-ciphers -p 443 mcp.example.com

# Check certificate validity
echo | openssl s_client -connect mcp.example.com:443 -servername mcp.example.com 2>/dev/null | openssl x509 -noout -text

4. HSTS Validation

curl -I https://mcp.example.com/health | grep -i strict-transport-security

Expected output:

Strict-Transport-Security: max-age=31536000; includeSubDomains

5. SSL Labs Test

For public-facing servers, use SSL Labs to get an A+ rating.


Troubleshooting

Issue: "Unable to configure HTTPS endpoint"

Symptom:

Unhandled exception. System.InvalidOperationException: Unable to configure HTTPS endpoint.

Solution:

  1. Verify certificate file exists at specified path
  2. Check file permissions (readable by server process)
  3. Verify password is correct
  4. Check certificate format (must be PFX for Kestrel)
# Verify PFX file
openssl pkcs12 -info -in server.pfx -noout

Issue: "The certificate chain was issued by an authority that is not trusted"

Symptom: Clients reject the certificate as untrusted.

Solution:

  1. Ensure CA bundle is included in PFX
  2. Add intermediate certificates to certificate store
  3. For development, use dotnet dev-certs https --trust

Issue: "Connection reset" or "SSL handshake failed"

Symptom: Client cannot establish TLS connection.

Solution:

  1. Check firewall allows port 443
  2. Verify TLS protocols match (client vs. server)
  3. Check cipher suite compatibility
# Test with specific TLS version
openssl s_client -connect mcp.example.com:443 -tls1_2

Issue: Certificate Expired

Symptom:

The remote certificate is invalid according to the validation procedure.

Solution:

  1. Check certificate expiry:
    echo | openssl s_client -connect mcp.example.com:443 2>/dev/null | openssl x509 -noout -enddate
    
  2. Renew certificate (see Certificate Management)
  3. Deploy new certificate

Issue: HSTS Not Working

Symptom: Browser still allows HTTP connections.

Solution:

  1. Verify HSTS header is sent:
    curl -I https://mcp.example.com/health | grep Strict-Transport-Security
    
  2. Clear browser HSTS cache (Chrome: chrome://net-internals/#hsts)
  3. Ensure middleware is configured correctly

Production Checklist

Before deploying to production, verify:

  • Valid TLS certificate from trusted CA
  • Certificate includes full chain (intermediate + root)
  • Private key is stored securely (Kubernetes secret, Azure Key Vault, etc.)
  • Certificate password is stored in environment variable (not in config)
  • Certificate expiry monitoring is configured
  • Automatic renewal is set up (Let's Encrypt, certbot)
  • HSTS header is enabled
  • HTTP → HTTPS redirect is configured
  • TLS 1.2+ is enforced (no SSLv3, TLS 1.0, TLS 1.1)
  • Strong cipher suites are configured
  • Certificate Common Name (CN) matches domain
  • Firewall allows port 443
  • Health check endpoint is accessible via HTTPS
  • Load balancer (if used) is configured for TLS passthrough or termination
  • Monitoring/alerting is set up for certificate expiry

References

ASP.NET Core Documentation:

Security Best Practices:

Certificate Authorities:


Document Version: 1.0.0 Last Updated: 2025-10-19 Maintained By: Svrnty Development Team Related: Deployment Guide, Security Best Practices