Files
qiming/qimingclaw/crates/qiming-mcp-stdio-proxy/AGENTS.md

8.1 KiB
Raw Blame History

qiming-mcp-stdio-proxy (Agent 开发文档)

概述

qiming-mcp-stdio-proxy 是一个基于 TypeScript 编写的 MCP (Model Context Protocol) 代理程序。它主要用于解决在复杂环境(例如 Electron 应用或统一的 Agent OS 平台)中集成多个 MCP Server 时遇到的生命周期管理、启动时序以及僵尸进程等关键问题。

核心架构与运行模式

代理程序提供三种主要的运行模式,均可通过 CLI 命令行启动:

  1. stdio (默认聚合模式)

    • 用途:将多个上游的 MCP Server (可以是标准的基于子进程的 stdio 服务器,也可以是基于 HTTP 的 bridge 服务器) 汇聚成单个对下游暴露的 stdio MCP 接口。
    • 用法qiming-mcp-stdio-proxy --config '{"mcpServers":{...}}'
    • 数据流Agent (stdin/stdout) ↔ 代理 ↔ [上游 1 (stdio), 上游 2 (HTTP Bridge), ...]
  2. convert (协议转换模式)

    • 用途:连接到单个远程的 MCP 服务(通过 SSE 或 Streamable HTTP并将其在本地作为标准的 stdio MCP 服务器暴露。
    • 用法qiming-mcp-stdio-proxy convert http://remote-mcp-server/sse
  3. proxy (持久化桥接模式, PersistentMcpBridge)

    • 用途:作为 Streamable HTTP Server 启动 PersistentMcpBridge。该模式会预先启动多个基于子进程的 MCP Server并通过本地 HTTP 将它们暴露出来供下游连接。
    • 用法qiming-mcp-stdio-proxy proxy --port 18099 --config '{"mcpServers":{...}}'

解决的核心痛点:就绪状态与生命周期

根据 fix-mcp-readiness-and-cleanup.md 中的记录,如果 Agent 引擎直接使用原生的 stdio 子进程来启动 MCP Server会遇到两个重大问题

  1. 首次对话的时序竞争Timing Race:标准 stdio MCP 进程的启动、初始化及连接需要时间。如果在启动 MCP 进程后立刻发送 Prompt 给 Agent 引擎,工具列表可能还未加载完毕,导致 Agent 认为没有合适的工具可用。
  2. 退出时的僵尸进程问题:在跨平台环境(如 Electron 应用的 Teardown 阶段)中,快速、可靠地清理所有子进程非常困难。

PersistentMcpBridge 解决方案

proxy 模式和 PersistentMcpBridge 类的引入,将 MCP Server 的生命周期与即时的 Agent 对话会话Session解耦从而彻底解决了上述问题

  1. 预热与阻塞启动Bridge 会预先 Spawn 所有配置的 MCP Server 子进程,并阻塞等待它们通过 listTools() 就绪检查。
  2. HTTP 协议转换:一旦就绪,它们将通过 Streamable HTTP 暴露 (例如 http://127.0.0.1:<PORT>/mcp/<serverId>)。
  3. 秒级连接:当下游的 Agent 引擎开启新的对话会话时,只需通过 HTTP URL 进行连接。由于 Bridge 早已运行并且缓存了工具列表,此连接过程是瞬间完成的,完全消除了时序竞争。
  4. 集中式的退出清理Bridge 采用优雅终止Graceful Termination逻辑来统一编排并管理所有子进程的关闭有效避免遗留孤儿进程。同时它还能追踪 HTTP 会话,自动清理失效连接。

关键文件导读

  • src/index.ts: CLI 入口点、参数解析及模式路由逻辑。
  • src/bridge.ts: PersistentMcpBridge 的具体实现代码。
  • src/customStdio.ts: 自定义的 StdioClientTransport 实现,具备更健壮的子进程管理能力及降级处理(这对 Windows 环境下的进程终止尤为重要)。
  • src/modes/: 包含 stdio, convertproxy 三种模式的具体实现逻辑。
  • src/logger.ts: 统一日志模块,同时输出到 stderr 和可选的日志文件。
  • src/resilient.ts: ResilientTransportWrapper,为 URL-based MCP Server 提供心跳监测、指数退避重连和请求队列。

日志系统 (logger.ts)

所有日志通过 logger.ts 统一输出stdout 保留给 MCP JSON-RPC 通信。

输出通道

通道 说明
stderr 始终输出Agent 引擎可捕获
日志文件 通过环境变量 MCP_PROXY_LOG_FILE 启用append 模式写入

环境变量

变量 说明 示例
MCP_PROXY_LOG_FILE 日志文件路径,设置后日志同时写入该文件 ~/.qimingbot/logs/mcp-proxy.log

日志格式

[2026-03-09 19:29:37.650] [info]  [qiming-mcp-proxy] Proxy server running on stdio

与 electron-log 格式一致:[时间戳] [级别] [标签] 消息

Electron 集成

Electron 宿主无法直接捕获 proxy 的 stderrproxy 由 ACP 引擎 spawn是 ACP 的子进程)。 集成方式Electron 在 proxy 环境变量中设置 MCP_PROXY_LOG_FILE,然后通过 fs.watchFile tail 读取日志文件,逐行转发到 electron-log,最终出现在 ~/.qimingbot/logs/main.log

弹性传输层 (ResilientTransportWrapper)

src/resilient.ts 为 URL-based MCP ServerSSE / Streamable HTTP提供连接弹性保障。

核心参数

参数 默认值 说明
pingIntervalMs 30000 心跳检测间隔 (ms),响应驱动调度
maxConsecutiveFailures 3 连续失败次数阈值,达到后触发重连
pingTimeoutMs 5000 单次心跳超时 (ms)
reconnectDelayMs 1000 退避基础延迟 (ms)
maxReconnectDelayMs 60000 退避延迟上限 (ms)
maxQueueSize 100 重连期间请求队列最大容量

心跳机制

  • 响应驱动调度: 使用 setTimeout 替代 setInterval,上一次心跳检查完成后才开始计时下一次,避免网络慢时请求堆积。
  • 并发保护: healthCheckInProgress 标志位防止多个心跳检查同时执行。
  • 轻量级检查: SSE/HTTP 连接依赖 transport 层的 onclose/onerror 回调监控连接健康状态,不再使用 client.listTools() 避免创建额外的 HTTP 连接。
  • 心跳间隔: 默认 30 秒(从 20 秒增加),减少不必要的检查频率。

重连触发机制

两种触发路径:

触发源 延迟 说明
Transport onclose/onerror 立即 SSE 断开、网络错误等,立即触发重连
心跳健康检查失败 连续 3 次 仅在使用 healthCheckFn 时生效stdio 传输)

