Skip to content

Latest commit

 

History

History
299 lines (232 loc) · 8.87 KB

File metadata and controls

299 lines (232 loc) · 8.87 KB

Cloud Mode MCP Loading Fix

问题日期: 2025-10-11 修复版本: 1.0.174 (待发布)

问题描述

用户在使用 --cloud-mode 启动时遇到致命错误:

❌ 启动云端模式失败: TypeError: Cannot read properties of undefined (reading 'OPEN')
    at Pz.isConnected (file:///opt/homebrew/lib/node_modules/deepv-code/bundle/dvcode.js:3049:7362)

错误发生在MCP服务器(特别是chrome-devtools)尝试连接时,导致云端模式完全无法启动。

根本原因

时序竞态条件

正常交互模式和云端模式在MCP初始化上存在关键差异:

正常交互模式流程

gemini.tsx:main()
  ↓
config.initialize()
  ↓
创建ToolRegistry
  ↓
setTimeout(300ms) → discoverMcpToolsAsync()  ← 延迟为UI优化
  ↓
渲染UI (AppWrapper)
  ↓
300ms后 → MCP开始连接

✅ 成功原因: 300ms延迟让UI完全渲染,避免MCP进程启动阻塞输入响应

云端模式流程(修复前)

gemini.tsx:main() → startCloudMode()
  ↓
remoteServer.startCloudMode()
  ↓
remoteSession.initialize()
  ↓
config.initialize()
  ↓
setTimeout(300ms) → discoverMcpToolsAsync()  ← 同样的延迟
  ↓
立即创建GeminiChat  ← ⚠️ 没有等待MCP
  ↓
建立WebSocket连接  ← ⚠️ 可能在MCP连接前
  ↓
MCP尝试使用未初始化的WebSocket → ❌ CRASH

❌ 失败原因: 云端模式没有UI渲染,300ms延迟成为竞态条件触发点

技术细节

  1. 300ms延迟的设计意图(来自 config.ts 注释):

    // 延迟300ms确保UI完全渲染和响应用户输入后再启动MCP服务器进程
    // MCP进程启动会占用事件循环,即使是异步的也会影响输入响应
  2. WebSocket未初始化问题

    • MCP客户端(特别是StdioClientTransport)在连接过程中可能检查WebSocket状态
    • this.wsnullundefined时,访问ws.readyState会抛出TypeError
    • 错误信息虽然显示"reading 'OPEN'",但实际是WebSocket实例本身未定义
  3. NULL Safety问题

    • cloudClient.tsisClosed()方法混合使用了null检查和直接访问
    • remoteSession.tssendMessage()没有optional chaining保护

修复方案

1. 核心修复:等待MCP Discovery完成

文件: packages/cli/src/remote/remoteSession.ts

添加 waitForMcpDiscovery() 方法,在session初始化时显式等待MCP工具发现完成:

async initialize(): Promise<void> {
  await this.config.initialize();

  // ⭐ 等待MCP discovery完成(云端模式关键修复)
  await this.waitForMcpDiscovery();

  // ... 继续初始化
}

private async waitForMcpDiscovery(): Promise<void> {
  const timeout = 15000; // 15秒超时
  const checkInterval = 100; // 每100ms检查一次

  // 获取配置的MCP服务器
  const mcpServers = this.config.getMcpServers() || {};
  const serverNames = Object.keys(mcpServers);

  if (serverNames.length === 0) {
    return; // 无MCP服务器,直接跳过
  }

  // 等待discovery完成
  while (Date.now() - startTime < timeout) {
    const discoveryState = getMCPDiscoveryState();

    if (discoveryState === MCPDiscoveryState.COMPLETED) {
      // 记录连接状态(成功/失败都继续)
      const connectedServers = serverNames.filter(name =>
        getMCPServerStatus(name) === MCPServerStatus.CONNECTED
      );

      console.log(`✅ MCP discovery完成: ${connectedServers.length}/${serverNames.length} 服务器已连接`);
      return;
    }

    await new Promise(resolve => setTimeout(resolve, checkInterval));
  }

  // 超时警告但不抛异常 - 允许继续运行
  console.warn(`⚠️ MCP discovery超时,继续启动(MCP工具可能不可用)`);
}

2. NULL Safety修复

文件: packages/cli/src/remote/cloudClient.ts

// 修复前
private isClosed(): boolean {
  return !this.ws ||
         this.ws.readyState === WebSocket.CLOSED ||  // ❌ 无null检查
         this.ws.readyState === WebSocket.CLOSING;
}

// 修复后
private isClosed(): boolean {
  return !this.ws ||
         this.ws?.readyState === WebSocket.CLOSED ||  // ✅ Optional chaining
         this.ws?.readyState === WebSocket.CLOSING;
}
// cleanup() 方法
if (this.ws) {
  this.ws.removeAllListeners();
  if (this.ws?.readyState === WebSocket.OPEN ||  // ✅ Optional chaining
      this.ws?.readyState === WebSocket.CONNECTING) {
    this.ws.close();
  }
  this.ws = null;
}

文件: packages/cli/src/remote/remoteSession.ts

// 修复前
if (this.ws.readyState === WebSocket.OPEN) {  // ❌ 无null检查
  this.ws.send(JSON.stringify(messageWithSession));
}

// 修复后
if (this.ws?.readyState === WebSocket.OPEN) {  // ✅ Optional chaining
  this.ws.send(JSON.stringify(messageWithSession));
} else {
  remoteLogger.warn('WebSocket未连接', {
    readyState: this.ws?.readyState ?? 'null'  // ✅ Null-safe logging
  });
}

3. MCP失败容错处理

关键设计原则: MCP连接失败不应中断主业务

  • waitForMcpDiscovery() 超时后继续运行,只记录警告
  • ✅ 记录每个MCP服务器的连接状态(成功/失败)
  • ✅ 允许部分MCP服务器失败,只要至少有一个成功即可
  • ✅ 所有MCP失败也能正常启动,只是工具集受限

修复验证

构建测试

npm run build
# ✅ 构建成功,无TypeScript错误

预期行为(修复后)

场景1: 所有MCP服务器成功连接

dvcode --cloud-mode
🔄 Authentication attempt 1/3...
✅ Authentication successful!
✅ [Cloud Mode] 已认证用户: xxx
🔄 [RemoteSession] 等待MCP discovery完成,已配置 1 个服务器: chrome-devtools
✅ [RemoteSession] MCP discovery完成: 1/1 服务器已连接
   已连接: chrome-devtools
✅ [RemoteSession] 会话初始化完成
🆔 CLI ID: cli_xxx
✅ Connected to cloud server

场景2: MCP服务器连接失败(但不中断)

dvcode --cloud-mode
🔄 Authentication attempt 1/3...
✅ Authentication successful!
🔄 [RemoteSession] 等待MCP discovery完成,已配置 1 个服务器: chrome-devtools
Error connecting to MCP server 'chrome-devtools': Connection failed
✅ [RemoteSession] MCP discovery完成: 0/1 服务器已连接
   ⚠️  连接失败(已忽略): chrome-devtools
✅ [RemoteSession] 会话初始化完成  ← ⚠️ 关键:继续启动!
✅ Connected to cloud server

场景3: MCP discovery超时

dvcode --cloud-mode
✅ Authentication successful!
🔄 [RemoteSession] 等待MCP discovery完成...
⚠️  [RemoteSession] MCP discovery超时(15000ms),继续启动会话(MCP工具可能不可用)
✅ [RemoteSession] 会话初始化完成
✅ Connected to cloud server

影响范围

修改的文件

  1. packages/cli/src/remote/remoteSession.ts

    • 添加 waitForMcpDiscovery() 方法
    • 修复 sendMessage() 的null safety
    • 导入MCP状态追踪函数
  2. packages/cli/src/remote/cloudClient.ts

    • 修复 isClosed() 的null safety
    • 修复 cleanup() 的null safety

不受影响的模块

  • ✅ 正常交互模式(gemini.tsx):仍使用300ms延迟,保持UI优化
  • ✅ 非交互模式(nonInteractiveCli.ts):可能受益于同样的修复
  • ✅ VSCode扩展:不使用云端模式,不受影响
  • ✅ Core包:MCP客户端逻辑未改变

后续优化建议

短期优化

  1. 添加集成测试: 测试云端模式下MCP连接的各种场景
  2. 性能监控: 记录MCP discovery实际耗时
  3. 超时配置化: 将15秒超时改为可配置参数

长期优化

  1. 统一初始化流程: 考虑将MCP discovery逻辑移到config.initialize()内部
  2. 环境变量检测: 检测是否在云端模式,跳过300ms延迟
  3. Connection Pool: 实现WebSocket连接池,避免重复初始化

相关Issue

  • 用户报告: 云端模式启动失败 Cannot read properties of undefined (reading 'OPEN')
  • 根因: MCP初始化与WebSocket连接的竞态条件
  • 解决: 显式等待MCP discovery完成,添加容错处理

测试建议

用户在升级后可以通过以下方式验证修复:

# 1. 测试云端模式启动
dvcode --cloud-mode

# 2. 观察日志输出
# 应该看到 "✅ [RemoteSession] MCP discovery完成" 而不是crash

# 3. 测试MCP工具可用性
# 在云端会话中使用MCP工具(如chrome-devtools)

# 4. 测试MCP失败容错
# 临时配置一个无效的MCP服务器,验证不会阻断启动

总结

本次修复解决了云端模式下的致命启动错误,关键改进包括:

  1. 显式等待机制: 确保MCP discovery在WebSocket连接前完成
  2. 容错设计: MCP失败不中断主业务流程
  3. NULL Safety: 所有WebSocket访问都添加optional chaining保护
  4. 详细日志: 记录MCP连接状态,便于问题诊断

修复后,云端模式将能够稳定启动,即使MCP服务器连接失败也能正常运行。