连接池管理
McpService 是 MCP 模块的核心服务,管理所有 MCP 服务器的连接生命周期。它维护一个客户端连接池,采用懒加载策略(首次使用时才建立连接),并提供健康检查和自动重连能力。
文件路径:packages/desktop/app/main/services/capabilities/tools/mcp-users/McpService.ts
类结构
class McpService extends EventEmitter {
private clients: Map<string, Client>;
private connectionStates: Map<string, ConnectionState>;
private toolsCache: Map<string, { tools: MCPTool[]; timestamp: number }>;
private readonly CACHE_TTL = 5 * 60 * 1000; // 5 分钟
constructor(private logger: LoggerService);
}
内部状态
| 属性 | 类型 | 说明 |
|---|---|---|
clients | Map<string, Client> | 服务器 ID → MCP Client 实例映射 |
connectionStates | Map<string, ConnectionState> | 服务器 ID → 连接状态映射 |
toolsCache | Map<string, { tools, timestamp }> | 服务器 ID → 工具列表缓存(含时间戳) |
连接状态
stateDiagram-v2
[*] --> disconnected
disconnected --> connecting: initClient()
connecting --> connected: 连接成功
connecting --> error: 连接失败
connected --> disconnected: disconnect()
error --> connecting: reconnect()
connected --> connecting: reconnect()
| 状态 | 说明 |
|---|---|
disconnected | 未连接或已断开 |
connecting | 正在建立连接 |
connected | 连接正常 |
error | 连接失败 |
连接生命周期
initClient — 初始化客户端
initClient(server: McpServerRecord): Promise<Client>
核心连接方法,执行流程:
- 检查复用 — 如果
clientsMap 中已有该 ID 的客户端,先做健康检查 - 健康检查 — 调用
client.listTools()验证连接是否有效 - 不健康处理 — 如果健康检查失败,先断开再重建
- 创建传输层 — 根据
server.type创建对应的 Transport - 创建客户端 — 创建 MCP SDK Client 实例,配置 capabilities
- 建立连接 — 调用
client.connect(transport) - 缓存客户端 — 存入
clientsMap - 发送事件 — 发出
server:connected事件
const client = new Client(
{ name: 'elftia', version: '1.0.0' },
{
capabilities: {
roots: { listChanged: true },
sampling: {}
}
}
);
await client.connect(transport);
disconnect — 断开连接
disconnect(serverId: string): Promise<void>
- 获取 Client 实例
- 调用
client.close()(带错误捕获) - 从
clientsMap 中移除 - 状态设为
disconnected
reconnect — 重新连接
reconnect(serverId: string): Promise<void>
先 disconnect(),然后如果服务器 isActive,再 initClient()。
cleanup — 全部清理
cleanup(): Promise<void>
断开所有连接,清空 clients、connectionStates、toolsCache 三个 Map。用于应用退出时。
传输层实现
StdioClientTransport
用于本地进程通信,通过标准输入/输出(stdin/stdout)与子进程通信。
new StdioClientTransport({
command: server.config.command,
args: server.config.args || [],
env: {
...process.env, // 继承系统环境
...server.config.env // 覆盖自定义变量
}
});
特点:
- 自动启动子进程
- 环境变量完全继承当前进程,再叠加自定义配置
- 进程退出时连接自动关闭
SSEClientTransport
基于 HTTP Server-Sent Events 的长连接传输,使用 Electron 的 net.fetch 替代 Node.js 原生 fetch。
new SSEClientTransport(new URL(server.config.url), {
fetch: (url, init) => {
return net.fetch(
typeof url === 'string' ? url : url.toString(),
init
);
},
requestInit: {
headers: server.config.headers || {}
}
});
使用 net.fetch 的原因:
- Electron 的
net模块遵循 Chromium 的网络栈 - 更好的 SSL/TLS 支持和证书管理
- 自动使用系统代理设置
StreamableHTTPClientTransport
基于 MCP 2025-03-26 规范的 Streamable HTTP 传输。配置方式与 SSE 类似:
new StreamableHTTPClientTransport(new URL(server.config.url), {
fetch: (url, init) => {
return net.fetch(
typeof url === 'string' ? url : url.toString(),
init
);
},
requestInit: {
headers: server.config.headers || {}
}
});
配置存储
MCP 服务器配置存储在 Electron Store 中:
const configStore = new Store<{
mcpServers: McpServerRecord[];
}>({
name: 'mcp-config',
defaults: { mcpServers: [] }
});
文件位置:{userData}/mcp-config.json
McpServerRecord 完整字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id | string | 自动生成 | 唯一标识,格式 mcp_{timestamp}_{random} |
name | string | - | 服务器名称(不可重复) |
type | McpServerTransport | - | stdio / sse / http |
scope | McpServerScope | 'user' | user(全局)/ local(项目级) |
config | McpServerConfig | - | 连接配置(command/args/env/url/headers) |
isActive | boolean | true | 是否启用 |
isTrusted | boolean | false | 是否受信任 |
disabledTools | string[] | [] | 禁用的工具名列表 |
disabledAutoApproveTools | string[] | [] | 禁止自动审批的工具名列表 |
installSource | string | 'manual' | 安装来源:builtin / manual / protocol / unknown |
projectPath | string? | - | 关联的项目路径(local scope 时使用) |
createdAt | number | Date.now() | 创建时间戳 |
updatedAt | number? | - | 最后更新时间戳 |
核心方法
list — 列出服务器
async list(): Promise<McpServerRecord[]>
直接从 configStore 读取服务器列表,不涉及连接。
add — 添加服务器
async add(input: McpServerInput): Promise<McpActionResult>
- 检查名称唯一性
- 创建
McpServerRecord,生成唯一 ID - 保存到 configStore
- 如果
isActive,尝试建立连接(连接失败不阻塞添加) - 发出
servers:changed事件
addJson — JSON 批量添加
async addJson(input: McpServerJsonInput): Promise<McpActionResult>
解析 JSON 字符串,遍历 { [name]: config } 对象,对每个条目调用 add()。返回汇总结果。
update — 更新服务器
async update(id: string, updates: Partial<McpServerRecord>): Promise<McpActionResult>
- 按 ID 查找服务器
- 合并更新(保持 ID 不变)
- 保存并设置
updatedAt - 如果
config或type变更,触发reconnect() - 发出
servers:changed事件
listServerTools — 获取服务器工具
async listServerTools(serverId: string): Promise<MCPTool[]>
- 检查缓存是否命中且未过期
- 命中则直接返回
- 未命中则
initClient()→client.listTools() - 转换为
MCPTool[]格式,过滤disabledTools - 写入缓存
工具 ID 生成规则:mcp__${cleanServerName}__${cleanToolName}(非字母数字字符替换为 _)
callTool — 调用工具
async callTool(serverId: string, toolName: string, args: any, callId: string): Promise<MCPCallToolResponse>
initClient()确保连接client.callTool({ name, arguments })- 记录调用耗时
- 发出
tool:called事件 - 返回
{ isError, content }或错误信息
test — 测试连接
async test(input: McpServerRemoveInput): Promise<McpTestResult>
尝试连接并列出工具,返回成功/失败和工具名列表。
discover — 发现能力
async discover(input: McpServerRemoveInput): Promise<McpDiscoverResult>
连接后分别调用 listTools()、listPrompts()、listResources()(后两者可选,允许部分失败),返回完整的能力清单。
事件
McpService 继承自 EventEmitter,发出以下事件:
| 事件 | 参数 | 触发时机 |
|---|---|---|
server:connected | serverId | 服务器连接成功 |
connection:state | serverId, state | 连接状态变更 |
servers:changed | 无 | 服务器列表变更(增/删/改) |
tool:called | { serverId, toolName, duration, success } | 工具调用完成 |
扩展点
- 新增传输类型 — 在
createTransport()的 switch 语句中添加新的 case 分支 - 自定义健康检查 — 修改
isClientHealthy()方法 - 缓存策略 — 修改
CACHE_TTL或实现更复杂的缓存失效策略 - 连接事件 — 监听 EventEmitter 事件实现自定义监控