Security · Production Checklist

10-Step Production Security Checklist

A security researcher demonstrated full RCE on a default FastMCP setup. 66% of MCP servers have security code smells. Don't be one of them.

The 10-Point Security Checklist

Every item below addresses a real attack vector documented in the MCP security landscape.

1. Authentication Required

Risk: FastMCP's default is no auth. HTTP transport without auth = unauthenticated RCE endpoint on the public internet.

from fastmcp.server.auth import BearerAuthProvider
import os

auth = BearerAuthProvider(
    token=os.environ["MCP_API_TOKEN"],
    jwt_secret=os.environ.get("JWT_SECRET"),
)
mcp = FastMCP("SecureServer", auth=auth)

2. TLS Everywhere

Risk: FastMCP does not handle TLS itself. Use nginx or a load balancer for TLS termination. Never run http:// in production.

server {
    listen 443 ssl http2;
    ssl_certificate /etc/letsencrypt/live/mcp.yourcompany.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mcp.yourcompany.com/privkey.pem;
    location /mcp { proxy_pass http://127.0.0.1:8000; }
}

3. Path Validation

Risk: If your tools access the filesystem, validate paths against directory traversal (../).

import os

SAFE_DIR = "/data/documents"

@mcp.tool()
def read_file(path: str) -> str:
    resolved = os.path.realpath(os.path.join(SAFE_DIR, path))
    if not resolved.startswith(SAFE_DIR):
        raise ValueError("Access denied: path traversal detected")
    with open(resolved) as f:
        return f.read()

4. Rate Limiting

Risk: Without limits, a malicious or misbehaving agent can exhaust your server.

limit_req_zone $binary_remote_addr zone=mcp:10m rate=30r/m;

location /mcp {
    limit_req zone=mcp burst=10 nodelay;
    proxy_pass http://127.0.0.1:8000;
}

5. Input Sanitization

Risk: All tool parameters arrive as JSON from an LLM. Never pass raw input to shell commands or SQL queries.

from pydantic import BaseModel, Field, field_validator
import shlex, subprocess

class RestartServiceInput(BaseModel):
    service_name: str = Field(max_length=64)
    @field_validator("service_name")
    @classmethod
    def no_shell_chars(cls, v):
        if any(c in v for c in [";", "&", "|", "$", "`"]):
            raise ValueError("Invalid service name")
        return v

@mcp.tool()
def restart_service(input: RestartServiceInput) -> str:
    # Safe: shlex.quote + no shell=True
    subprocess.run(["systemctl", "restart", input.service_name], capture_output=True)
    return "ok"

6. CORS Restrictions

Never use allow_origins=["*"] in production. Include mcp-session-id in expose_headers or browser clients silently fail.

app = mcp.http_app(
    cors_allow_origins=["https://your-app.com"],
    cors_expose_headers=["mcp-session-id"],
)

7. Secrets Management

Risk: The Docker MCP security analysis found plaintext secrets visible in process lists across thousands of installations.

# docker-compose.yml — environment from .env file, never commit secrets
environment:
  - MCP_API_TOKEN=${MCP_API_TOKEN}
  - JWT_SECRET=${JWT_SECRET}

8. Non-Root Container

RUN useradd -m mcpuser
USER mcpuser

9. Tool-Level RBAC

@mcp.tool(auth=["admin"])
def delete_record(record_id: str) -> str:
    """Delete a record. Admin only."""
    ...

10. Audit Logging

mcp = FastMCP(
    "AuditedServer",
    auth=auth,
    instrumentation="opentelemetry",  # Native OTEL support
)

FAQ

Does adding auth slow down my MCP server?

Negligibly. Bearer token validation is a hash comparison (O(1)). JWT with jwt_secret adds ~1ms per request. The real latency is in token_store roundtrips (Redis <1ms). For 99% of deployments, auth overhead is invisible compared to LLM inference time.

What if I already have an API gateway (Kong, Traefik, AWS API Gateway)?

You can offload items 2 (TLS), 4 (Rate Limiting), and 6 (CORS) to the gateway. Keep items 1 (Auth), 3 (Path Validation), 5 (Input Sanitization), and 9 (RBAC) at the FastMCP layer — the gateway can't enforce tool-level authorization or path validation inside your tool code.

Does this checklist apply to other MCP SDKs (Python MCP, TypeScript MCP)?

Mostly yes. Items 2-8 and 10 are transport-layer concerns (nginx, Docker, secrets) that apply regardless of SDK. Items 1, 9 (auth/RBAC) use FastMCP-specific APIs but the patterns are portable. The security research cited in the footer covers the broader MCP ecosystem.

How do I validate this checklist actually works?

Run the checklist in reverse order on a staging instance: start with audit logging (10) to capture what happens, then test RBAC bypass (9), then attempt CORS misconfig (6), then path traversal (3). The Production Manual includes a full penetration-testing script that automates all 10 checks against your endpoint.

Full Security Audit — Save Hours of Audit Prep

The Production Manual includes all 10 checklist items with expanded patterns: enterprise RBAC with Cerbos/OPA integration, audit trail setup, incident response runbook, and an automated pen-test script that validates every item against your live endpoint.

From $39, lifetime updates. Free 14-day refund, no lock-in.

Get the Production Manual