SSE/HTTP 传输:由于不使用 healthCheckFn,心跳检查只是轻量级存活性验证(检查 activeTransport !== null),实际重连由 transport 层的 onclose/onerror 回调立即触发

重连策略

  • 指数退避: 1s → 2s → 4s → 8s → 16s → 32s → 60scapped与 Rust mcp-proxy 的 CappedExponentialBackoff 一致
  • 不限重试: 初次连接和 heartbeat 触发的重连均不限次数
  • 成功重置: 连接成功后 retryAttempt 重置为 0退避延迟回归 1s

重连流程

初次连接 / Heartbeat 失败 ×3
    ↓
关闭当前 transport清理 handler、停止 heartbeat
    ↓
指数退避等待1s → 2s → ... → 60s
    ↓
performConnect() 创建新 transport
    ├── 成功 → state='connected',重置 retryAttempt=0恢复 heartbeat
    └── 失败 → 继续退避重试(不限次数)

状态机

idle → connecting → connected ←→ reconnecting
                        ↓
                     closed永久停止

如何修改与扩展

  • 若要添加新的 CLI 选项,请更新 src/index.ts 以及位于 src/modes/ 下对应的模式文件。
  • 若需修改 Bridge 的 HTTP 路由策略或 Session 处理逻辑,请参考 src/bridge.ts 中的 handleHttpRequest 方法。

测试与质量保障

项目使用 Vitest 作为主要的测试框架。测试文件位于 tests/ 目录中,主要覆盖组件的功能完整性、集成逻辑及内部封装(如 ResilientTransportWrapper 心跳检测和连接重试)。

运行相关指令获取覆盖率等指标:

  • npm run test:以 Watch 模式启动测试监听。
  • npm run test:run:单次运行所有测试用例。
  • npm run test:coverage:运行所有测试用例并生成 v8 代码行级和分支覆盖率报告,输出结果将存在于根目录下的 coverage/ 文件夹中。