MCP 安全最佳实践指南:保护你的 AI 工具链

📅 2026/5/9 ✍️ 小文 📖 约 1 分钟

MCP 协议让 AI 能操作数据库、文件系统、API,安全不容忽视。本文从权限控制、沙箱隔离、审计追踪等维度,全面讲解 MCP 安全配置。

MCP 赋予了 AI 「手和眼睛」——能写文件、查数据库、调用 API、操作生产环境。能力越大,责任越大。如果安全没做好,一句 prompt 就可能导致数据泄露或系统破坏。

本文从实际部署角度,梳理 MCP Server 的安全最佳实践。

安全威胁等级划分

在深入配置之前,先了解 MCP Server 的安全风险等级:

风险等级示例后果
🔴 高危数据库写入、文件删除、生产环境 API数据丢失、服务中断
🟡 中危文件读取、数据查询、消息发送信息泄露、误操作
🟢 低危计算统计、格式化输出、只读查询影响较小

核心原则:每个 MCP Server 只给最小必要权限,默认拒绝一切。

一、文件系统安全

1.1 限定操作目录(最重要)

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"],
      "env": {
        "NODE_OPTIONS": "--experimental-policy=fs-policy.json"
      }
    }
  }
}

给文件系统 Server 时,永远不要把根目录 / 或用户根目录 ~ 作为允许路径。应该限定到明确的项目目录。

1.2 使用只读模式

// 有些文件系统 MCP Server 支持只读模式
{
  "mcpServers": {
    "logs-viewer": {
      "command": "npx",
      "args": ["-y", "readonly-fs-mcp", "/var/log"],
      "env": {
        "READ_ONLY": "true"
      }
    }
  }
}

1.3 敏感文件清单

确保文件系统 MCP Server 不能访问以下路径:

# 禁止访问清单
~/.ssh/
~/.aws/
~/.config/gcloud/
/etc/passwd
/etc/shadow
/var/log/auth.log
*.env
*.pem
*.key

配置方法:使用 .mcpignore 文件(类似 .gitignore):

