Files
qiming/qimingclaw/docs/persistent-mcp-bridge-architecture.md

12 KiB
Raw Permalink Blame History

持久化 MCP Server Bridge 架构设计

背景与问题

原有方案

qiming-mcp-stdio-proxy 作为 MCP 聚合代理,由 ACP 引擎在每个 session 启动时 spawn聚合所有用户配置的 MCP server 到一个 stdio endpoint。

ACP Session
└── qiming-mcp-stdio-proxy (stdio)
    ├── chrome-devtools-mcp (子进程)
    ├── filesystem-mcp (子进程)
    └── ...

核心问题proxy 进程的生命周期与 ACP session 绑定 — session 结束 → proxy 进程退出 → 所有 MCP server 子进程被 kill。

对于 chrome-devtools-mcp 这类有状态stateful的 MCP server

问题 说明
状态丢失 每次 session 启动都 spawn 新 chrome-devtools-mcp → 启动新 Chrome → 之前的浏览器状态页面、登录态、DOM 修改)全部丢失
--isolated 模式 避免 Chrome profile 冲突,但每次都是全新浏览器,无法跨 session 连续操作
--isolated 模式 Chrome profile 锁冲突:"browser already running",因为上一个 Chrome 可能还没完全退出

设计目标

将需要持久化状态的 MCP server 的生命周期从 ACP session 中解耦,由 Electron 主进程管理。


新方案:双通道架构

概览

引入 persistent 标志位,将 MCP server 分为两类,走不同的通道:

类型 标志 生命周期 通道
临时 serverephemeral persistent: false(默认) 跟随 ACP session qiming-mcp-stdio-proxy 聚合(不变)
持久化 serverpersistent persistent: true 跟随 Electron 主进程 PersistentMcpBridge HTTP ← qiming-mcp-stdio-proxy bridge 入口({ url }

架构图

Electron Main Process长生命周期
│
├── PersistentMcpBridge新组件单例
│   │
│   ├── 持久化子进程: chrome-devtools-mcpstdio主进程 spawn
│   │   └── MCP Client ← StdioClientTransport → 子进程 stdin/stdout
│   │
│   ├── HTTP Server127.0.0.1,自动分配端口)
│   │   └── 路由: POST/GET/DELETE /mcp/<serverId>
│   │       └── 每个 HTTP client session → Server + StreamableHTTPServerTransport
│   │           └── 代理 tools/list, tools/call → 共享的 MCP Client
│   │
│   └── 自动重启 + 健康检查 + session 清理
│
└── McpProxyManager.getAgentMcpConfig()
    └── 单一 mcp-proxyqiming-mcp-stdio-proxy --config '{ mcpServers: { stdio... | url... } }'
        ├── 临时 server → { command, args, env }stdio 子进程)
        └── 持久化 server → { url }bridgeStreamableHTTP → PersistentMcpBridge

Per ACP Session短生命周期
│
└── qiming-mcp-stdio-proxy单一进程混合 stdio + bridge
    ├── 临时 serverstdio 子进程
    └── 持久化 serverStreamableHTTPClientTransport → PersistentMcpBridge HTTP

数据流

