MCP-Server设计
3932 字约 13 分钟
domain/aiai/mcp
2026-07-24
1. 核心结论
- MCP Server 是提供资源、工具和提示的服务端组件
- Server 设计需要明确定义提供的资源类型和工具接口
- 好的 Server 设计应该职责单一、接口清晰、错误处理完善
- Server 可以使用任何语言实现,官方提供 TypeScript 和 Python SDK
- Server 的安全性和性能直接影响整个 MCP 系统
2. 基础概念
MCP Server:实现 MCP 协议的服务端,提供资源、工具和提示。
Resource:可读取的数据源,有 URI 标识。
Tool:可执行的操作,有输入参数和输出结果。
Prompt:预定义的提示模板,可填充参数。
SDK:官方提供的开发工具包(TypeScript、Python)。
Transport:通信方式,stdio(本地)或 HTTP/SSE(远程)。
3. 工作原理
Server 设计流程:
- 选择 SDK:
- TypeScript:
@modelcontextprotocol/sdk - Python:
mcp包
- TypeScript:
- 定义资源:
- 确定资源 URI 格式
- 实现资源读取逻辑
- 设置资源元数据
- 定义工具:
- 定义工具名称和描述
- 定义输入参数 Schema
- 实现工具执行逻辑
- 定义提示(可选):
- 定义提示模板
- 定义可填充参数
- 设置提示描述
- 配置传输:
- stdio:本地进程通信
- HTTP/SSE:远程网络通信
- 错误处理:
- 参数验证
- 执行异常
- 权限检查
Server 示例结构(Python):
from mcp.server import Server
from mcp.types import Resource, Tool
server = Server("my-server")
@server.list_resources()
async def list_resources():
return [Resource(uri="file://data", name="Data File")]
@server.read_resource()
async def read_resource(uri):
return "Resource content"
@server.list_tools()
async def list_tools():
return [Tool(name="search", description="Search data")]
@server.call_tool()
async def call_tool(name, arguments):
return {"result": "Tool output"}设计原则:
- 单一职责:每个 Server 专注一个领域
- 清晰接口:资源和工具命名规范
- 完善文档:描述清楚功能和使用方式
- 安全优先:验证输入、限制权限
- 性能优化:缓存、批量操作
4. 实战场景
- 文件系统 Server:读取本地文件
- 数据库 Server:查询数据库
- API Server:封装外部 API
- Git Server:访问代码库
- 搜索 Server:提供搜索功能
- 自定义业务 Server:企业内部系统
5. 常见误区
- Server 职责过多:一个 Server 做太多事情
- 忽视错误处理:工具执行失败没有正确返回
- 不验证输入:直接使用用户输入
- 资源 URI 混乱:没有统一的 URI 规范
- 忽视性能:大量数据不加分页或缓存
6. 进阶方向
- 动态资源生成
- 工具组合与编排
- Server 间通信
- 流式资源输出
- Server 认证与授权
- Server 监控与日志
7. 推荐资料
- MCP Python SDK - https://github.com/modelcontextprotocol/python-sdk
- MCP TypeScript SDK - https://github.com/modelcontextprotocol/typescript-sdk
- MCP Server Examples - https://github.com/modelcontextprotocol/servers
- Building MCP Servers - https://modelcontextprotocol.io/quickstart/server
8. Server 与业务服务的边界
MCP Server 的定位是协议适配层,不是业务逻辑容器。
Server 的职责:
- 把外部系统的能力翻译成 MCP 协议可理解的资源、工具和提示
- 处理协议层面的认证、校验、序列化
- 管理连接生命周期和传输细节
Server 不应承担的职责:
- 复杂的业务流程编排(应留给调用方或专门的编排层)
- 跨系统的业务事务管理
- 不属于当前能力范围的数据转换或聚合
把业务逻辑堆进 Server 会导致:
- Server 变得难以测试和部署
- 能力边界模糊,Client 无法清晰理解可用操作
- 升级业务逻辑时需要重启 Server,影响可用性
9. Server 分层结构
一个设计良好的 Server 通常可以分为以下层次:
各层职责:
| 层次 | 职责 | 示例 |
|---|---|---|
| MCP 协议层 | 处理 JSON-RPC、传输、会话管理 | stdio / HTTP+SSE 传输、请求路由 |
| 能力适配层 | 把外部能力映射为 MCP 资源/工具/提示 | 把 REST API 封装为 Tool |
| 业务服务层 | 实际的业务逻辑调用 | 调用下游微服务、执行计算 |
| 横切关注点 | 认证、权限、审计、校验、幂等 | API Key 验证、请求日志 |
分层的好处:
- 协议层和业务层可以独立演进
- 能力适配层可以复用,同一个业务服务可以同时暴露为 MCP Server 和 REST API
- 横切关注点集中管理,避免散落
10. Server 拆分粒度
什么时候拆成多个 Server
- 能力属于完全不同的领域(文件系统 vs 数据库 vs 第三方 API)
- 不同能力需要不同的部署环境或权限
- 单个 Server 的工具数量过多(超过 20-30 个),Client 难以有效选择
- 不同能力的更新频率差异很大
什么时候保持一个 Server
- 能力围绕同一个数据源或同一个领域
- 能力之间频繁组合使用,拆开反而增加 Client 配置成本
- 团队规模小,维护多个 Server 的运维成本高于收益
拆分判断清单
- 工具数量是否已经超过 Client 的有效处理能力?
- 是否存在需要隔离的权限边界?
- 不同能力的发布节奏是否冲突?
- 是否有能力需要独立扩缩容?
如果以上问题有 2 个以上回答"是",考虑拆分。
11. 单体 Server vs 多个小型 Server
| 维度 | 单体 Server | 多个小型 Server |
|---|---|---|
| 部署复杂度 | 低,一个进程 | 高,需要管理多个进程 |
| 配置成本 | Client 只需配置一个 Server | Client 需要配置多个 Server |
| 故障隔离 | 一个功能出错可能影响全部 | 故障局限在单个 Server |
| 独立更新 | 更新一个功能需要重启整个 Server | 各 Server 独立更新 |
| 权限管理 | 统一权限模型 | 可以按 Server 精细授权 |
| 资源消耗 | 共享内存和连接 | 每个 Server 有自己的基础开销 |
| 适用阶段 | 原型期、能力少、单人使用 | 生产环境、能力多、团队协作 |
| 调试难度 | 所有日志在一个进程 | 需要跨进程追踪 |
实践建议:从单体开始,随着能力增长和团队扩大逐步拆分。过早拆分带来的运维成本往往大于收益。
12. 本地 Server vs 远程 Server
| 维度 | 本地 Server(stdio) | 远程 Server(HTTP+SSE) |
|---|---|---|
| 通信方式 | 标准输入/输出 | HTTP 请求 + SSE 流 |
| 部署位置 | 与 Client 同一台机器 | 独立服务器或云服务 |
| 启动方式 | Client 自动启动和管理进程 | 需要独立部署和运维 |
| 访问范围 | 仅限本机 | 可被多个 Client 共享 |
| 安全性 | 进程隔离,攻击面小 | 需要认证、TLS、网络防护 |
| 性能 | 低延迟,无网络开销 | 有网络延迟,但可水平扩展 |
| 适用场景 | 本地开发工具、文件操作 | 团队共享服务、云资源访问 |
| 凭证管理 | 环境变量或本地配置文件 | 服务端密钥管理,更安全 |
| 典型示例 | 文件系统 Server、Git Server | 数据库 Server、API 网关 Server |
选择建议:
- 如果 Server 访问的是本机资源(文件、本地进程),用 stdio
- 如果 Server 需要被多个 Client 共享或访问远程资源,用 HTTP+SSE
- 两者可以共存:同一个能力可以同时提供本地和远程两种部署方式
13. 网关型 Server 和代理型 Server
网关型 Server(Gateway)
网关型 Server 作为统一入口,聚合多个后端能力:
- 对外暴露统一的 MCP 接口
- 内部路由到不同的后端服务
- 负责认证、限流、路由选择
- Client 不需要知道后端有多少个服务
适用场景:
- 企业内部有多个系统,但希望给 Client 一个统一的能力入口
- 需要对所有调用做统一的审计和权限控制
代理型 Server(Proxy)
代理型 Server 透传请求到另一个 MCP Server:
- 可以在透传过程中添加认证、日志、转换
- 常用于远程访问场景:本地 Client → 代理 → 远程 MCP Server
- 可以做协议转换(如 stdio ↔ HTTP+SSE)
适用场景:
- 远程 Server 不支持直接暴露给 Client
- 需要在中间层添加认证或审计
- 跨网络环境访问(如内网 → 外网)
两者对比
| 维度 | 网关型 | 代理型 |
|---|---|---|
| 核心功能 | 聚合和路由 | 透传和增强 |
| 后端数量 | 多个 | 通常一个 |
| 接口转换 | 可能转换接口 | 通常保持协议一致 |
| 典型位置 | 架构中心 | 网络边界 |
14. 凭证管理
MCP Server 经常需要访问外部系统,凭证管理是关键的安全环节。
凭证类型
- API Key:最常见,用于调用外部 API
- OAuth Token:需要用户授权的场景
- 数据库凭据:连接数据库的用户名和密码
- TLS 证书:双向认证场景
管理原则
- 凭证不硬编码:永远不要把密钥写在代码里
- 环境变量注入:通过环境变量传递凭证,Server 启动时读取
- 最小权限:Server 使用的凭证只授予必要的权限
- 凭证轮转:支持凭证更新而不需要重启 Server
- 作用域隔离:不同外部系统的凭证分开管理
常见模式
# 本地 Server:通过环境变量
DATABASE_URL=postgresql://user:pass@localhost/db my-server
# 远程 Server:通过密钥管理服务
Server 从 AWS Secrets Manager / Vault / 云厂商密钥服务获取凭据
# 代理型 Server:代理层统一注入凭证
Client 不持有后端凭证,代理在透传时添加认证头安全注意事项
- 日志中不要打印完整凭证
- 错误信息中不要泄露内部系统细节
- 定期审计 Server 可以访问的凭证范围
- 参考 MCP认证与授权 了解更多安全实践
15. 幂等性设计
工具调用可能因为网络超时、Client 重试等原因被重复执行。关键操作需要设计为幂等的。
什么是幂等
同一个操作执行一次和执行多次,产生的结果和副作用相同。
天然幂等的操作
- 读取操作(
GET、SELECT) - 设置绝对值的操作(
SET x = 5) - 查询类工具
需要额外设计的操作
- 创建资源:使用 Client 提供的幂等键(idempotency key)
- 递增/递减操作:记录操作日志,去重后再执行
- 发送消息:用唯一 ID 标记,接收方去重
设计模式
| 模式 | 做法 | 适用场景 |
|---|---|---|
| 幂等键 | Client 提供唯一 ID,Server 检查是否已处理 | 创建、支付、提交 |
| 条件执行 | 只在满足条件时执行(如 IF NOT EXISTS) | 资源创建 |
| 状态检查 | 执行前检查目标状态是否已达到 | 配置变更 |
| 操作日志 | 记录每次操作,重复请求时返回已有结果 | 所有写操作 |
在 Tool 定义中体现
- 在工具描述中说明是否幂等
- 如果工具不是幂等的,提醒 Client 注意重试风险
- 参考 MCP错误处理 了解错误场景下的重试策略
16. 会话状态
无状态 Server
- 每次请求独立处理,不依赖之前的请求
- 优点:简单、可靠、易于扩展
- 缺点:无法维持上下文(如多步操作)
- 适合:查询类工具、独立操作
有状态 Server
- 维护会话信息,支持多步操作
- 优点:可以支持复杂交互流程
- 缺点:需要管理会话生命周期、内存占用、故障恢复复杂
- 适合:交互式编辑、多步向导、流式处理
状态管理建议
- 优先无状态:如果可以通过参数传递上下文,就不要用会话状态
- 状态最小化:只保存必要的状态信息
- 设置过期时间:避免状态无限累积
- 提供清理接口:允许 Client 主动结束会话
- 考虑 Resource 替代:把状态编码为 Resource URI,而不是存在内存中
# 有状态的替代方案:用 Resource URI 编码状态
# 不好:Server 内存中记住"当前正在编辑的文件"
# 更好:Client 每次传入文件 URI,Server 无状态处理参考 MCP能力设计指南 了解更多能力设计中的状态管理策略。
17. 缓存和限流
缓存策略
| 缓存层 | 缓存内容 | 过期策略 |
|---|---|---|
| Server 内存缓存 | 频繁读取的资源内容 | TTL 过期 + 主动失效 |
| Client 侧缓存 | 工具返回结果 | 由 Client 管理 |
| 外部系统缓存 | 下游 API 响应 | 遵循外部系统的缓存头 |
缓存注意事项:
- 资源内容变更时主动失效缓存,或设置合理的 TTL
- 不要缓存敏感数据(凭证、个人信息)
- 大结果集优先使用分页而非缓存全量
- 考虑缓存预热:Server 启动时预加载常用数据
限流策略
Server 需要保护自己不被过高的调用量压垮:
- 工具调用频率限制:限制单个 Client 的调用频率
- 并发限制:限制同时执行的工具调用数量
- 资源读取限制:限制单次读取的数据量
- 下游保护:如果后端有调用限制,Server 需要做队列或降级
限流实现方式:
- 令牌桶 / 滑动窗口算法
- 返回标准错误码,告知 Client 稍后重试
- 在工具描述中说明频率限制,让 Client 自行调节
参考 MCP部署与运维 了解生产环境中的性能监控和调优。
18. 资源清理
Server 需要在适当时机清理资源,避免泄漏。
需要清理的资源
- 文件句柄和连接池
- 临时文件
- 内存中的缓存和会话数据
- 子进程
- 外部系统的锁或租约
清理时机
| 时机 | 触发条件 | 清理内容 |
|---|---|---|
| 正常关闭 | 收到 SIGTERM / SIGINT | 所有资源 |
| Client 断开 | 传输层连接关闭 | 该 Client 相关的会话和临时资源 |
| 工具执行完成 | 单次调用结束 | 临时文件、子进程 |
| 超时 | 操作超时 | 未完成的操作相关资源 |
| 异常 | 未捕获的异常 | 确保 finally 块中清理 |
最佳实践
- 使用
try/finally或上下文管理器确保清理 - 注册优雅关闭钩子(graceful shutdown handler)
- 设置超时避免资源无限等待
- 记录清理日志,方便排查泄漏
- 定期扫描和清理孤儿资源(如超时的临时文件)
19. 设计要点总结
MCP Server 应作为协议适配层和能力边界,而不是把全部业务逻辑、权限逻辑和数据模型都堆进一个进程。
好的 Server 设计让每一层各司其职:协议层处理通信,适配层翻译能力,业务层专注逻辑,横切层统一管理安全和可观测性。