MCP-Client设计
3809 字约 13 分钟
domain/aiai/mcpai/mcp/client-design
2026-07-24
1. 核心结论
- MCP Client 是连接 MCP Server 并消费资源的客户端组件
- Client 负责发现 Server 能力、管理连接、处理请求和响应
- Client 设计需要处理连接生命周期、错误恢复和并发请求
- 主流 AI 应用(Claude Desktop、Cursor)内置 MCP Client 功能
- 自定义 Client 可以集成 MCP 到自己的 AI 应用中
2. 基础概念
MCP Client:实现 MCP 协议的客户端,连接 Server 并消费资源。
Connection:Client 与 Server 之间的通信连接。
Capability Discovery:发现 Server 提供的资源、工具和提示。
Request/Response:Client 发送请求,Server 返回响应。
Notification:Server 主动推送的消息。
Session:一次完整的连接会话。
3. 工作原理
Client 工作流程:
- 连接建立:
- 创建 Transport(stdio 或 HTTP)
- 初始化连接
- 交换能力信息
- 能力发现:
- 查询 Server 支持的资源
- 查询 Server 支持的工具
- 查询 Server 支持的提示
- 资源消费:
- 请求读取资源
- 接收资源内容
- 注入到 LLM 上下文
- 工具调用:
- 选择工具
- 构造参数
- 发送调用请求
- 接收执行结果
- 连接管理:
- 心跳检测
- 断线重连
- 优雅关闭
Client 实现示例(Python):
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async with stdio_client(
StdioServerParameters(command="python", args=["server.py"])
) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 发现资源
resources = await session.list_resources()
# 读取资源
content = await session.read_resource("file://data")
# 调用工具
result = await session.call_tool("search", {"query": "test"})Client 设计要点:
- 连接池管理多个 Server
- 异步处理提高并发能力
- 超时和重试机制
- 错误分类和处理
- 日志和监控
4. 实战场景
- AI 应用集成:在自己的应用中支持 MCP
- 多 Server 管理:同时连接多个 MCP Server
- 自定义 Host:构建自己的 MCP 宿主应用
- 工具聚合:整合多个 Server 的工具
- 自动化脚本:使用 MCP 自动化任务
5. 常见误区
- 不处理连接失败:Server 不可用时没有 fallback
- 忽视超时设置:请求可能永远等待
- 不验证 Server 响应:直接使用未验证的数据
- 并发处理不当:多个请求互相干扰
- 不管理连接生命周期:连接泄漏
6. 进阶方向
- 连接池优化
- 智能 Server 选择
- 请求批处理
- 缓存策略
- 客户端安全
- 多协议支持
7. 推荐资料
- MCP Client SDK - https://github.com/modelcontextprotocol/python-sdk
- MCP Specification - https://modelcontextprotocol.io/specification
- Claude Desktop MCP - https://docs.anthropic.com/en/docs/agents/mcp
- Building MCP Clients - https://modelcontextprotocol.io/quickstart/client
8. Host 与 Client 的边界
MCP 架构中有两个容易混淆的角色:Host 和 Client。
| 维度 | Host | Client |
|---|---|---|
| 定义 | 用户直接交互的应用程序(如 Claude Desktop、Cursor) | 协议层组件,负责与 Server 通信 |
| 职责 | 用户界面、模型调用、权限决策、上下文管理 | 协议握手、能力发现、请求发送、响应解析 |
| 可见性 | 用户可见 | 通常对用户不可见 |
| 数量 | 一个 Host 可包含多个 Client | 每个 Client 通常对应一个 Server 连接 |
为什么区分重要? Host 负责「要不要调用」的决策,Client 负责「怎么调用」的执行。混淆两者会导致权限控制混乱、错误处理不当。
┌─────────────────────────────┐
│ Host 应用 │
│ ┌─────────┐ ┌─────────┐ │
│ │ Client A│ │ Client B│ │
│ │ ↔ S1 │ │ ↔ S2 │ │
│ └─────────┘ └─────────┘ │
│ ┌─────────┐ ┌─────────┐ │
│ │ Client C│ │ Client D│ │
│ │ ↔ S3 │ │ ↔ S4 │ │
│ └─────────┘ └─────────┘ │
└─────────────────────────────┘9. 多 Server 管理
实际应用中,Client 通常同时连接多个 Server。管理多连接需要解决以下问题:
连接生命周期管理:
- 每个 Server 独立初始化、独立重连
- 某个 Server 故障不应影响其他 Server
- 支持动态添加和移除 Server
能力聚合:
- 合并所有 Server 的工具列表供模型选择
- 记录每个工具来自哪个 Server,用于调用路由
- 处理同名工具的冲突(见「关键设计问题」)
健康监控:
- 定期检测各 Server 的响应状态
- 对不可用的 Server 标记降级,不阻塞整体流程
- 支持优雅关闭所有连接
10. 能力缓存
Server 声明的工具、资源、提示列表不应每次调用都重新查询,需要缓存。
缓存策略:
- 首次连接后缓存完整能力列表
- 后续直接使用缓存,避免重复
list_tools/list_resources请求 - 缓存以 Server 连接为粒度,每个 Client 维护自己的缓存
缓存失效机制:
- Server 发送
notifications/tools/list_changed(或 resources/prompts 对应的通知) - 收到通知后,重新拉取对应列表并更新缓存
- 未收到通知时,假定缓存有效
注意事项:
- 缓存可能短暂过期(Server 更新了但未通知),需要容错
- 调用了一个已被移除的工具时,应优雅处理错误而非崩溃
- MCP能力发现
11. 调用路由
当模型决定调用某个工具时,Client 需要将其路由到正确的 Server。
路由表结构:
tool_name → client_id → server_connection
search → client_a → Server A (搜索引擎)
read_file → client_b → Server B (文件系统)
query_db → client_c → Server C (数据库)路由流程:
- 模型输出 tool_call:
{ name: "search", arguments: {...} } - Host 查路由表,找到对应的 Client
- Client 将请求发送到对应的 Server
- Server 返回结果,Client 回传给 Host
同名工具冲突:当多个 Server 提供同名工具时,路由策略可以是:
- 优先选择用户显式配置的 Server
- 按 Server 优先级排序
- 让模型通过工具描述中的 Server 前缀区分
- 提示用户消歧义
12. 超时、取消、重试与熔断
网络请求不可靠,Client 必须有健壮的错误处理机制。
超时控制:
| 类型 | 建议值 | 说明 |
|---|---|---|
| 连接超时 | 10-30s | 建立连接的等待时间 |
| 请求超时 | 30-120s | 等待 Server 响应的时间 |
| 工具调用超时 | 视工具而定 | 某些工具可能需要更长时间 |
取消机制:
- 用户取消操作时,Client 应发送取消请求(JSON-RPC notification)
- 支持通过
AbortController(JS)或asyncio.CancelledError(Python)取消进行中的请求 - 取消后清理相关资源,避免泄漏
重试策略:
- 仅对可重试错误重试(网络超时、临时不可用)
- 不对业务错误重试(参数错误、权限拒绝)
- 使用指数退避(exponential backoff):1s → 2s → 4s → 8s
- 设置最大重试次数(通常 3 次)
熔断器模式:
- 连续 N 次失败后,暂时停止向该 Server 发送请求
- 熔断期间直接返回降级响应,避免雪崩
- 定期(如 30s)发送探测请求,检测 Server 是否恢复
- 恢复后自动关闭熔断状态
正常 → 失败计数 → 达到阈值 → 熔断开启
↓
定期探测 → 成功 → 熔断关闭
↓
定期探测 → 失败 → 继续熔断13. 并发控制
多个请求可能同时发往同一个 Server,需要控制并发。
并发问题:
- Server 可能有并发限制
- 无序的请求可能导致状态不一致
- 大量并发请求可能压垮 Server
控制策略:
- 信号量:限制同时发往同一 Server 的最大请求数
- 请求队列:超出并发限制的请求排队等待
- 请求合并:对相同资源的读取请求可以合并
- 顺序保证:对同一资源的写操作需要串行化
实现要点:
- 每个 Client 维护独立的并发计数器
- 使用
asyncio.Semaphore(Python)或DispatchSemaphore(Swift) - 超时排队:队列中的请求也应有超时,避免无限等待
14. 权限确认与用户审批
高风险操作不应自动执行,需要用户确认。
为什么需要确认?
- Server 可能执行不可逆操作(删除文件、发送消息、转账)
- 模型的判断可能出错,用户应有最终决定权
- 恶意或有 bug 的 Server 可能尝试危险操作
分级策略:
| 级别 | 操作类型 | 处理方式 |
|---|---|---|
| 低 | 读取文件、搜索、查询 | 自动执行 |
| 中 | 创建文件、修改配置 | 通知用户(可配置为自动) |
| 高 | 删除文件、发送消息、API 调用 | 需要用户明确确认 |
| 极高 | 支付、不可逆操作 | 需要用户明确确认 + 二次验证 |
实现要点:
- Host 维护权限规则(可按工具、Server、操作类型配置)
- 用户可设置「始终允许」或「始终拒绝」
- 确认请求应展示:操作内容、目标资源、潜在风险
- 超时未确认视为拒绝
15. 结果规范化
Server 返回的结果需要规范化后才能有效注入到模型上下文。
规范化内容:
- 格式统一:不同 Server 的返回格式可能不同,需统一为 Host 内部格式
- 错误转译:将 Server 的错误码和错误信息转为用户/模型可理解的描述
- 内容截断:过长的结果需要截断,避免占满上下文窗口
- 类型转换:将 Server 特定的数据类型转为通用表示
规范化流程:
Server 原始响应 → 错误检查 → 内容提取 → 格式转换 → 长度裁剪 → 注入上下文注意事项:
- 保留原始响应用于调试和日志
- 裁剪时优先保留关键信息(摘要优于截断)
- 对结构化数据(JSON、表格)使用智能裁剪而非简单截断
- MCP上下文管理
16. 上下文裁剪
模型的上下文窗口有限,Server 返回的内容不能无限制注入。
裁剪策略:
- Token 预算:为每个工具的返回结果分配 token 上限
- 优先级裁剪:工具调用的结果优先于普通上下文
- 摘要替代:对超长内容先生成摘要,再注入
- 分页加载:只加载前 N 条结果,按需加载更多
裁剪方法:
| 方法 | 适用场景 | 说明 |
|---|---|---|
| 硬截断 | 简单文本 | 超过 token 限制直接截断 |
| 摘要 | 长文档 | 用 LLM 生成摘要后注入 |
| 结构化提取 | JSON/表格 | 只保留关键字段 |
| 相关性排序 | 多条结果 | 按相关性排序,保留 top-K |
注意事项:
- 裁剪后应告知模型「结果已截断」,避免模型误以为信息完整
- 保留继续获取的能力(如翻页、展开详情)
- MCP上下文管理
17. Server 崩溃后的恢复
Server 可能因为异常、资源耗尽或 bug 而崩溃,Client 需要能恢复。
检测崩溃:
- 请求超时且无响应
- Transport 连接断开(stdio 进程退出、HTTP 连接关闭)
- 心跳检测失败
恢复流程:
检测到崩溃 → 标记 Server 不可用 → 通知 Host
↓
等待退避时间 → 尝试重连 → 重新初始化
↓
重连成功 → 重新发现能力 → 恢复正常
↓
重连失败 → 继续退避重试 → 达到上限后放弃关键原则:
- 崩溃恢复不应阻塞其他 Server 的正常调用
- 重连后需重新进行能力协商(Session 不复用)
- 正在进行的请求应返回明确错误,而非无限等待
- 记录崩溃日志,便于排查 Server 端问题
18. 不可信 Server 隔离
不是所有 Server 都是可信的。恶意或有漏洞的 Server 可能:
- 返回注入提示词的内容
- 尝试读取 Host 敏感数据
- 执行超出声明范围的操作
隔离措施:
- 沙箱运行:Server 进程运行在受限环境中(容器、沙箱)
- 最小权限:Server 只能访问其声明需要的资源
- 输出过滤:检查 Server 返回内容是否包含可疑的 prompt injection
- 网络隔离:限制 Server 的网络访问范围
- 资源限制:限制 Server 的 CPU、内存、磁盘使用
信任等级:
| 等级 | 来源 | 限制 |
|---|---|---|
| 可信 | 官方/本地开发 | 无额外限制 |
| 受限 | 已知第三方 | 输出过滤 + 权限限制 |
| 不可信 | 未知来源 | 沙箱 + 网络隔离 + 严格过滤 |
19. Client 调用流程图
20. 关键设计问题
Client 是否信任 Server 的描述?
不完全信任。 Server 提供的工具描述(name、description、inputSchema)是 Client 向模型展示的依据,但 Server 可能:
- 提供误导性描述,诱导模型调用
- 在描述中嵌入 prompt injection
- 声明的能力与实际行为不符
应对:Host 可对工具描述做安全过滤;对高风险 Server 的描述添加 Host 层面的标注;关键操作需用户确认。
工具描述缓存失效如何处理?
- Server 通过
notifications/tools/list_changed通知 Client - Client 收到通知后重新拉取工具列表
- 竞态问题:缓存过期和工具调用之间可能有时间窗口,需容错处理
- 如果调用了已不存在的工具,应返回清晰错误并刷新缓存
多个 Server 提供同名能力时如何处理?
几种策略:
- 命名空间前缀:自动给工具名加上 Server 标识(如
serverA:search) - 优先级配置:用户配置 Server 优先级,优先使用高优先级的
- 用户消歧义:提示用户选择使用哪个 Server 的工具
- 合并去重:如果功能完全相同,合并为一个工具调用
目前没有标准做法,Host 实现者需要根据场景选择。
调用前是否需要用户确认?
取决于:
- 操作的可逆性:不可逆操作必须确认
- 操作的影响范围:影响外部系统(发邮件、删文件)需确认
- 用户的信任设置:用户可配置白名单跳过确认
- Server 的信任等级:不可信 Server 的所有操作都需确认
高风险操作如何分级?
建议分级维度:
- 读取 vs 写入:写入比读取风险高
- 本地 vs 远程:远程操作比本地风险高
- 可逆 vs 不可逆:不可逆操作风险最高
- 单条 vs 批量:批量操作风险更高
Host 应提供可配置的权限策略,允许用户按 Server、工具、操作类型设置不同的确认级别。