Claude Skills Guide

Model Context Protocol (MCP) servers have become essential infrastructure for AI-powered workflows, enabling Claude and similar assistants to interact with external tools, databases, and services. However, exposing these servers in production environments introduces security considerations that many developers overlook. This guide covers practical strategies for securing your MCP servers against common threats.

Understanding the Attack Surface

MCP servers typically expose HTTP endpoints that accept JSON-RPC requests. These endpoints can read files, execute commands, query databases, or interact with third-party APIs. When deployed without proper security measures, they become attractive targets for attackers.

The primary attack vectors include unauthenticated access, command injection through poorly validated inputs, excessive permissions, and exposure of sensitive credentials. Each of these requires a different defensive approach.

Authentication and Authorization

The foundational layer of MCP server security starts with authentication. Never expose an MCP server to the public internet without authentication mechanisms.

Implementing Token-Based Authentication

from functools import wraps
from fastapi import HTTPException, Header

async def verify_token(authorization: str = Header(None)):
    if not authorization:
        raise HTTPException(status_code=401, detail="Missing authorization header")
    
    scheme, _, token = authorization.partition(" ")
    if scheme.lower() != "bearer":
        raise HTTPException(status_code=401, detail="Invalid authentication scheme")
    
    if not validate_token(token):
        raise HTTPException(status_code=403, detail="Invalid or expired token")
    
    return token

def require_auth(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        await verify_token(kwargs.get("authorization"))
        return await func(*args, **kwargs)
    return wrapper

This pattern ensures only authenticated clients can invoke MCP tools. Store tokens in secure vaults rather than environment variables for production deployments.

Role-Based Access Control

Different Claude skills require different permission levels. The supermemory skill might need read access to knowledge bases, while pdf processing requires file system access. Implement granular permissions:

const toolPermissions = {
  'memory-search': ['read'],
  'memory-write': ['read', 'write'],
  'file-read': ['read'],
  'file-write': ['read', 'write'],
  'execute-command': ['admin']
};

function checkPermission(toolName, userRole) {
  const requiredPermissions = toolPermissions[toolName] || [];
  const userPermissions = rolePermissions[userRole] || [];
  return requiredPermissions.every(p => userPermissions.includes(p));
}

Input Validation and Sanitization

Command injection remains one of the most critical vulnerabilities in MCP servers. Any tool that accepts user input and passes it to system commands requires rigorous validation.

Safe Parameter Handling

import shlex
import re

def sanitize_command_args(args: dict) -> dict:
    """Prevent command injection through argument sanitization"""
    sanitized = {}
    for key, value in args.items():
        if isinstance(value, str):
            # Allow only alphanumeric, dash, underscore, and spaces
            if not re.match(r'^[a-zA-Z0-9_\-\s]+$', value):
                raise ValueError(f"Invalid characters in argument: {key}")
        sanitized[key] = value
    return sanitized

def execute_safe_command(tool_name: str, args: dict):
    sanitized = sanitize_command_args(args)
    # Use parameterized execution instead of shell string building
    cmd = [tool_name]
    for key, value in sanitized.items():
        cmd.extend([f"--{key}", str(value)])
    return subprocess.run(cmd, capture_output=True, text=True)

The tdd skill emphasizes writing tests before implementing security controls. Apply this methodology by creating test cases for malicious inputs before deploying sanitization logic.

Network Security

TLS Encryption

Always terminate TLS at your MCP server or behind a reverse proxy. Here’s a minimal Nginx configuration:

server {
    listen 443 ssl http2;
    server_name mcp.yourdomain.com;
    
    ssl_certificate /etc/letsencrypt/live/mcp.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mcp.yourdomain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Rate Limiting

Protect against denial-of-service attacks and brute-force attempts by implementing rate limiting:

from collections import defaultdict
from datetime import datetime, timedelta

class RateLimiter:
    def __init__(self, max_requests: int = 100, window_seconds: int = 60):
        self.max_requests = max_requests
        self.window = timedelta(seconds=window_seconds)
        self.requests = defaultdict(list)
    
    def is_allowed(self, client_id: str) -> bool:
        now = datetime.now()
        # Clean old entries
        self.requests[client_id] = [
            t for t in self.requests[client_id]
            if now - t < self.window
        ]
        
        if len(self.requests[client_id]) >= self.max_requests:
            return False
        
        self.requests[client_id].append(now)
        return True

Secrets Management

Never hardcode API keys, database credentials, or encryption keys in your MCP server codebase. The frontend-design skill and other Claude capabilities should integrate with proper secrets management:

import os
from functools import lru_cache

@lru_cache()
def get_secret(secret_name: str) -> str:
    """Retrieve secrets from environment or secrets manager"""
    # Check environment first
    value = os.environ.get(secret_name)
    if value:
        return value
    
    # Fall back to secrets manager
    from vault import get_vault_secret
    return get_vault_secret(secret_name)

Logging and Monitoring

Comprehensive logging enables incident detection and forensic analysis. Log all authentication attempts, tool invocations, and errors:

import logging
import json
from datetime import datetime

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("mcp-security")

def log_mcp_request(client_id: str, tool: str, args: dict, success: bool):
    event = {
        "timestamp": datetime.utcnow().isoformat(),
        "client_id": client_id,
        "tool": tool,
        "args_keys": list(args.keys()),  # Never log full args with secrets
        "success": success
    }
    logger.info(json.dumps(event))

Integrate with SIEM systems for production alerting. The supermemory skill can help maintain an audit trail of security events across your infrastructure.

Container Isolation

When deploying MCP servers in containers, apply defense-in-depth principles:

# Run as non-root user
FROM node:20-alpine
RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -s /bin/sh -D appuser

WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser

# Read-only filesystem where possible
ENTRYPOINT ["node", "server.js"]

Combine with Kubernetes network policies to restrict inter-pod communication:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mcp-server-policy
spec:
  podSelector:
    matchLabels:
      app: mcp-server
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: claude-frontend

Regular Security Audits

Schedule periodic reviews of your MCP server configurations. The pdf skill can generate automated security reports, while code analysis tools should scan for vulnerabilities in tool implementations.

Key audit points include:

Summary

Securing MCP servers in production requires a layered approach: authentication at the entry point, input validation at every tool handler, network encryption in transit, secrets management throughout, and comprehensive logging for visibility. These controls work together to reduce your attack surface while maintaining the functionality that makes MCP valuable.

Apply these practices incrementally, starting with authentication and TLS, then adding rate limiting and monitoring. Regular audits ensure your security posture improves over time rather than degrading as your deployment grows.

Built by theluckystrike — More at zovo.one