25 min read
Intermediate
Security

MCP Security Fundamentals

Essential security practices for MCP servers covering input validation, sandboxing, access control, and threat modeling

MCPgee Team

MCP Expert

A working MCP server (any language)Basic understanding of web security conceptsFamiliarity with common attack vectorsUnderstanding of MCP architecture from introductory tutorials

MCP Security Fundamentals

Introduction

MCP servers bridge AI assistants with your infrastructure, databases, and APIs. This privileged position makes security critical. A poorly secured MCP server can expose sensitive data, allow unauthorized operations, or become an attack vector. This tutorial covers the essential security practices every MCP server developer should implement.

For authentication-specific patterns, see our dedicated MCP authentication tutorial. For general MCP concepts, start with What is MCP.

Threat Model for MCP Servers

Attack Surface

MCP servers face threats from multiple directions:

  1. Malicious prompts: AI clients may send crafted inputs designed to exploit tool implementations
  2. Prompt injection: Users may trick the AI into calling tools with harmful arguments
  3. Network attacks: Remote MCP servers are exposed to standard network threats
  4. Supply chain: Compromised dependencies can affect server security
  5. Data exfiltration: Tools may inadvertently expose sensitive data

Trust Boundaries

plaintext
┌──────────────────────────────────────────────┐
│                 Untrusted Zone               │
│  ┌──────────┐                                │
│  │ AI Client│ ── User Prompts ──┐            │
│  └──────────┘                   │            │
│                                 v            │
│  ┌──────────────────────────────────────┐    │
│  │           MCP Server                 │    │
│  │  ┌──────────┐  ┌──────────────────┐  │    │
│  │  │ Input    │->│ Tool Execution   │  │    │
│  │  │Validation│  │ (Sandboxed)      │  │    │
│  │  └──────────┘  └────────┬─────────┘  │    │
│  └─────────────────────────┼────────────┘    │
│                            │                 │
└────────────────────────────┼─────────────────┘
                             v
┌──────────────────────────────────────────────┐
│              Trusted Zone                    │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐  │
│  │ Database │  │ File     │  │ External  │  │
│  │          │  │ System   │  │ APIs      │  │
│  └──────────┘  └──────────┘  └───────────┘  │
└──────────────────────────────────────────────┘

Input Validation

Every tool argument must be validated before use. Never trust input from AI clients.

String Validation

python
from mcp.server.fastmcp import FastMCP
import re
import json

mcp = FastMCP("secure-server")

