MCP 连接池与生命周期
理解共享 transport、会话视图、重启以及空闲回收的实际条件。
McpTransportPool 在同一工作区 ACP child 中共享 MCP transport,避免每个逻辑会话都启动一套相同服务。每个 workspace runtime 有独立池;未启动的未信任 secondary 不会拥有活动池。
连接与会话分开
共享键是 server name 加 config fingerprint。相同键的并发 acquire 共用 spawnInFlight promise;连接成功后,各 session 建立自己的 SessionMcpView 和工具/提示注册视图。
一个会话发现工具不会直接把工具注册到所有其他会话。includeTools/excludeTools 可在会话视图应用,而 transport 和 canonical snapshot 继续共享。键的具体字段见配置指纹。
默认共享 stdio 与 websocket。HTTP/SSE 默认不共享,因为 header 可能带会话 OAuth 状态;可通过 QWEN_SERVE_MCP_POOL_TRANSPORTS 明确选择。SDK MCP 不参与共享。
受 pool 管理的非共享连接生命周期随对应 session,最后 detach 时及时关闭;非共享不等于绕过 workspace budget。独立 CLI/SDK 的其他管理路径仍有自己的 manager 预算,见预算控制。
Entry 状态与反向索引
entry 维护 spawning、active、draining、closed、failed 状态、session refs 和 generation。sessionToEntries 反向索引让 releaseSession 只遍历该 session 引用的 entry,不扫描整个池。
初始化失败要回滚预算;已被关闭或失败的 entry 不能在迟到 connect/discovery 返回后重新变成 active。每个 await 后的生命周期检查用于防止向已经关闭的 session 注册工具。
两个空闲计时器
最后一个 ref 离开时,默认启动 30 秒 drain grace;再次 attach 会取消该 grace。另有从首次 idle 开始的 5 分钟 max-idle 计时器,普通 attach/detach 不会不断推迟它。
但“5 分钟硬期限”不能理解为到点强杀正在使用的连接:当前源码在计时器触发时重新检查 refs;若仍有活动 session,保留连接并清空 firstIdleAt,使下次 idle 有新的窗口。只有触发时没有 refs 才执行 max_idle 强制关闭。
restart 会清除两个旧计时器、重置 firstIdleAt,并将 draining 转回 active,避免重连过程中旧 timer 杀掉新 transport。官方总述中不带这一活动引用条件的“绝不重置”说法不宜照搬。
断线与重启
默认 stdio/websocket 重连策略为固定 5 秒、最多 3 次;HTTP/SSE 为从 1 秒到 16 秒的指数间隔、最多 5 次。具体配置可由 PoolEntryOptions 调整。
静默断线时先发 failed,再解除 subscriber view,使挂起 callTool 可收到 MCPCallInterruptedError,而不是永远等待。McpClient.lastTransportError 用于保留最近的传输失败原因,connect 开始会清空它。
restartByName 可选择 entryIndex;未指定时针对同名 entry。单项结果与多项 entries 数组形状不同,调用者应据 capability 和实际返回处理。generation 在重启时变化;entryIndex 是创建时分配的稳定标识,不是数组位置。
Drain 与子进程
drainAll 进入单向 draining 状态并拒绝新 acquire;继续工作需要新池。正常 ACP/IDE 连接结束和进程退出都应触发 drain,不只依赖 SIGTERM。
stdio 子进程的后代清理使用一次进程表快照加树遍历,必要时回退逐 PID 查询;最多 256 个后代、深度 8,属于有界清理而非无限进程追踪。snapshot.subprocessCount 只计 stdio,websocket/HTTP/SSE 连接远端服务,不等于本机子进程。