1. 生产环境的挑战
将 MCP 服务器从本地开发部署到生产环境面临一系列独特的挑战:
进程模型差异:开发时通常使用 stdio 传输,每个 AI 助手会话启动一个独立的服务器进程。这种"每会话一进程"模型在生产中会导致资源爆炸,需要转向常驻进程或按需扩容模型。
多用户并发:开发环境通常是单用户,而生产环境需要同时服务成百上千的 AI 助手会话。这要求 MCP 服务器是无状态的或可共享状态的。
网络拓扑复杂度:生产环境中 MCP 服务器可能分布在多个可用区、多个 Kubernetes 集群中。客户端需要服务发现机制来定位健康的服务器实例。
安全边界强化:开发时可能为了方便开放了宽松权限,生产环境需要严格的网络隔离、身份认证和审计。
可观测性要求:需要追踪每个工具调用的延迟、成功率和资源消耗,以便及时发现问题和优化性能。
2. 部署架构选择
2.1 架构模式 A:单边车模式(Sidecar)
每个 AI 宿主实例旁运行一个 MCP 服务器进程,两者共享生命周期:
┌─────────────────────────────────────┐
│ AI Assistant Host │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ LLM Client │ │ MCP Client │ │
│ └─────────────┘ └──────────────┘ │
│ │ │ │
│ │ │ stdio │
│ │ ┌────┴────┐ │
│ │ │ MCP Server│ │
│ │ └────┬────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────┐ │
│ │ External Services │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
优点:隔离性好,故障影响范围小,适合有状态工具
缺点:资源利用率低,每个会话都占用独立内存
2.2 架构模式 B:共享服务池
多个 AI 宿主实例共享一个 MCP 服务器集群:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ AI Host 1│ │ AI Host 2│ │ AI Host 3│
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└─────────────┼─────────────┘
│ HTTP + SSE
┌────────▼────────┐
│ Load Balancer │
└────────┬────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ MCP Srv │ │ MCP Srv │ │ MCP Srv │
│ Pod 1 │ │ Pod 2 │ │ Pod 3 │
└─────────┘ └─────────┘ └─────────┘
优点:资源利用率高,易于扩展,集中管理
缺点:需要处理并发安全,网络延迟增加
2.3 架构模式 C:函数即服务(FaaS)
将 MCP 服务器部署为无函数,冷启动时初始化:
# serverless.yml 示例
service: mcp-tools
provider:
name: aws
runtime: nodejs20.x
functions:
filesystem:
handler: dist/handlers/filesystem.handler
timeout: 30
memorySize: 512
events:
- http:
path: /mcp/filesystem
method: post
优点:按需付费,自动扩缩容,无服务器管理负担
缺点:冷启动延迟,执行时间受限,状态管理困难
3. Kubernetes 部署
3.1 Deployment 配置
# mcp-server-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-filesystem-server
labels:
app: mcp-filesystem
spec:
replicas: 3
selector:
matchLabels:
app: mcp-filesystem
template:
metadata:
labels:
app: mcp-filesystem
spec:
containers:
- name: server
image: registry.example.com/mcp/filesystem:v1.2.3
ports:
- containerPort: 3000
name: http
env:
- name: NODE_ENV
value: "production"
- name: ALLOWED_PATHS
value: "/data/workspace"
- name: LOG_LEVEL
value: "info"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
securityContext:
runAsNonRoot: true
runAsUser: 1001
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
volumeMounts:
- name: data-volume
mountPath: /data
readOnly: true
- name: tmp-volume
mountPath: /tmp
volumes:
- name: data-volume
persistentVolumeClaim:
claimName: workspace-data
- name: tmp-volume
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: mcp-filesystem-service
spec:
selector:
app: mcp-filesystem
ports:
- port: 80
targetPort: 3000
type: ClusterIP
3.2 水平自动扩缩容
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: mcp-filesystem-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mcp-filesystem-server
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 4
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 2
periodSeconds: 120
4. 健康检查与优雅关闭
4.1 健康检查端点
// src/health.ts
import { Request, Response } from "express";
interface HealthStatus {
status: "healthy" | "degraded" | "unhealthy";
checks: Record<string, CheckResult>;
timestamp: string;
}
interface CheckResult {
status: "pass" | "fail" | "warn";
responseTime: number;
message?: string;
}
export async function healthCheck(req: Request, res: Response): Promise<void> {
const start = Date.now();
const checks: Record<string, CheckResult> = {};
// 检查文件系统访问
try {
const fsStart = Date.now();
await checkFilesystemAccess();
checks.filesystem = {
status: "pass",
responseTime: Date.now() - fsStart,
};
} catch (error) {
checks.filesystem = {
status: "fail",
responseTime: Date.now() - start,
message: error instanceof Error ? error.message : String(error),
};
}
// 检查内存使用
const memUsage = process.memoryUsage();
const memPercent = memUsage.heapUsed / memUsage.heapTotal;
checks.memory = {
status: memPercent > 0.9 ? "warn" : "pass",
responseTime: 0,
message: `Heap: ${(memUsage.heapUsed / 1024 / 1024).toFixed(1)}MB`,
};
const overallStatus = Object.values(checks).every((c) => c.status === "pass")
? "healthy"
: Object.values(checks).some((c) => c.status === "fail")
? "unhealthy"
: "degraded";
const status: HealthStatus = {
status: overallStatus,
checks,
timestamp: new Date().toISOString(),
};
res.status(overallStatus === "healthy" ? 200 : overallStatus === "degraded" ? 200 : 503).json(status);
}
export async function readinessCheck(req: Request, res: Response): Promise<void> {
// 就绪检查:服务器是否准备好接收流量
// 例如:是否已完成初始化、是否已连接到依赖服务
res.status(200).json({ ready: true });
}
4.2 优雅关闭
// src/shutdown.ts
import { Server } from "http";
class GracefulShutdown {
private isShuttingDown = false;
private activeConnections: Set<any> = new Set();
constructor(private httpServer: Server, private timeoutMs: number = 30000) {}
register(): void {
process.on("SIGTERM", () => this.shutdown("SIGTERM"));
process.on("SIGINT", () => this.shutdown("SIGINT"));
// 追踪活跃连接
this.httpServer.on("connection", (conn) => {
this.activeConnections.add(conn);
conn.on("close", () => {
this.activeConnections.delete(conn);
});
});
}
private async shutdown(signal: string): Promise<void> {
console.log(`收到 ${signal},开始优雅关闭...`);
this.isShuttingDown = true;
// 停止接收新连接
this.httpServer.close(() => {
console.log("HTTP 服务器已停止接收新连接");
});
// 设置超时强制退出
const forceExit = setTimeout(() => {
console.error("关闭超时,强制退出");
process.exit(1);
}, this.timeoutMs);
// 等待活跃连接处理完
const waitForConnections = async () => {
while (this.activeConnections.size > 0) {
console.log(`等待 ${this.activeConnections.size} 个连接完成...`);
await new Promise((r) => setTimeout(r, 1000));
}
};
await waitForConnections();
clearTimeout(forceExit);
console.log("所有连接已关闭,正常退出");
process.exit(0);
}
}
5. 监控与可观测性
5.1 Prometheus 指标
// src/metrics.ts
import prometheus from "prom-client";
// 注册表
const register = new prometheus.Registry();
// 工具调用计数器
const toolCallsTotal = new prometheus.Counter({
name: "mcp_tool_calls_total",
help: "Total number of tool calls",
labelNames: ["tool_name", "status"],
registers: [register],
});
// 工具调用延迟直方图
const toolCallDuration = new prometheus.Histogram({
name: "mcp_tool_call_duration_seconds",
help: "Tool call duration in seconds",
labelNames: ["tool_name"],
buckets: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
registers: [register],
});
// 活跃连接数
const activeConnections = new prometheus.Gauge({
name: "mcp_active_connections",
help: "Number of active MCP connections",
registers: [register],
});
// 资源使用
const resourceReadsTotal = new prometheus.Counter({
name: "mcp_resource_reads_total",
help: "Total number of resource reads",
labelNames: ["uri"],
registers: [register],
});
export function recordToolCall(toolName: string, durationSec: number, success: boolean): void {
const status = success ? "success" : "failure";
toolCallsTotal.inc({ tool_name: toolName, status });
toolCallDuration.observe({ tool_name: toolName }, durationSec);
}
export function getMetrics(): Promise<string> {
return register.metrics();
}
5.2 结构化日志
// src/logger.ts
import winston from "winston";
export const logger = winston.createLogger({
level: process.env.LOG_LEVEL ?? "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: "mcp-server" },
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: "logs/error.log", level: "error" }),
new winston.transports.File({ filename: "logs/combined.log" }),
],
});
// 工具调用日志
export function logToolCall(params: {
tool: string;
args: Record<string, unknown>;
result: "success" | "failure";
durationMs: number;
sessionId: string;
error?: Error;
}): void {
logger.info("tool_call", {
tool: params.tool,
args: sanitizeArgs(params.args),
result: params.result,
duration_ms: params.durationMs,
session_id: params.sessionId,
error: params.error?.message,
});
}
function sanitizeArgs(args: Record<string, unknown>): Record<string, unknown> {
const sensitive = new Set(["password", "token", "secret", "api_key", "key"]);
const result: Record<string, unknown> = {};
for (const [k, v] of Object.entries(args)) {
result[k] = sensitive.has(k.toLowerCase()) ? "[REDACTED]" : v;
}
return result;
}
5.3 告警规则
# alert-rules.yaml
groups:
- name: mcp-alerts
rules:
- alert: MCPHighErrorRate
expr: rate(mcp_tool_calls_total{status="failure"}[5m]) / rate(mcp_tool_calls_total[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "MCP 服务器错误率过高"
description: "{{ $labels.tool_name }} 的错误率超过 10%"
- alert: MCPHighLatency
expr: histogram_quantile(0.95, rate(mcp_tool_call_duration_seconds_bucket[5m])) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "MCP 工具调用延迟过高"
description: "{{ $labels.tool_name }} 的 P95 延迟超过 2 秒"
- alert: MCPPodCrashLooping
expr: rate(kube_pod_container_status_restarts_total{container="mcp-server"}[15m]) > 0
for: 5m
labels:
severity: critical
annotations:
summary: "MCP Pod 正在反复重启"
6. 配置管理
6.1 环境变量分层
// src/config.ts
interface ServerConfig {
port: number;
logLevel: string;
allowedPaths: string[];
maxFileSize: number;
rateLimit: {
callsPerMinute: number;
callsPerHour: number;
};
features: {
tools: boolean;
resources: boolean;
prompts: boolean;
subscriptions: boolean;
};
}
function loadConfig(): ServerConfig {
return {
port: parseInt(process.env.PORT ?? "3000"),
logLevel: process.env.LOG_LEVEL ?? "info",
allowedPaths: (process.env.ALLOWED_PATHS ?? "/tmp").split(","),
maxFileSize: parseInt(process.env.MAX_FILE_SIZE ?? "10485760"), // 10MB
rateLimit: {
callsPerMinute: parseInt(process.env.RATE_LIMIT_PER_MINUTE ?? "60"),
callsPerHour: parseInt(process.env.RATE_LIMIT_PER_HOUR ?? "500"),
},
features: {
tools: process.env.FEATURE_TOOLS !== "false",
resources: process.env.FEATURE_RESOURCES !== "false",
prompts: process.env.FEATURE_PROMPTS !== "false",
subscriptions: process.env.FEATURE_SUBSCRIPTIONS === "true",
},
};
}
export const config = loadConfig();
6.2 ConfigMap 和 Secret
apiVersion: v1
kind: ConfigMap
metadata:
name: mcp-server-config
data:
LOG_LEVEL: "info"
ALLOWED_PATHS: "/data/workspace,/data/shared"
MAX_FILE_SIZE: "10485760"
RATE_LIMIT_PER_MINUTE: "120"
FEATURE_SUBSCRIPTIONS: "true"
---
apiVersion: v1
kind: Secret
metadata:
name: mcp-server-secrets
type: Opaque
stringData:
DATABASE_URL: "postgresql://user:pass@db/mcp"
API_KEY: "sk-production-key"
7. 备份与恢复
// 定期备份工具调用日志和配置
import { CronJob } from "cron";
const backupJob = new CronJob(
"0 2 * * *", // 每天凌晨 2 点
async () => {
logger.info("开始备份 MCP 数据");
try {
// 备份审计日志
await backupAuditLogs();
// 备份配置文件
await backupConfiguration();
// 清理过期日志
await cleanupOldLogs(30); // 保留 30 天
} catch (error) {
logger.error("备份失败", { error });
}
},
null,
true,
"UTC"
);
8. 总结
MCP 服务器的生产部署需要从开发思维全面转向运维思维:
| 维度 | 开发环境 | 生产环境 |
|---|---|---|
| 进程 | 每会话一个 | 常驻服务池 |
| 传输 | stdio | HTTP + SSE |
| 认证 | 无 | OAuth / mTLS |
| 资源 | 无限制 | CPU/内存/文件限制 |
| 监控 | 控制台日志 | Prometheus + Grafana |
| 扩展 | 手动 | HPA 自动扩缩容 |
| 部署 | npm start | Kubernetes |
成功的生产部署需要关注三个核心指标:可用性(uptime)、延迟(latency)和错误率(error rate)。通过健康检查确保可用性,通过资源限制和自动扩缩容控制延迟,通过结构化日志和告警规则监控错误率。只有这三项指标都达标,MCP 服务器才能真正稳定地服务于 AI 应用。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。