MCP 在生产环境中:解决 Transport、Auth 和扩展挑战
Engineering Team
从原型到生产:有什么变化
构建一个在笔记本电脑上运行的 MCP server 很简单。运行一个能处理数千个并发 AI agent 会话的分布式基础设施则是完全不同的工程挑战。生产 MCP 部署必须解决五个原型可以忽略的问题:transport 可扩展性、身份验证和授权、大规模会话管理、审计跟踪和多服务器编排。
本文是一份技术指南,面向将 MCP server 从开发环境迁移到生产环境的工程团队。我们假设您已构建过至少一个 MCP server 并了解协议基础。如果没有,请先阅读我们关于构建第一个 MCP server 的配套文章。
Transport 可扩展性:stdio vs SSE vs Streamable HTTP
MCP 定义了三种传输机制。为生产环境选择正确的传输是您的第一个架构决策。
stdio Transport
stdio transport 通过标准输入/输出流进行通信。Host 应用程序将 MCP server 作为子进程启动,并通过 stdin/stdout 交换 JSON-RPC 消息。
优势:
- 零网络配置
- 进程级隔离
- 无端口冲突
- 最低延迟(无网络堆栈)
局限性:
- Server 必须与 host 在同一台机器上运行
- 每个 client 会话一个 server 进程
- 无法进行负载均衡
- 无水平扩展
最适合: 本地开发工具、IDE 扩展、单用户桌面应用程序。
SSE (Server-Sent Events) Transport
SSE transport 使用 HTTP 进行 client 到 server 的消息传递,使用 Server-Sent Events 进行 server 到 client 的消息传递。Server 作为 HTTP 服务运行。
优势:
- 可通过网络访问(远程服务器)
- 与现有 HTTP 基础设施兼容
- 支持多个并发 client
- 可穿越防火墙和代理
局限性:
- 单向流(仅 server 到 client 通过 SSE)
- 需要会话亲和性(有状态连接)
- 某些负载均衡器在处理长连接 SSE 时有困难
- 协议中没有内置的重连语义
最适合: 中小型部署、内部工具、拥有现有 HTTP 基础设施的团队。
Streamable HTTP Transport
Streamable HTTP 是最新的 transport,专为生产部署设计。它对所有消息使用标准 HTTP POST,对长时间运行的操作使用可选的 SSE 流。
优势:
- 完全无状态的请求/响应模型
- 与任何 HTTP 负载均衡器兼容
- 通过
Mcp-Session-Id头内置会话管理 - 支持流式和非流式响应
- CDN 和代理兼容
局限性:
- 需要服务器端会话存储(Redis、数据库)
- 每消息开销略高于 stdio
- 较新的 transport——生态系统工具较少
最适合: 生产云部署、多租户平台、企业环境。
Transport 比较矩阵
| 特性 | stdio | SSE | Streamable HTTP |
|---|---|---|---|
| 网络访问 | 仅本地 | 远程 | 远程 |
| 负载均衡 | 不可能 | Session-sticky | 标准 HTTP LB |
| 水平扩展 | 否 | 有限 | 是 |
| 防火墙友好 | N/A | 是 | 是 |
| 重连 | N/A | 手动 | 内置 |
| 并发 client | 1 | 多个 | 多个 |
| 延迟 | 最低 | 低 | 低 |
| 无状态性 | 有状态 | 有状态 | 可无状态 |
| 生产就绪度 | 仅开发 | 中等 | 高 |
身份验证:SSO 集成、API Key 和 OAuth
生产中的 MCP server 必须同时验证 AI host 应用程序和代表其行事的最终用户的身份。
远程服务器的 OAuth 2.0
MCP 规范包含用于远程(基于 HTTP)服务器的内置 OAuth 2.0 流程:
- MCP client 发送不带凭据的
initialize请求。 - Server 响应 HTTP 401 和指向 OAuth 授权端点的
WWW-Authenticate头。 - Host 应用程序打开浏览器进行用户身份验证。
- 身份验证成功后,host 收到 access token。
- 后续 MCP 请求在
Authorization: Bearer头中包含 token。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
const app = express();
app.use("/mcp", async (req, res, next) => {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) {
res.status(401).json({
error: "unauthorized",
oauth_url: "https://auth.example.com/oauth/authorize",
});
return;
}
const user = await validateToken(token);
if (!user) {
res.status(403).json({ error: "invalid_token" });
return;
}
req.mcpUser = user;
next();
});
API Key 身份验证
对于服务间 MCP 通信(无人类用户参与),API key 更为简单:
const API_KEYS = new Map([
["sk-prod-abc123", { name: "analytics-service", scopes: ["read"] }],
["sk-prod-def456", { name: "admin-service", scopes: ["read", "write"] }],
]);
function authenticateApiKey(key: string) {
return API_KEYS.get(key) || null;
}
SSO 集成模式
对于企业部署,与现有 SSO(SAML、OIDC)集成:
User -> AI Host -> MCP Client -> MCP Server -> SSO Provider
|
v
验证 OIDC token
提取用户角色
应用工具级 RBAC
每个 MCP tool 可以在执行前检查已验证用户的角色:
server.tool(
"delete_record",
"按 ID 删除数据库记录",
{ table: z.string(), id: z.string() },
async ({ table, id }, { authContext }) => {
if (!authContext.roles.includes("admin")) {
return {
content: [{ type: "text", text: "禁止:需要管理员角色" }],
isError: true,
};
}
// 继续删除操作
}
);
扩展:有状态会话 vs 负载均衡器
MCP 会话本质上是有状态的:initialize 握手协商能力,服务器可能在会话内的工具调用之间维护上下文。这与水平扩展之间存在矛盾。
会话存储架构
将会话状态从服务器进程提取到外部存储:
interface McpSession {
id: string;
userId: string;
capabilities: ServerCapabilities;
createdAt: Date;
lastActivityAt: Date;
metadata: Record<string, unknown>;
}
class RedisSessionStore {
constructor(private redis: Redis) {}
async create(session: McpSession): Promise<void> {
await this.redis.set(
`mcp:session:${session.id}`,
JSON.stringify(session),
"EX", 3600
);
}
async get(sessionId: string): Promise<McpSession | null> {
const data = await this.redis.get(`mcp:session:${sessionId}`);
return data ? JSON.parse(data) : null;
}
}
使用 Streamable HTTP 进行水平扩展
通过外部化会话和 Streamable HTTP transport,您可以在标准负载均衡器后运行多个 MCP server 实例。无需 session-sticky 路由。任何服务器实例都可以通过从 Redis 加载会话来处理任何请求。
Auto-Scaling 配置
MCP server 的 Kubernetes HPA 示例:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: mcp-server
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mcp-server
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: mcp_active_sessions
target:
type: AverageValue
averageValue: "100"
审计跟踪和日志记录
生产中的每个 MCP tool 调用都必须记录,以用于安全、合规和调试。
结构化日志 Schema
interface McpAuditLog {
timestamp: string;
sessionId: string;
userId: string;
toolName: string;
toolInput: Record<string, unknown>;
toolOutput: string;
durationMs: number;
success: boolean;
errorMessage?: string;
ipAddress: string;
userAgent: string;
}
合规性考虑
| 要求 | 实现 |
|---|---|
| GDPR 数据访问 | 记录 AI 通过 MCP tools 访问了哪些用户数据 |
| SOC 2 审计跟踪 | 带时间戳的所有工具调用不可变日志 |
| PII 保护 | 在日志记录前编辑工具输入中的敏感字段 |
| 数据保留 | 设置符合合规要求的日志 TTL |
| 访问审查 | 记录身份验证事件和权限检查 |
多服务器部署的 Gateway 模式
企业环境通常需要数十个 MCP server——每个内部系统一个。MCP Gateway 集中管理:
Gateway 提供:
- 统一身份验证:所有后端服务器一个认证流程。
- 工具命名空间路由:工具以服务器名称为前缀(
jira.create_issue、slack.send_message)。 - 集中式速率限制:按用户、按工具和按服务器的限制。
- 请求路由:将工具调用路由到相应的后端服务器。
- 熔断器:如果后端服务器不健康,gateway 返回优雅的错误而非挂起。
常见问题
问:在生产中应该使用 stdio 还是 HTTP transport? 答:对于需要扩展到单台机器以外的任何部署,使用 Streamable HTTP。仅在 AI host 和 MCP server 在同一台机器上运行的本地桌面集成中使用 stdio。
问:一个 MCP server 可以处理多少并发会话? 答:使用 Streamable HTTP 和外部化会话,单个 Node.js 服务器实例通常处理 500-1,000 个并发会话。使用 Rust 或 Go 服务器,可实现 5,000-10,000 个。超出此范围时进行水平扩展。
问:如何处理 MCP server 停机?
答:实现健康检查(/health 端点),使用 Kubernetes 存活/就绪探针,并为 gateway 配置熔断器。MCP client 将收到连接错误,AI host 可将其呈现为“工具暂时不可用“。
问:MCP server 可以调用其他 MCP server 吗? 答:可以。MCP server 也可以是 MCP client,形成链式调用。这是 agent 间通信的基础。谨慎使用此模式——深层链增加延迟和故障概率。
问:如何管理 MCP server API 版本?
答:使用 initialize 响应中的服务器版本字段。对于破坏性更改(删除工具、更改 schema),升级主版本号并在迁移期间同时支持新旧版本。
问:MCP 消息的最大大小是多少? 答:协议本身没有定义最大值,但实际限制取决于 transport 和基础设施。对于 Streamable HTTP,将响应保持在 10 MB 以下。对于 stdio,系统管道缓冲区(通常 64 KB)可能需要对大型响应进行分块。