一个 MCP 客户端同时接多个 Server 是常态,但协议只保证「工具名在单个 Server 内唯一」,跨 Server 的唯一性要你自己解决。所以多 Server 编排的核心不是连上,而是三件事:合并工具列表时做命名空间、维护「工具名 → 所属会话」的路由表、以及控制暴露给模型的工具总量。
连接层:一个 Server 一条会话
标准的客户端做法是每个 Server 建一条独立的 ClientSession,各自 initialize(),然后遍历所有会话调 list_tools(),把结果合并成一张表交给模型,同时保留「工具名 → (server, tool)」的映射用于回程路由。
import asyncio
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
SERVERS = {
"fs": StdioServerParameters(
command="npx",
args=["-y", "@modelcontextprotocol/server-filesystem", "/data"],
),
"db": StdioServerParameters(command="python", args=["db_server.py"]),
}
async def connect_all(servers):
stack = AsyncExitStack() # 任一 Server 启动失败也能干净收尾
sessions, tools = {}, []
for name, params in servers.items():
read, write = await stack.enter_async_context(stdio_client(params))
session = await stack.enter_async_context(ClientSession(read, write))
await session.initialize()
sessions[name] = session
for t in (await session.list_tools()).tools:
tools.append({
"exposed": f"{name}__{t.name}", # 命名空间前缀,防重名
"route": (name, t.name), # 回程路由
"schema": t.inputSchema,
"desc": f"[{name}] {t.description}",
})
return sessions, tools, stack
分隔符用双下划线或冒号都行——关键是选一个真实工具名里不会出现的字符,这样回程时按分隔符切开就能定位到具体会话。
search,客户端要么随机挑一个,要么直接拒绝重复名,结果是模型以为自己调了 A,实际请求永远到不了 A 。三种编排策略
| 策略 | 做法 | 工具总量 | 适用规模 |
|---|---|---|---|
| 全量平铺 | 所有 Server 的所有工具一次性给模型 | 不限 | ≤ 3 个 Server 、≤ 40 个工具 |
| 命名空间 + 全量 | 加前缀后全量暴露 | 不限 | 3–8 个 Server,需要消歧 |
| 分组按需 | 按任务阶段只挂载相关 Server 组 | 每组 ≤ 20 | 8 个以上 Server 或工具上百 |
判断标准很直接:工具描述占用的上下文,是不是已经挤掉了模型用来推理的空间。工具数量一多,选型准确率会明显下降,这时该做的不是优化描述文案,而是减少暴露。
配置层:Server 放哪一级
多 Server 的第一现场是配置文件。常见客户端的形态是在 mcpServers 下给每个 Server 一个唯一 key,stdio 与远程 HTTP 可以混写:
{
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.example.com/mcp/",
"headers": { "Authorization": "Bearer ${GITHUB_TOKEN}" }
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
},
"internal": {
"command": "python",
"args": ["internal_server.py"],
"env": { "INTERNAL_API_KEY": "${KEY}" }
}
}
}
作用域上分三档:用户级(个人通用工具)、项目级(随仓库提交,团队共享)、本地级(含密钥,不进版本库)。原则是「能项目级就别用户级,含密钥一律本地级」。
远程 Server 的会话与恢复
远程走 Streamable HTTP 时,MCP 规范(2025-11-25 版)对会话与断线恢复有明确要求,多 Server 场景下尤其要留意:
- 会话 ID 。服务端在初始化响应里通过
MCP-Session-Id下发,客户端此后每个请求都必须带上;服务端可随时终止会话,之后该 ID 的请求会返回 404,客户端必须重新初始化。 - 协议版本头。HTTP 场景下所有后续请求都要带
MCP-Protocol-Version,服务端据此决定响应行为。 - 多连接。允许客户端同时保持多条 SSE 流,但服务端不得把同一条消息广播到多条流上——这意味着你的路由表必须精确到「哪条流属于哪个会话」。
- 断线续传。服务端可为事件附加 ID,客户端重连时用
Last-Event-ID请求重放;服务端只能重放同一条流上的消息,不能跨流补发。 - 主动关闭。不再需要某会话时应发 HTTP DELETE 显式终止,避免服务端堆积僵尸会话。
多 Server 故障隔离的四条做法
成本控制:别把所有工具都塞进上下文
- 先统计每个 Server 的工具数与描述总长度,按占用排序。
- 把低频 Server 拆成独立配置组,按任务阶段挂载。
- 描述里写清楚「什么时候不该用这个工具」,比堆参数说明更有效。
- 定期清理从不被调用的工具——日志里零命中的工具,就是在白烧上下文。
相关话题本站已经分开写过:配置放在哪一层见 MCP 配置作用域与团队共享;协议错误与执行错误的区分见 MCP 错误处理的两种路径;本地文件类 Server 的接法见 Filesystem MCP Server 实战;选哪些 Server 值得接可参考 常用 MCP 服务器推荐清单。










评论 (0)