@mcp.tool()
def search_users(query: str) -> str:
    """Search for users by name.

    Args:
        query: Search query (letters, numbers, spaces only)
    """
    # Validate input format
    if not query or len(query) > 100:
        return json.dumps({"error": "Query must be 1-100 characters"})

    if not re.match(r'^[a-zA-Z0-9\s]+

Path Traversal Prevention

typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import path from 'path';
import fs from 'fs/promises';

const ALLOWED_ROOT = '/app/data';

server.tool(
  'read-file',
  'Read a file from the data directory',
  { filepath: z.string() },
  async ({ filepath }) => {
    // Resolve the absolute path
    const resolvedPath = path.resolve(ALLOWED_ROOT, filepath);

    // Verify it is within the allowed directory
    if (!resolvedPath.startsWith(ALLOWED_ROOT)) {
      return {
        content: [{ type: 'text', text: 'Error: Access denied - path traversal detected' }],
        isError: true,
      };
    }

    // Check file exists
    try {
      const content = await fs.readFile(resolvedPath, 'utf-8');
      return { content: [{ type: 'text', text: content }] };
    } catch {
      return {
        content: [{ type: 'text', text: 'Error: File not found' }],
        isError: true,
      };
    }
  }
);

SQL Injection Prevention

Always use parameterized queries:

python
# WRONG - vulnerable to SQL injection
@mcp.tool()
def bad_query(table: str, filter: str) -> str:
    """Never do this."""
    result = db.execute(f"SELECT * FROM {table} WHERE {filter}")
    return str(result)

# CORRECT - parameterized query with validated table name
ALLOWED_TABLES = {"users", "orders", "products"}

@mcp.tool()
def safe_query(table: str, column: str, value: str) -> str:
    """Query a table safely.

    Args:
        table: Table name (users, orders, or products)
        column: Column to filter on
        value: Value to match
    """
    if table not in ALLOWED_TABLES:
        return json.dumps({"error": f"Invalid table. Allowed: {ALLOWED_TABLES}"})

    if not re.match(r'^[a-zA-Z_]+

Command Injection Prevention

Never pass untrusted input to shell commands:

python
import subprocess

# WRONG - command injection risk
@mcp.tool()
def bad_exec(command: str) -> str:
    """Never expose raw command execution."""
    return subprocess.check_output(command, shell=True, text=True)

# CORRECT - use allowlists and avoid shell=True
ALLOWED_COMMANDS = {
    "git-status": ["git", "status", "--short"],
    "git-log": ["git", "log", "--oneline", "-10"],
    "disk-usage": ["df", "-h"],
}

@mcp.tool()
def run_command(command_name: str) -> str:
    """Run a pre-defined system command.

    Args:
        command_name: Command to run (git-status, git-log, disk-usage)
    """
    if command_name not in ALLOWED_COMMANDS:
        return json.dumps({"error": f"Unknown command: {command_name}"})

    result = subprocess.run(
        ALLOWED_COMMANDS[command_name],
        capture_output=True,
        text=True,
        timeout=10,
    )
    return result.stdout

Sandboxing

Process Isolation

Run MCP servers in isolated environments:

python
import resource
import os

def apply_sandbox():
    """Apply process-level restrictions."""
    # Limit memory to 256MB
    resource.setrlimit(resource.RLIMIT_AS, (256 * 1024 * 1024, 256 * 1024 * 1024))

    # Limit CPU time to 30 seconds
    resource.setrlimit(resource.RLIMIT_CPU, (30, 30))

    # Limit number of open files
    resource.setrlimit(resource.RLIMIT_NOFILE, (100, 100))

    # Limit number of child processes
    resource.setrlimit(resource.RLIMIT_NPROC, (10, 10))

Container Sandboxing

Use Docker with security constraints for the strongest isolation. See our Docker deployment tutorial for details.

bash
docker run -i --rm \
  --read-only \
  --tmpfs /tmp:noexec,nosuid,size=64m \
  --memory=256m \
  --cpus=0.5 \
  --network=none \
  --security-opt=no-new-privileges \
  my-mcp-server:latest

Access Control

Principle of Least Privilege

Only expose the minimum necessary tools and resources:

python
from mcp.server.fastmcp import FastMCP
import os

mcp = FastMCP("restricted-server")

# Only register tools appropriate for the deployment context
ROLE = os.environ.get("MCP_ROLE", "reader")

@mcp.tool()
def read_data(key: str) -> str:
    """Read data by key.

    Args:
        key: Data key to look up
    """
    return json.dumps({"key": key, "value": data_store.get(key)})

# Only register write tools for write-enabled deployments
if ROLE in ("writer", "admin"):
    @mcp.tool()
    def write_data(key: str, value: str) -> str:
        """Write data to storage.

        Args:
            key: Data key
            value: Value to store
        """
        data_store[key] = value
        return json.dumps({"status": "written", "key": key})

if ROLE == "admin":
    @mcp.tool()
    def delete_data(key: str) -> str:
        """Delete data from storage.

        Args:
            key: Data key to delete
        """
        data_store.pop(key, None)
        return json.dumps({"status": "deleted", "key": key})

Rate Limiting

Prevent abuse with request rate limits:

typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

const requestCounts = new Map<string, { count: number; resetAt: number }>();

function checkRateLimit(toolName: string, maxPerMinute: number = 60): boolean {
  const now = Date.now();
  const entry = requestCounts.get(toolName);

  if (!entry || now > entry.resetAt) {
    requestCounts.set(toolName, { count: 1, resetAt: now + 60000 });
    return true;
  }

  if (entry.count >= maxPerMinute) {
    return false;
  }

  entry.count++;
  return true;
}

server.tool(
  'query-database',
  'Execute a database query',
  { sql: z.string() },
  async ({ sql }) => {
    if (!checkRateLimit('query-database', 30)) {
      return {
        content: [{ type: 'text', text: 'Rate limit exceeded. Try again later.' }],
        isError: true,
      };
    }

    // Execute query...
    return { content: [{ type: 'text', text: 'Query results...' }] };
  }
);

Audit Logging

Log all tool invocations for security monitoring:

python
import logging
import json
from datetime import datetime
from functools import wraps

logging.basicConfig(
    filename='mcp_audit.log',
    format='%(message)s',
    level=logging.INFO,
)

def audit_tool(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        log_entry = {
            "timestamp": datetime.utcnow().isoformat(),
            "tool": func.__name__,
            "arguments": kwargs,
        }
        try:
            result = func(*args, **kwargs)
            log_entry["status"] = "success"
            log_entry["result_size"] = len(str(result))
            return result
        except Exception as e:
            log_entry["status"] = "error"
            log_entry["error"] = str(e)
            raise
        finally:
            logging.info(json.dumps(log_entry))
    return wrapper

@mcp.tool()
@audit_tool
def sensitive_operation(data: str) -> str:
    """Perform a sensitive operation.

    Args:
        data: Data to process
    """
    return process(data)

Data Protection

Sensitive Data Filtering

Prevent accidental exposure of sensitive data:

python
import re

SENSITIVE_PATTERNS = [
    (re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'), '[EMAIL]'),
    (re.compile(r'\b\d{3}-\d{2}-\d{4}\b'), '[SSN]'),
    (re.compile(r'\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14})\b'), '[CARD]'),
    (re.compile(r'(?:password|secret|token|api_key)\s*[=:]\s*\S+', re.I), '[REDACTED]'),
]

def sanitize_output(text: str) -> str:
    """Remove sensitive data from tool output."""
    for pattern, replacement in SENSITIVE_PATTERNS:
        text = pattern.sub(replacement, text)
    return text

@mcp.tool()
def read_log(logfile: str) -> str:
    """Read a log file with sensitive data redacted.

    Args:
        logfile: Log file name
    """
    content = Path(f"/app/logs/{logfile}").read_text()
    return sanitize_output(content)

Encryption at Rest and in Transit

  • Transport: Always use TLS for network MCP servers (see advanced MCP tutorial)
  • Storage: Encrypt sensitive data before storing
  • Secrets: Never log or return secrets in tool outputs

Dependency Security

Keep Dependencies Updated

bash
# TypeScript
npm audit
npm update

# Python
pip install --upgrade mcp
pip-audit

Minimal Dependencies

Only install what you need. Each dependency is a potential attack vector.

Lock Files

Always commit lock files (package-lock.json, uv.lock) to ensure reproducible builds.

Security Checklist

Use this checklist for every MCP server deployment:

  • [ ] All tool inputs are validated (type, length, format)
  • [ ] Path traversal prevention is implemented for file operations
  • [ ] SQL queries use parameterized statements
  • [ ] No shell command injection vectors
  • [ ] Rate limiting is in place
  • [ ] Audit logging captures all tool invocations
  • [ ] Sensitive data is filtered from outputs
  • [ ] Server runs as non-root user
  • [ ] TLS is configured for network transports
  • [ ] Dependencies are up to date and audited
  • [ ] Error messages do not leak internal details
  • [ ] Resource limits (memory, CPU, file descriptors) are set

Conclusion

Security is not optional for MCP servers. Since they bridge AI with your infrastructure, every tool is a potential attack surface. By implementing input validation, sandboxing, access control, and audit logging, you protect both your systems and your users.

For authentication-specific patterns including OAuth 2.0 and JWT, continue with our MCP authentication tutorial. For a deeper dive into security topics, read our MCP server security guide.

, query): return json.dumps({"error": "Query contains invalid characters"}) # Safe to use in database query with parameterized queries results = db.execute("SELECT * FROM users WHERE name LIKE ?", [f"%{query}%"]) return json.dumps({"results": results})

Path Traversal Prevention

SQL Injection Prevention

Always use parameterized queries:

Command Injection Prevention

Never pass untrusted input to shell commands:

Sandboxing

Process Isolation

Run MCP servers in isolated environments:

Container Sandboxing

Use Docker with security constraints for the strongest isolation. See our Docker deployment tutorial for details.

Access Control

Principle of Least Privilege

Only expose the minimum necessary tools and resources:

Rate Limiting

Prevent abuse with request rate limits:

Audit Logging

Log all tool invocations for security monitoring:

Data Protection

Sensitive Data Filtering

Prevent accidental exposure of sensitive data:

Encryption at Rest and in Transit

  • Transport: Always use TLS for network MCP servers (see advanced MCP tutorial)
  • Storage: Encrypt sensitive data before storing
  • Secrets: Never log or return secrets in tool outputs

Dependency Security

Keep Dependencies Updated

Minimal Dependencies

Only install what you need. Each dependency is a potential attack vector.

Lock Files

Always commit lock files (package-lock.json, uv.lock) to ensure reproducible builds.

Security Checklist

Use this checklist for every MCP server deployment:

  • [ ] All tool inputs are validated (type, length, format)
  • [ ] Path traversal prevention is implemented for file operations
  • [ ] SQL queries use parameterized statements
  • [ ] No shell command injection vectors
  • [ ] Rate limiting is in place
  • [ ] Audit logging captures all tool invocations
  • [ ] Sensitive data is filtered from outputs
  • [ ] Server runs as non-root user
  • [ ] TLS is configured for network transports
  • [ ] Dependencies are up to date and audited
  • [ ] Error messages do not leak internal details
  • [ ] Resource limits (memory, CPU, file descriptors) are set

Conclusion

Security is not optional for MCP servers. Since they bridge AI with your infrastructure, every tool is a potential attack surface. By implementing input validation, sandboxing, access control, and audit logging, you protect both your systems and your users.

For authentication-specific patterns including OAuth 2.0 and JWT, continue with our MCP authentication tutorial. For a deeper dive into security topics, read our MCP server security guide.

, column): return json.dumps({"error": "Invalid column name"}) result = db.execute( f"SELECT * FROM {table} WHERE {column} = ?", [value] ) return json.dumps({"results": result})

Command Injection Prevention

Never pass untrusted input to shell commands:

Sandboxing

Process Isolation

Run MCP servers in isolated environments:

Container Sandboxing

Use Docker with security constraints for the strongest isolation. See our Docker deployment tutorial for details.

Access Control

Principle of Least Privilege

Only expose the minimum necessary tools and resources:

Rate Limiting

Prevent abuse with request rate limits:

Audit Logging

Log all tool invocations for security monitoring:

Data Protection

Sensitive Data Filtering

Prevent accidental exposure of sensitive data:

Encryption at Rest and in Transit

  • Transport: Always use TLS for network MCP servers (see advanced MCP tutorial)
  • Storage: Encrypt sensitive data before storing
  • Secrets: Never log or return secrets in tool outputs

Dependency Security

Keep Dependencies Updated

Minimal Dependencies

Only install what you need. Each dependency is a potential attack vector.

Lock Files

Always commit lock files (package-lock.json, uv.lock) to ensure reproducible builds.

Security Checklist

Use this checklist for every MCP server deployment:

  • [ ] All tool inputs are validated (type, length, format)
  • [ ] Path traversal prevention is implemented for file operations
  • [ ] SQL queries use parameterized statements
  • [ ] No shell command injection vectors
  • [ ] Rate limiting is in place
  • [ ] Audit logging captures all tool invocations
  • [ ] Sensitive data is filtered from outputs
  • [ ] Server runs as non-root user
  • [ ] TLS is configured for network transports
  • [ ] Dependencies are up to date and audited
  • [ ] Error messages do not leak internal details
  • [ ] Resource limits (memory, CPU, file descriptors) are set

Conclusion

Security is not optional for MCP servers. Since they bridge AI with your infrastructure, every tool is a potential attack surface. By implementing input validation, sandboxing, access control, and audit logging, you protect both your systems and your users.

For authentication-specific patterns including OAuth 2.0 and JWT, continue with our MCP authentication tutorial. For a deeper dive into security topics, read our MCP server security guide.

, query): return json.dumps({"error": "Query contains invalid characters"}) # Safe to use in database query with parameterized queries results = db.execute("SELECT * FROM users WHERE name LIKE ?", [f"%{query}%"]) return json.dumps({"results": results})

Path Traversal Prevention

SQL Injection Prevention

Always use parameterized queries:

Command Injection Prevention

Never pass untrusted input to shell commands:

Sandboxing

Process Isolation

Run MCP servers in isolated environments:

Container Sandboxing

Use Docker with security constraints for the strongest isolation. See our Docker deployment tutorial for details.

Access Control

Principle of Least Privilege

Only expose the minimum necessary tools and resources:

Rate Limiting

Prevent abuse with request rate limits:

Audit Logging

Log all tool invocations for security monitoring:

Data Protection

Sensitive Data Filtering

Prevent accidental exposure of sensitive data:

Encryption at Rest and in Transit

  • Transport: Always use TLS for network MCP servers (see advanced MCP tutorial)
  • Storage: Encrypt sensitive data before storing
  • Secrets: Never log or return secrets in tool outputs

Dependency Security

Keep Dependencies Updated

Minimal Dependencies

Only install what you need. Each dependency is a potential attack vector.

Lock Files

Always commit lock files (package-lock.json, uv.lock) to ensure reproducible builds.

Security Checklist

Use this checklist for every MCP server deployment:

  • [ ] All tool inputs are validated (type, length, format)
  • [ ] Path traversal prevention is implemented for file operations
  • [ ] SQL queries use parameterized statements
  • [ ] No shell command injection vectors
  • [ ] Rate limiting is in place
  • [ ] Audit logging captures all tool invocations
  • [ ] Sensitive data is filtered from outputs
  • [ ] Server runs as non-root user
  • [ ] TLS is configured for network transports
  • [ ] Dependencies are up to date and audited
  • [ ] Error messages do not leak internal details
  • [ ] Resource limits (memory, CPU, file descriptors) are set

Conclusion

Security is not optional for MCP servers. Since they bridge AI with your infrastructure, every tool is a potential attack surface. By implementing input validation, sandboxing, access control, and audit logging, you protect both your systems and your users.

For authentication-specific patterns including OAuth 2.0 and JWT, continue with our MCP authentication tutorial. For a deeper dive into security topics, read our MCP server security guide.

Code Examples

Input Validation (Python)python
import re
import json
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("secure-server")

@mcp.tool()
def search(query: str) -> str:
    """Search with validated input.

    Args:
        query: Search query (alphanumeric, max 100 chars)
    """
    if not query or len(query) > 100:
        return json.dumps({"error": "Invalid query length"})
    if not re.match(r'^[a-zA-Z0-9\s]+$', query):
        return json.dumps({"error": "Invalid characters"})

    results = db.execute("SELECT * FROM items WHERE name LIKE ?", [f"%{query}%"])
    return json.dumps({"results": results})
Path Traversal Prevention (TypeScript)typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import path from 'path';
import fs from 'fs/promises';

const ROOT = '/app/data';

server.tool('read-file', 'Read a file safely', { filepath: z.string() },
  async ({ filepath }) => {
    const resolved = path.resolve(ROOT, filepath);
    if (!resolved.startsWith(ROOT)) {
      return { content: [{ type: 'text', text: 'Access denied' }], isError: true };
    }
    const content = await fs.readFile(resolved, 'utf-8');
    return { content: [{ type: 'text', text: content }] };
  }
);
Rate Limiting Middlewaretypescript
const limits = new Map<string, { count: number; resetAt: number }>();

function checkRate(key: string, max = 60): boolean {
  const now = Date.now();
  const entry = limits.get(key);
  if (!entry || now > entry.resetAt) {
    limits.set(key, { count: 1, resetAt: now + 60000 });
    return true;
  }
  if (entry.count >= max) return false;
  entry.count++;
  return true;
}

Key Takeaways

  • Every MCP tool input must be validated for type, length, and format before use
  • Use parameterized queries and allowlists to prevent injection attacks
  • Path traversal prevention is critical for any file system tools
  • Audit logging of all tool invocations enables security monitoring and incident response
  • Run MCP servers with minimal privileges in sandboxed environments

Troubleshooting

Rate limiter blocks legitimate requests during high usage

Implement per-client rate limiting instead of global limits. Use sliding window counters for smoother rate limiting. Consider separate limits for read and write operations.

Input validation rejects valid international characters

Update regex patterns to support Unicode characters where appropriate. Use \w with Unicode flags or explicit Unicode ranges instead of [a-zA-Z]. Be cautious about over-restricting internationalized inputs.

Audit logs are too large and consuming too much disk space

Implement log rotation with size limits. Use structured logging and ship logs to a centralized service (CloudWatch, ELK, etc.). Consider logging only security-relevant events instead of all tool calls.

Next Steps

  • Implement authentication with the MCP authentication tutorial
  • Deploy securely with Docker containers
  • Set up continuous security scanning in your CI/CD pipeline
  • Review the MCP server security guide for advanced patterns

Was this helpful?

Share tutorial:

Stay Updated with MCP Insights

Join 5,000+ developers and get weekly insights on MCP development, new server releases, and implementation strategies delivered to your inbox.

We respect your privacy. Unsubscribe at any time.

MCPgee Team

We write in-depth guides, tutorials, and reviews to help developers get the most out of the Model Context Protocol ecosystem.

Frequently Asked Questions

Related Tutorials

SecurityAdvanced

MCP Authentication Implementation

Implement authentication for MCP servers using OAuth 2.0, JWT tokens, API keys, and role-based access control

30 min read
DeploymentIntermediate

Containerize MCP Servers with Docker

Containerize MCP servers with Docker for consistent, portable, and secure deployments

25 min read
AdvancedAdvanced

MCP Server Performance Optimization

Optimize MCP server performance with caching, connection pooling, rate limiting, and monitoring strategies

25 min read

Recommended MCP Servers

Popular servers related to this tutorial that you can start using right away.

SecurityAI/ML
13,648

Casdoor MCP Server

An open-source Agent-first Identity and Access Management (IAM) /LLM MCP & agent gateway and auth server with web UI sup

APIsAI/ML
11,873

FastAPI-MCP

Exposes FastAPI endpoints as Model Context Protocol (MCP) tools while preserving existing authentication, schemas, and d

AI/MLDeveloper Tools
7,802

Lamda MCP Server

The most powerful Android RPA agent framework, next generation mobile automation.

AI/MLSecurity
6,565

Anthropic Cybersecurity Skills MCP Server

754 structured cybersecurity skills for AI agents · Mapped to 5 frameworks: MITRE ATT&CK, NIST CSF 2.0, MITRE ATLAS, D3F

Developer ToolsAI/ML
4,060

Ciso Assistant Community MCP Server

CISO Assistant is a one-stop-shop GRC platform for Risk Management, AppSec, Compliance & Audit, TPRM, BIA, Privacy, and

Developer ToolsSecurity
3,286

Metorial MCP Server

🎖️ 📇 ☁️ Connect AI agents to 600+ integrations with a single interface - OAuth, scaling, and monitoring included

Explore MCP Servers

Browse our directory of 33,000+ MCP servers. Find the perfect tools for your AI-powered workflows.