# .mcpignore
**/.env
**/.ssh/**
**/*.pem
**/*.key
**/credentials*
**/*secret*

二、数据库安全

2.1 使用只读账号

-- 为 MCP 创建专用只读账号
CREATE USER 'mcp_reader'@'localhost' IDENTIFIED BY 'strong_password_here';
GRANT SELECT ON my_database.* TO 'mcp_reader'@'localhost';
-- 永远不要给 DDL 或 DML 权限!

配置时使用这个只读账号:

{
  "mcpServers": {
    "database": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres"],
      "env": {
        "PGUSER": "mcp_reader",
        "PGPASSWORD": "strong_password_here",
        "PGDATABASE": "my_database"
      }
    }
  }
}

2.2 SQL 查询限制

即使给了只读权限,也要限制查询复杂度,避免恶意查询拖垮数据库:

-- 设置查询超时
SET statement_timeout = '5s';

-- 限制返回行数
SET max_rows = 1000;

2.3 敏感列脱敏

在数据库 MCP Server 中,对敏感字段进行自动脱敏:

SENSITIVE_COLUMNS = {'password', 'credit_card', 'ssn', 'token', 'secret'}

def sanitize_results(rows, columns):
    for row in rows:
        for i, col in enumerate(columns):
            if any(sensitive in col.lower() for sensitive in SENSITIVE_COLUMNS):
                row[i] = '***REDACTED***'
    return rows

三、API 安全

3.1 Token 管理

永远不要把 API Token 硬编码在配置文件中!

✅ 正确做法:使用环境变量传入

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_TOKEN": "${GITHUB_TOKEN}"  // 从系统环境变量读取
      }
    }
  }
}

❌ 错误做法:直接写在配置文件里

{
  "mcpServers": {
    "github": {
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_TOKEN": "ghp_xxxxxxxxxxxxxxxx"  // 危险!
      }
    }
  }
}

3.2 Token 作用域最小化

服务最小权限需要的 Scope
GitHub读取代码repo:read
GitHub创建 PRrepo
Slack读取消息channels:history
Slack发送消息chat:write
AWS读取 S3s3:GetObject
AWS写入 S3s3:PutObject

每个 MCP Server 只给它需要的、最小的权限范围

3.3 请求限流

# 为 MCP Server 增加请求频率限制
from datetime import datetime, timedelta
from collections import defaultdict
import asyncio

class RateLimiter:
    def __init__(self, max_calls: int = 10, period: int = 60):
        self.max_calls = max_calls
        self.period = period
        self.calls = defaultdict(list)
    
    async def check(self, client_id: str = "default"):
        now = datetime.now()
        self.calls[client_id] = [
            t for t in self.calls[client_id]
            if now - t < timedelta(seconds=self.period)
        ]
        if len(self.calls[client_id]) >= self.max_calls:
            raise Exception(f"请求频率超限,请等待 {self.period} 秒后再试")
        self.calls[client_id].append(now)

四、沙箱隔离

4.1 使用 Docker 运行(强烈推荐)

Docker 提供了天然的进程隔离、资源限制和网络控制。

# Dockerfile
FROM python:3.12-slim

WORKDIR /app
COPY server.py .
RUN pip install mcp httpx

# 以非 root 用户运行
RUN useradd -m mcpuser
USER mcpuser

CMD ["python", "server.py"]
{
  "mcpServers": {
    "file-search": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "--network", "none",           // 禁止网络(如果不需要)
        "--memory", "512m",             // 限制内存
        "--cpus", "1",                  // 限制 CPU
        "--read-only",                  // 只读文件系统
        "-v", "/data/mcp:/data:ro",     // 只读挂载
        "file-search-mcp"
      ]
    }
  }
}

4.2 使用 seccomp 限制系统调用

{
  "mcpServers": {
    "risky-command": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "--security-opt", "seccomp=mcp-seccomp.json",
        "mcp-server"
      ]
    }
  }
}

seccomp 配置文件示例(禁止危险系统调用):

{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "names": ["mount", "umount", "ptrace", "perf_event_open"],
      "action": "SCMP_ACT_ERRNO"
    }
  ]
}

4.3 AppArmor 配置

# /etc/apparmor.d/mcp-filesystem
profile mcp-filesystem {
  # 只允许读取 /home/user/projects 目录
  /home/user/projects/** r,
  # 禁止写入(只读模式)
  deny /home/user/projects/** w,
  # 禁止访问敏感文件
  deny /home/user/.ssh/** rw,
  deny /home/user/.aws/** rw,
}

五、审计与监控

5.1 日志记录

每个 MCP Server 都应该记录完整的调用日志:

import logging
import json
from datetime import datetime

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.FileHandler('/var/log/mcp/audit.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger('mcp-audit')

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    logger.info(json.dumps({
        "tool": name,
        "args": arguments,
        "timestamp": datetime.utcnow().isoformat(),
        "host": os.environ.get("HOSTNAME", "unknown")
    }))
    # 实际工具逻辑...

5.2 告警规则

设置异常行为告警:

# 告警条件示例
ALERT_RULES = [
    ("批量删除操作", lambda log: "delete" in log.get("tool", "") and log.get("args", {}).get("count", 0) > 10),
    ("敏感文件读取", lambda log: any(k in str(log.get("args", {})) for k in [".ssh", ".env", "secret"])),
    ("异常时间段操作", lambda log: is_outside_business_hours(log.get("timestamp"))),
    ("高频调用", lambda log: check_rate_limit(log)),
]

5.3 调用链追踪

# 集成 OpenTelemetry
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

tracer = trace.get_tracer(__name__)

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    with tracer.start_as_current_span(f"mcp.{name}") as span:
        span.set_attribute("tool.name", name)
        span.set_attribute("tool.args", str(arguments))
        result = await execute_tool(name, arguments)
        span.set_attribute("tool.success", True)
        return result

六、企业级部署清单

生产部署 Checklist

项目说明
最小权限原则每个 Server 只给必要的权限
专用账号MCP Server 使用独立操作系统账号
Docker 隔离每个 Server 运行在独立容器中
资源限制限制 CPU、内存、网络、磁盘 I/O
只读优先默认只读,需要写入时显式授权
无网络不需要网络的 Server 禁止出站连接
密钥管理所有密钥通过环境变量或 Vault 注入
审计日志记录所有工具调用
异常告警设置敏感操作的实时告警
定期审计每周审查 MCP Server 权限和日志

七、常见安全误区

❌ 误区 1:「MCP Server 只给自家人用,不用太严格」

事实:AI 行为的不可预测性是核心问题。你信任自己,但一个表述不清的 prompt 可能让 AI 执行了误操作。

❌ 误区 2:「本地运行的 Server 不需要安全措施」

事实:本地 MCP Server 可能被其他本地应用利用。如果 AI 在你的终端里运行,任何进程都能发送工具调用请求。

❌ 误区 3:「用了 MCP 官方的 Server 就安全了」

事实:官方 Server 在代码层面安全,但配置权限是用户控制的。官方文件系统 Server 给了正确的工具,但如果你给了它访问 .ssh 目录的权限,就是配置的问题。

❌ 误区 4:「SSE 模式的 MCP Server 加个 Token 就行」

事实:SSE 模式暴露在网络上,除了 Token 认证,还需要:

  • HTTPS 加密
  • IP 白名单
  • 请求频率限制
  • 完整的认证体系(OAuth 2.0 / JWT)

总结

MCP 安全的三个核心原则:

  1. 隔离:每个 Server 独立运行,互不影响
  2. 最小化:权限、资源、网络,越小越好
  3. 可审计:每个操作都有记录,出了问题能追溯

在实际部署中,从最安全的配置开始,然后根据实际需求逐步放开权限——而不是反过来。

📤 分享到