Agent 引擎claude-code / qimingcode
  │
  └── mcp_servers.mcp-proxy (stdio)      ← 单一入口
      └── qiming-mcp-stdio-proxy --config '{ mcpServers: {...} }'
          ├── stdio 条目 → 子进程(临时 server
          └── bridge 条目 { url } → StreamableHTTPClientTransport
              └── HTTP POST /mcp/<serverId> → Electron PersistentMcpBridge
                  └── MCP Client (StdioClientTransport) → chrome-devtools-mcp持久化子进程

核心组件

1. McpServerEntry — 配置类型

interface McpServerEntry {
  command: string;
  args: string[];
  env?: Record<string, string>;
  persistent?: boolean;  // 标记为持久化 server
}

persistent: true 的 server 由 PersistentMcpBridge 管理,不再传给 qiming-mcp-stdio-proxy。

2. PersistentMcpBridge — 主进程 HTTP Bridge

文件: src/main/services/packages/persistentMcpBridge.ts

职责

  • 管理持久化 MCP server 子进程的完整生命周期
  • 提供 HTTP bridge 供 ACP session 的 bridge client 连接
  • 工具列表缓存、子进程自动重启、session 清理

关键设计

设计点 方案
子进程管理 MCP SDK StdioClientTransport 内部 spawn通过 Client.connect() 启动
HTTP 协议 MCP SDK StreamableHTTPServerTransport(非 SSEStreamableHTTP 是最新标准)
端口分配 server.listen(0, '127.0.0.1') 系统自动分配,仅监听 loopback
路径路由 单端口,/mcp/<serverId> 路由到对应 server
Session 模型 每个 HTTP client session 独立的 Server + StreamableHTTPServerTransport 对,共享底层 Client
并发安全 MCP SDK Client.callTool() 内部使用 JSON-RPC 请求 ID 做响应关联,天然支持并发
自动重启 子进程退出 → 5 秒冷却 → 自动重新 spawn + connect
安全防护 HTTP body 10MB 限制、stale session 60 秒定期清理、进程 force-kill 兜底

API

class PersistentMcpBridge {
  start(servers: Record<string, McpServerEntry>): Promise<void>;  // 启动 bridge
  stop(): Promise<void>;                                           // 停止 bridge
  getBridgeUrl(serverId: string): string | null;                   // 获取 HTTP URL
  isRunning(): boolean;                                             // 是否在运行
  isServerHealthy(serverId: string): boolean;                       // server 健康检查
}

export const persistentMcpBridge: PersistentMcpBridge;  // 单例

3. Bridge 入口(已并入 qiming-mcp-stdio-proxy

持久化 server 的桥接不再使用独立脚本,由 qiming-mcp-stdio-proxy--config{ url } 条目完成:

  • stdio 条目{ command, args, env } → proxy 内部 spawn 子进程(临时 server
  • bridge 条目{ url: "http://127.0.0.1:PORT/mcp/<serverId>" } → proxy 内部用 StreamableHTTPClientTransport 连接 PersistentMcpBridge

协议桥接:

Agent 引擎 (stdio)
  ↕
qiming-mcp-stdio-proxy单一进程
  ├── stdio 上游 → 子进程
  └── bridge 上游 → StreamableHTTPClientTransport → PersistentMcpBridge HTTP

4. McpProxyManager — 配置分发

文件: src/main/services/packages/mcp.ts

getAgentMcpConfig() 构建单一 mcp-proxy 配置,临时与持久化 server 均写入同一份 mcpServers

getAgentMcpConfig() {
  const proxyServers = {};
  // 临时 server → { command, args, env }
  for (const [name, entry] of ephemeral) {
    proxyServers[name] = resolveServersConfig({ [name]: entry })[name];
  }
  // 持久化 server → { url: persistentMcpBridge.getBridgeUrl(name) }
  if (persistentMcpBridge.isRunning()) {
    for (const [name, entry] of persistent) {
      const url = persistentMcpBridge.getBridgeUrl(name);
      if (url) proxyServers[name] = { url };
    }
  }
  return {
    'mcp-proxy': {
      command: process.execPath,
      args: [proxyScript, '--config', JSON.stringify({ mcpServers: proxyServers })],
      env: { ELECTRON_RUN_AS_NODE: '1' },
    },
  };
}

Agent 引擎收到的 MCP 配置(仅一个 key

{
  "mcp-proxy": {
    "command": "/path/to/electron",
    "args": ["/path/to/qiming-mcp-stdio-proxy", "--config", "{\"mcpServers\":{\"filesystem\":{\"command\":\"npx\",\"args\":[\"...\"]},\"chrome-devtools\":{\"url\":\"http://127.0.0.1:PORT/mcp/chrome-devtools\"}}}"],
    "env": { "ELECTRON_RUN_AS_NODE": "1" }
  }
}

生命周期管理

启动流程

app.whenReady() → runStartupTasks()
  └── mcpProxyManager.start()
      ├── 验证 qiming-mcp-stdio-proxy 已安装(缓存脚本路径)
      └── persistentMcpBridge.start(persistentServers)
          ├── 为每个 persistent server 创建 StdioClientTransport + Client
          ├── Client.connect() → spawn 子进程 → MCP initialize
          ├── Client.listTools() → 缓存 tool 列表
          └── 启动 HTTP server自动端口 + session 清理定时器

ACP Session 流程

agentService.init() → getAgentMcpConfig()
  └── mcp-proxy: ACP 引擎 spawn 单一 qiming-mcp-stdio-proxy
      ├── stdio 上游 → 临时 server 子进程
      └── bridge 上游 → HTTP 连接 PersistentMcpBridge持久化 server

Session 结束:
  └── qiming-mcp-stdio-proxy 进程退出
      ├── 临时 server 子进程全部退出 ✓
      └── bridge HTTP 连接关闭,持久化 MCP server 子进程继续运行 ✓ (关键!)

停止/退出流程

app 退出 / services:stopAll
  └── mcpProxyManager.cleanup() / mcpProxyManager.stop()
      └── persistentMcpBridge.stop()
          ├── 清理 session cleanup 定时器
          ├── 关闭所有 HTTP session transport
          ├── 关闭 HTTP server
          └── 逐个停止持久化 server:
              ├── Client.close()
              ├── transport.close() → 子进程终止
              └── 兜底: process.kill(pid, SIGTERM/SIGKILL)

子进程自动重启

子进程异常退出 → transport.onclose 触发
  └── scheduleRestart(5秒冷却)
      └── 清理旧 client/transport → spawnAndConnect()
          └── 新 StdioClientTransport + Client → listTools()

配置

默认配置

const DEFAULT_MCP_PROXY_CONFIG = {
  mcpServers: {
    'chrome-devtools': {
      command: 'chrome-devtools-mcp',
      args: [],
      persistent: true,     // 持久化: 由 PersistentMcpBridge 管理
    },
  },
};

用户自定义 Server

通过 UIMCPSettings 组件)添加的 server 默认为临时ephemeral走 qiming-mcp-stdio-proxy 聚合。

需要持久化的 server 需在配置中设置 persistent: true


依赖

依赖 用途 位置
@modelcontextprotocol/sdk MCP Client/Server/Transport 实现 package.json devDependencies
qiming-mcp-stdio-proxy 临时 + 持久化 聚合stdio + bridge 入口) 应用依赖 + ~/.qiming-agent/node_modules/

TypeScript 兼容性

MCP SDK 使用 package.json exports 字段的子路径导出,但项目 tsconfig.main.jsonmoduleResolution: "node" 不支持。

解决方案:src/shared/types/mcp-sdk.d.ts 提供 ambient module 声明,为以下子路径提供类型:

  • @modelcontextprotocol/sdk/client
  • @modelcontextprotocol/sdk/server
  • @modelcontextprotocol/sdk/client/stdio.js
  • @modelcontextprotocol/sdk/server/streamableHttp.js
  • @modelcontextprotocol/sdk/server/stdio.js
  • @modelcontextprotocol/sdk/client/streamableHttp.js
  • @modelcontextprotocol/sdk/types.js

文件清单

文件 状态 说明
src/main/services/packages/persistentMcpBridge.ts 新建 核心 bridge 服务
src/main/services/packages/mcp.ts 修改 统一 mcp-proxy 配置stdio + bridge 合并进一份 config
src/shared/types/mcp-sdk.d.ts 新建 MCP SDK 子路径类型声明
src/main/services/packages/mcp.test.ts 修改 新增持久化相关测试
src/main/main.ts 小改 cleanup 流程说明
src/main/ipc/processHandlers.ts 小改 stopAll/restartAll 说明
package.json 修改 SDK 依赖 + extraResources

验证

  1. npm run build → TypeScript 编译通过
  2. npm run test → 所有测试通过(含新增的 persistent bridge 测试用例)
  3. npm run electron:dev → 开发模式验证:
    • PersistentMcpBridge 启动 → chrome-devtools-mcp 成功 spawn → 28 tools 就绪
    • HTTP bridge 监听自动分配端口(如 57278
    • ACP session 收到单一 mcp-proxy 配置config 内含 stdio + bridge 条目)
    • qiming-mcp-stdio-proxy 内 bridge 连接 PersistentMcpBridge HTTP → tools 正常列出和调用
  4. 多 session 测试 → 同一 Chrome 实例跨 session 持续可用
  5. 应用退出 → 无残留进程

Last updated: 2026-02-27