MCP 安全最佳实践指南:保护你的 AI 工具链
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 | 创建 PR | repo |
| Slack | 读取消息 | channels:history |
| Slack | 发送消息 | chat:write |
| AWS | 读取 S3 | s3:GetObject |
| AWS | 写入 S3 | s3: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 安全的三个核心原则:
- 隔离:每个 Server 独立运行,互不影响
- 最小化:权限、资源、网络,越小越好
- 可审计:每个操作都有记录,出了问题能追溯
在实际部署中,从最安全的配置开始,然后根据实际需求逐步放开权限——而不是反过来。