Files
qiming/qimingclaw/crates/agent-electron-client/docs/v2/03-SKILLS-MCP.md

58 KiB
Raw Blame History

version, last-updated, status
version last-updated status
2.0 2026-03-09 design

03 — Skills & MCP 工具管理

一、现状分析

Qiming 服务端已有能力

技能管理 APIworkspace/qiming/src/services/skill.ts

端点 能力
GET /api/skill/{id} 查询技能详情
POST /api/skill/update 修改技能
POST /api/skill/import 导入技能
GET /api/skill/export/{id} 导出技能
POST /api/skill/upload-file 上传文件到技能
GET /api/skill/template 查询技能模板
POST /api/published/skill/list 已发布技能列表

MCP 管理 APIworkspace/qiming/src/services/mcp.ts

端点 能力
POST /api/mcp/create 创建 MCP 服务
POST /api/mcp/update 更新 MCP 服务
POST /api/mcp/test MCP 试运行
POST /api/mcp/stop/{id} 停用 MCP
GET /api/mcp/{id} MCP 详情
GET /api/mcp/list/{spaceId} MCP 管理列表
GET /api/mcp/deployed/list/{spaceId} 已部署 MCP 列表
POST /api/mcp/server/config/refresh/{id} 重新生成配置
POST /api/mcp/server/config/export/{id} 导出配置

Agent 组件 API 中也有技能/MCP/插件/工作流组件更新:

  • POST /api/agent/component/mcp/update — 更新 Agent 的 MCP 组件配置
  • POST /api/agent/component/skill/update — 更新 Agent 的技能组件配置
  • POST /api/agent/component/plugin/update — 更新 Agent 的插件组件配置
  • POST /api/agent/component/workflow/update — 更新 Agent 的工作流组件配置

插件管理 APIworkspace/qiming/src/services/plugin.ts

端点 能力
POST /api/plugin/add 新增插件
POST /api/plugin/http/update 更新 HTTP 插件
POST /api/plugin/code/update 更新 Code 插件
POST /api/plugin/test 插件试运行
GET /api/plugin/{id} 插件详情
POST /api/plugin/delete/{id} 删除插件
POST /api/plugin/publish 发布插件
GET /api/published/plugin/{id} 已发布插件信息

工作流管理 APIworkspace/qiming/src/services/workflow.ts

端点 能力
GET /api/workflow/{id} 获取工作流详情
POST /api/workflow/update 更新工作流信息
POST /api/workflow/publish 发布工作流
POST /api/workflow/test-run 工作流试运行
GET /api/workflow/node/list/{id} 获取节点列表
POST /api/workflow/node/add 新增节点
POST /api/workflow/node/execute 单节点试运行
GET /api/published/workflow/{id} 已发布工作流信息

V1 现有实现

MCP 层 (packages/mcp.ts)

核心设计:配置下发 + stdio 聚合代理

服务端 / ACP 下发 context_servers
              ↓
    syncMcpConfigToProxyAndReload(mcpServers)
              ↓
    ┌─────────────────────────────────────┐
    │  1. 合并默认服务 (chrome-devtools)    │
    │  2. 注入环境变量 (PATH, UV_*, etc.)   │
    │  3. 写入临时配置文件 (tmpdir)         │
    │  4. PersistentMcpBridge 重启         │
    │     (仅 persistent 服务)             │
    └─────────────────────────────────────┘
              ↓
    qiming-mcp-stdio-proxy 聚合启动
              ↓
    Agent 引擎 (ACP) 通过 stdio 与 MCP servers 通信

MCP Server 类型:

类型 配置 启动方式
stdio { command, args, env } mcp-proxy 按需 spawn 子进程
persistent { command, args, env, persistent: true } PersistentMcpBridge 长连接管理
remote { url, transport: "sse"|"streamable-http" } 直接透传 URL

问题:

  1. 配置仅来自 ACP 下发,无法提前获取和管理
  2. 客户端无法独立新增/修改 MCP 配置
  3. 无法将客户端新增的 MCP 同步到服务器

Skills 层 (renderer/services/integrations/skills.ts)

  • 仅在 Renderer 进程中实现 SkillManager
  • 5 个硬编码内置技能WebSearch、Calculator、FileRead、CommandRun、NetworkFetch
  • 无持久化、无与服务器同步、无动态新增/删除

问题:

  1. Skills 仅在前端,无法被 Agent 引擎调用
  2. 无法从服务器提前获取技能列表
  3. Agent 自己新增的技能无法同步到服务器

二、V2 设计原则

2.1 双向管理 + 同步

数据类型 服务器职责 客户端职责
MCP 配置 CRUD + 存储 + Agent 组件绑定 提前获取 + CRUD + 推送到服务器 + 联动启动
Skills CRUD + 存储 + 发布 提前获取 + CRUD + 推送到服务器

2.2 核心能力

┌─────────────────────────────────────────────────────────────────────────┐
│                      V2 工具管理核心能力                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. 提前获取    →  主动从服务器拉取配置列表(不等 ACP 下发)              │
│  2. 修改管理    →  客户端独立 CRUD 配置                                  │
│  3. 同步到服务器 →  客户端新增/修改的配置推送到服务器                     │
│  4. 联动启动    →  MCP 配置变更后联动启动 proxy                          │
│  5. 冷启动优化  →  预热常用 MCP 包,解决首次安装慢                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

2.3 MCP 冷启动优化

问题分析

V1 的 MCP 冷启动慢的原因:

首次启动 MCP Server (如 mcp-server-filesystem)
              ↓
    npx -y @anthropic/mcp-server-filesystem
              ↓
    ┌─────────────────────────────────────┐
    │  1. 检查本地缓存                      │
    │  2. 无缓存 → 下载 npm 包 (慢!)        │
    │  3. 安装依赖 (慢!)                    │
    │  4. 启动进程                          │
    └─────────────────────────────────────┘
              ↓
    首次启动可能需要 10-30 秒

V2 解决方案MCP 预热机制

┌────────────────────────────────────────────────────────────────────────┐
│                      MCP 预热机制 (V2)                                  │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │  1. 常用 MCP 包预定义                                              │  │
│  │                                                                   │  │
│  │  PREINSTALLED_MCP_PACKAGES = [                                   │  │
│  │    "@anthropic/mcp-server-filesystem",                           │  │
│  │    "@anthropic/mcp-server-github",                               │  │
│  │    "@anthropic/mcp-server-slack",                                │  │
│  │    "@anthropic/mcp-server-sqlite",                               │  │
│  │    "mcp-server-fetch",                                           │  │
│  │    "chrome-devtools-mcp",                                        │  │
│  │    // ... 更多常用包                                              │  │
│  │  ]                                                               │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                                                                        │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │  2. 应用启动时后台预热                                             │  │
│  │                                                                   │  │
│  │  async function warmupMcpPackages(): Promise<void> {             │  │
│  │    for (const pkg of PREINSTALLED_MCP_PACKAGES) {                │  │
│  │      // 检查是否已安装                                             │  │
│  │      if (!await isPackageCached(pkg)) {                          │  │
│  │        // 后台静默预下载                                           │  │
│  │        await predownloadPackage(pkg);                            │  │
│  │      }                                                            │  │
│  │    }                                                              │  │
│  │  }                                                                │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                                                                        │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │  3. 服务器配置同步时预热                                            │  │
│  │                                                                   │  │
│  │  async syncAgentMcpComponents(agentId): Promise<void> {          │  │
│  │    const mcps = await fetchFromServer(agentId);                  │  │
│  │                                                                   │  │
│  │    // 预热所有需要的 MCP 包                                        │  │
│  │    for (const mcp of mcps) {                                     │  │
│  │      await warmupMcpPackage(mcp.command, mcp.args);              │  │
│  │    }                                                              │  │
│  │                                                                   │  │
│  │    // 然后启动 proxy                                              │  │
│  │    await syncMcpConfigToProxyAndReload(mcps);                    │  │
│  │  }                                                                │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                                                                        │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │  4. 进度提示与状态展示                                              │  │
│  │                                                                   │  │
│  │  UI 显示: "正在准备 MCP 服务 (3/5)..."                            │  │
│  │  ┌──────────────────────────────────────┐                        │  │
│  │  │ ████████░░░░░░░░  60%                │                        │  │
│  │  │ 正在下载: mcp-server-github           │                        │  │
│  │  └──────────────────────────────────────┘                        │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

McpWarmupService 实现

/**
 * MCP 预热服务
 * 
 * 职责:
 * - 应用启动时预热常用 MCP 包
 * - 服务器配置同步时预热需要的包
 * - 提供预热进度回调
 */
class McpWarmupService {
  private warmupStatus: Map<string, WarmupStatus> = new Map();
  private npmCache: string; // npm 缓存目录

  /** 预定义的常用 MCP 包 */
  private readonly PREINSTALLED_PACKAGES = [
    { name: "@anthropic/mcp-server-filesystem", command: "npx", args: ["-y", "@anthropic/mcp-server-filesystem"] },
    { name: "@anthropic/mcp-server-github", command: "npx", args: ["-y", "@anthropic/mcp-server-github"] },
    { name: "mcp-server-fetch", command: "npx", args: ["-y", "mcp-server-fetch"] },
    { name: "chrome-devtools-mcp", command: "npx", args: ["-y", "chrome-devtools-mcp@latest"] },
    // uvx 包
    { name: "mcp-server-sqlite", command: "uvx", args: ["mcp-server-sqlite"] },
  ];

  /**
   * 应用启动时预热常用包
   */
  async warmupOnStartup(
    onProgress?: (status: WarmupProgress) => void,
  ): Promise<void> {
    const total = this.PREINSTALLED_PACKAGES.length;
    let completed = 0;

    for (const pkg of this.PREINSTALLED_PACKAGES) {
      onProgress?.({
        phase: "startup",
        current: pkg.name,
        completed,
        total,
        percent: Math.round((completed / total) * 100),
      });

      await this.warmupPackage(pkg.name, pkg.command, pkg.args);
      completed++;
    }

    onProgress?.({
      phase: "complete",
      current: null,
      completed: total,
      total,
      percent: 100,
    });
  }

  /**
   * 预热单个 MCP 包
   */
  async warmupPackage(
    packageName: string,
    command: string,
    args: string[],
  ): Promise<WarmupResult> {
    // 1. 检查是否已缓存
    if (await this.isPackageCached(packageName)) {
      this.warmupStatus.set(packageName, { cached: true, warmedAt: Date.now() });
      return { cached: true, alreadyCached: true };
    }

    // 2. 执行预热命令(静默下载到缓存)
    this.warmupStatus.set(packageName, { cached: false, warming: true });

    try {
      if (command === "npx" || command === "npm") {
        await this.warmupNpxPackage(packageName, args);
      } else if (command === "uvx" || command === "uv") {
        await this.warmupUvxPackage(packageName, args);
      }

      this.warmupStatus.set(packageName, { cached: true, warmedAt: Date.now() });
      return { cached: true, alreadyCached: false };
    } catch (error) {
      this.warmupStatus.set(packageName, { cached: false, error: String(error) });
      return { cached: false, error: String(error) };
    }
  }

  /**
   * 检查包是否已缓存
   */
  private async isPackageCached(packageName: string): Promise<boolean> {
    // 检查 npm 缓存
    const npmCached = await this.checkNpmCache(packageName);
    if (npmCached) return true;

    // 检查 uv 缓存
    const uvCached = await this.checkUvCache(packageName);
    return uvCached;
  }

  /**
   * 预热 npx 包
   */
  private async warmupNpxPackage(packageName: string, args: string[]): Promise<void> {
    // 使用 npx --yes 预下载包(不实际运行)
    // 或者直接 npm pack 然后解压到缓存
    const { execFile } = require("child_process");
    
    return new Promise((resolve, reject) => {
      const proc = execFile(
        "npx",
        [...args, "--help"], // 只获取帮助,不实际运行服务
        { timeout: 60000 },
        (error) => {
          if (error) reject(error);
          else resolve();
        },
      );
    });
  }

  /**
   * 预热 uvx 包
   */
  private async warmupUvxPackage(packageName: string, args: string[]): Promise<void> {
    const { execFile } = require("child_process");
    const uvPath = getUvBinPath();
    
    return new Promise((resolve, reject) => {
      const proc = execFile(
        uvPath,
        ["tool", "install", packageName, "--quiet"],
        { timeout: 120000 },
        (error) => {
          if (error) reject(error);
          else resolve();
        },
      );
    });
  }

  /**
   * 获取预热状态
   */
  getWarmupStatus(): Record<string, WarmupStatus> {
    return Object.fromEntries(this.warmupStatus);
  }

  /**
   * 批量预热(用于服务器配置同步)
   */
  async warmupForMcpConfigs(
    configs: McpConfigEntry[],
    onProgress?: (status: WarmupProgress) => void,
  ): Promise<void> {
    const packages = configs
      .filter((c) => c.transport.type === "stdio")
      .map((c) => ({
        name: this.extractPackageName(c.transport.command, c.transport.args),
        command: c.transport.command,
        args: c.transport.args || [],
      }))
      .filter((p, i, arr) => 
        p.name && arr.findIndex((x) => x.name === p.name) === i, // 去重
      );

    const total = packages.length;
    let completed = 0;

    for (const pkg of packages) {
      onProgress?.({
        phase: "sync",
        current: pkg.name,
        completed,
        total,
        percent: Math.round((completed / total) * 100),
      });

      await this.warmupPackage(pkg.name, pkg.command, pkg.args);
      completed++;
    }

    onProgress?.({
      phase: "complete",
      current: null,
      completed: total,
      total,
      percent: 100,
    });
  }

  /**
   * 从命令和参数中提取包名
   */
  private extractPackageName(command: string, args: string[]): string {
    if (command === "npx" || command.includes("npx")) {
      // npx -y @anthropic/mcp-server-filesystem
      const idx = args.indexOf("-y");
      if (idx >= 0 && args[idx + 1]) {
        return args[idx + 1].split("@")[0] || args[idx + 1];
      }
    }
    if (command === "uvx" || command.includes("uvx")) {
      // uvx mcp-server-sqlite
      return args[0] || "";
    }
    return "";
  }
}

interface WarmupStatus {
  cached: boolean;
  warmedAt?: number;
  warming?: boolean;
  error?: string;
}

interface WarmupProgress {
  phase: "startup" | "sync" | "complete";
  current: string | null;
  completed: number;
  total: number;
  percent: number;
}

interface WarmupResult {
  cached: boolean;
  alreadyCached?: boolean;
  error?: string;
}

与 McpManager 集成

class McpManager {
  private warmupService: McpWarmupService;

  /**
   * 从服务器同步 MCP 配置时,先预热再启动
   */
  async syncAgentMcpComponents(
    agentId: number,
    onProgress?: (status: WarmupProgress) => void,
  ): Promise<SyncResult> {
    // 1. 从服务器拉取配置
    const mcps = await this.fetchFromServer(agentId);

    // 2. 预热需要的 MCP 包
    await this.warmupService.warmupForMcpConfigs(mcps, onProgress);

    // 3. 启动 proxy此时包已缓存启动很快
    await this.applyMcpConfig();

    return {
      success: true,
      pulled: mcps.length,
      pushed: 0,
      conflicts: [],
    };
  }
}

UI 进度展示

┌─ MCP 服务准备中 ──────────────────────────────────────────────────────┐
│                                                                       │
│  ████████████████░░░░░░░░  67%                                       │
│                                                                       │
│  正在准备: @anthropic/mcp-server-github                               │
│                                                                       │
│  ✓ @anthropic/mcp-server-filesystem (已缓存)                         │
│  ✓ mcp-server-fetch (已缓存)                                         │
│  ○ @anthropic/mcp-server-github (下载中...)                          │
│  ○ chrome-devtools-mcp (等待中)                                      │
│                                                                       │
│  [跳过] [后台运行]                                                     │
└───────────────────────────────────────────────────────────────────────┘

2.4 MCP 架构V2 增强版)

┌────────────────────────────────────────────────────────────────────────┐
│                    Qiming Agent OS (服务器)                              │
│  /api/mcp/create  ·  /api/mcp/update  ·  /api/mcp/list/{spaceId}      │
│  /api/mcp/{id}    ·  /api/mcp/test    ·  /api/mcp/stop/{id}           │
│  /api/agent/component/mcp/update                                      │
└────────────────────────────────┬───────────────────────────────────────┘
                                 │
                    ┌────────────┴────────────┐
                    │                         │
                    ▼                         ▼
            提前获取/同步              Agent 组件绑定同步
                    │                         │
                    ▼                         ▼
┌────────────────────────────────────────────────────────────────────────┐
│                    QimingClaw Client (Electron)                          │
│                                                                        │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │  McpManager (V2 增强)                                             │  │
│  │                                                                   │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐              │  │
│  │  │ list()      │  │ create()    │  │ update()    │              │  │
│  │  │ 从服务器拉取 │  │ 本地+服务器  │  │ 本地+服务器  │              │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘              │  │
│  │                                                                   │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐              │  │
│  │  │ delete()    │  │ test()      │  │ syncToServer│              │  │
│  │  │ 本地+服务器  │  │ 本地试运行   │  │ 推送变更    │              │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘              │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                                 │                                      │
│                                 ▼                                      │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │  mcpProxyManager (保持 V1 实现)                                    │  │
│  │  - syncMcpConfigToProxyAndReload(mcpServers)                      │  │
│  │  - getAgentMcpConfig() → 注入 ACP 引擎                             │  │
│  │  - PersistentMcpBridge 管理 persistent 服务                        │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                                 │                                      │
│                                 ▼                                      │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │  qiming-mcp-stdio-proxy (聚合层)                                    │  │
│  │  - stdio 聚合所有 MCP servers                                      │  │
│  │  - 支持 stdio / sse / streamable-http 传输                        │  │
│  │  - 工具白名单/黑名单过滤                                           │  │
│  └──────────────────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────────────────┘

三、MCP 管理层设计

3.1 McpManagerV2 增强)

/**
 * MCP 管理器 (V2)
 * 
 * 核心能力:
 * 1. 提前获取 - 从服务器拉取 MCP 配置列表
 * 2. 修改管理 - 本地 CRUD + 同步到服务器
 * 3. 联动启动 - 配置变更后触发 proxy 重启
 */
class McpManager {
  private qimingClient: QimingApiClient;
  private localConfig: Map<string, McpConfigEntry> = new Map();
  private syncStatus: Map<string, SyncStatus> = new Map();

  // ==================== 提前获取 ====================

  /**
   * 从服务器拉取 Space 下的所有 MCP 配置
   * (可独立于 Agent 使用,提前获取并展示)
   */
  async fetchFromServer(spaceId: number): Promise<McpConfigEntry[]> {
    const list = await this.qimingClient.request<McpDetailInfo[]>(
      "GET",
      `/api/mcp/list/${spaceId}`,
    );

    // 更新本地缓存
    for (const item of list) {
      this.localConfig.set(item.id, this.mapServerToLocal(item));
      this.syncStatus.set(item.id, { synced: true, lastSyncAt: Date.now() });
    }

    return list.map((item) => this.mapServerToLocal(item));
  }

  /**
   * 获取单个 MCP 配置详情
   */
  async getDetail(mcpId: number): Promise<McpConfigEntry> {
    const detail = await this.qimingClient.request<McpDetailInfo>(
      "GET",
      `/api/mcp/${mcpId}`,
    );
    return this.mapServerToLocal(detail);
  }

  // ==================== 修改管理 ====================

  /**
   * 创建新的 MCP 配置
   * → 先保存到本地,再同步到服务器
   */
  async create(config: McpCreateInput): Promise<McpConfigEntry> {
    // 1. 本地验证
    this.validateConfig(config);

    // 2. 同步到服务器
    const serverResult = await this.qimingClient.request<McpDetailInfo>(
      "POST",
      "/api/mcp/create",
      {
        name: config.name,
        description: config.description,
        type: config.transport.type,
        config: config.transport,
      },
    );

    // 3. 更新本地缓存
    const entry = this.mapServerToLocal(serverResult);
    this.localConfig.set(entry.id, entry);
    this.syncStatus.set(entry.id, { synced: true, lastSyncAt: Date.now() });

    // 4. 如果标记为自动启动,联动启动
    if (config.autoStart) {
      await this.applyMcpConfig();
    }

    return entry;
  }

  /**
   * 更新 MCP 配置
   * → 本地 + 服务器同步
   */
  async update(mcpId: string, updates: McpUpdateInput): Promise<McpConfigEntry> {
    const existing = this.localConfig.get(mcpId);
    if (!existing) {
      throw new Error(`MCP ${mcpId} not found`);
    }

    // 1. 同步到服务器
    await this.qimingClient.request(
      "POST",
      "/api/mcp/update",
      {
        id: mcpId,
        name: updates.name,
        description: updates.description,
        config: updates.transport,
      },
    );

    // 2. 更新本地缓存
    const updated: McpConfigEntry = {
      ...existing,
      ...updates,
      updatedAt: Date.now(),
    };
    this.localConfig.set(mcpId, updated);
    this.syncStatus.set(mcpId, { synced: true, lastSyncAt: Date.now() });

    // 3. 联动启动
    await this.applyMcpConfig();

    return updated;
  }

  /**
   * 删除 MCP 配置
   * → 本地 + 服务器同步
   */
  async delete(mcpId: string): Promise<void> {
    // 1. 从服务器删除
    await this.qimingClient.request(
      "POST",
      "/api/mcp/stop",
      { id: mcpId },
    );

    // 2. 从本地移除
    this.localConfig.delete(mcpId);
    this.syncStatus.delete(mcpId);

    // 3. 联动启动(更新 proxy 配置)
    await this.applyMcpConfig();
  }

  /**
   * 测试 MCP 配置(本地试运行)
   */
  async test(mcpId: string): Promise<McpTestResult> {
    const config = this.localConfig.get(mcpId);
    if (!config) {
      throw new Error(`MCP ${mcpId} not found`);
    }

    // 可选:委托服务器测试
    // return await this.qimingClient.request("POST", "/api/mcp/test", { id: mcpId });

    // 或本地测试(启动临时进程验证)
    return await this.testMcpLocally(config);
  }

  // ==================== Agent 组件绑定 ====================

  /**
   * 绑定 MCP 到 Agent
   * → 更新 Agent 组件配置
   */
  async bindToAgent(agentId: number, mcpIds: string[]): Promise<void> {
    await this.qimingClient.request(
      "POST",
      `/api/agent/component/mcp/update`,
      {
        agentId,
        mcpIds: mcpIds.map((id) => parseInt(id)),
      },
    );

    // 联动启动
    await this.applyMcpConfig();
  }

  /**
   * 获取 Agent 绑定的 MCP 配置
   */
  async getAgentMcpConfig(agentId: number): Promise<McpConfigEntry[]> {
    const components = await this.qimingClient.request<AgentComponentInfo[]>(
      "GET",
      `/api/agent/component/list/${agentId}`,
    );

    const mcpComponents = components.filter((c) => c.type === "mcp");
    const results: McpConfigEntry[] = [];

    for (const mc of mcpComponents) {
      const detail = await this.getDetail(mc.refId);
      results.push(detail);
    }

    return results;
  }

  // ==================== 联动启动 ====================

  /**
   * 应用 MCP 配置到 proxy
   * → 调用 V1 的 syncMcpConfigToProxyAndReload
   */
  private async applyMcpConfig(): Promise<void> {
    const mcpServers: Record<string, McpServerEntry> = {};

    for (const [id, config] of this.localConfig) {
      if (config.enabled) {
        mcpServers[config.name] = this.mapToServerEntry(config);
      }
    }

    await syncMcpConfigToProxyAndReload(mcpServers);
  }

  // ==================== 辅助方法 ====================

  /**
   * 列出本地缓存的 MCP 配置
   */
  listLocal(): McpConfigEntry[] {
    return Array.from(this.localConfig.values());
  }

  /**
   * 获取同步状态
   */
  getSyncStatus(): Record<string, SyncStatus> {
    return Object.fromEntries(this.syncStatus);
  }

  /**
   * 手动推送本地变更到服务器
   */
  async syncToServer(): Promise<SyncResult> {
    let pushed = 0;
    for (const [id, status] of this.syncStatus) {
      if (!status.synced) {
        const config = this.localConfig.get(id);
        if (config) {
          await this.qimingClient.request("POST", "/api/mcp/update", {
            id,
            name: config.name,
            config: config.transport,
          });
          status.synced = true;
          status.lastSyncAt = Date.now();
          pushed++;
        }
      }
    }
    return { success: true, pulled: 0, pushed, conflicts: [] };
  }

  private mapServerToLocal(server: McpDetailInfo): McpConfigEntry {
    return {
      id: String(server.id),
      name: server.name,
      description: server.description || "",
      transport: server.config,
      enabled: server.enabled ?? true,
      autoStart: server.autoStart ?? false,
      createdAt: server.createdAt,
      updatedAt: server.updatedAt,
    };
  }

  private mapToServerEntry(config: McpConfigEntry): McpServerEntry {
    const transport = config.transport;
    if (transport.url) {
      return {
        url: transport.url,
        transport: transport.transportType,
        headers: transport.headers,
        authToken: transport.authToken,
      };
    }
    return {
      command: transport.command,
      args: transport.args || [],
      env: transport.env,
      persistent: transport.persistent,
    };
  }
}

3.2 类型定义

/** MCP 配置条目 */
interface McpConfigEntry {
  id: string;
  name: string;
  description: string;
  transport: McpTransportConfig;
  enabled: boolean;
  autoStart: boolean;
  createdAt: number;
  updatedAt: number;
}

/** MCP 传输配置 */
type McpTransportConfig =
  | {
      type: "stdio";
      command: string;
      args?: string[];
      env?: Record<string, string>;
      cwd?: string;
      persistent?: boolean;
    }
  | {
      type: "sse";
      url: string;
      headers?: Record<string, string>;
      authToken?: string;
    }
  | {
      type: "streamable-http";
      url: string;
      headers?: Record<string, string>;
      authToken?: string;
    };

/** 创建 MCP 输入 */
interface McpCreateInput {
  name: string;
  description?: string;
  transport: McpTransportConfig;
  autoStart?: boolean;
}

/** 更新 MCP 输入 */
interface McpUpdateInput {
  name?: string;
  description?: string;
  transport?: McpTransportConfig;
  enabled?: boolean;
  autoStart?: boolean;
}

/** MCP 测试结果 */
interface McpTestResult {
  success: boolean;
  tools?: string[]; // 发现的工具列表
  error?: string;
  latencyMs: number;
}

/** 同步状态 */
interface SyncStatus {
  synced: boolean;
  lastSyncAt: number;
  error?: string;
}

四、Skills 管理层设计

4.1 SkillManagerV2 增强)

/**
 * 技能管理器 (V2)
 * 
 * 核心能力:
 * 1. 提前获取 - 从服务器拉取技能列表
 * 2. 修改管理 - 本地 CRUD + 同步到服务器
 * 3. Agent 学习技能 - Agent 自创建并推送
 */
class SkillManager {
  private qimingClient: QimingApiClient;
  private skills: Map<string, SkillEntry> = new Map();
  private syncStatus: Map<string, SyncStatus> = new Map();

  // ==================== 提前获取 ====================

  /**
   * 从服务器拉取 Space 下的所有技能
   */
  async fetchFromServer(spaceId: number): Promise<SkillEntry[]> {
    const list = await this.qimingClient.request<SkillDetailInfo[]>(
      "GET",
      `/api/published/skill/list`,
      { spaceId },
    );

    for (const item of list) {
      this.skills.set(String(item.id), this.mapServerToLocal(item));
      this.syncStatus.set(String(item.id), { synced: true, lastSyncAt: Date.now() });
    }

    return list.map((item) => this.mapServerToLocal(item));
  }

  /**
   * 获取技能详情
   */
  async getDetail(skillId: number): Promise<SkillEntry> {
    const detail = await this.qimingClient.request<SkillDetailInfo>(
      "GET",
      `/api/skill/${skillId}`,
    );
    return this.mapServerToLocal(detail);
  }

  /**
   * 导出技能
   */
  async export(skillId: number): Promise<Buffer> {
    const response = await this.qimingClient.request<ArrayBuffer>(
      "GET",
      `/api/skill/export/${skillId}`,
    );
    return Buffer.from(response);
  }

  // ==================== 修改管理 ====================

  /**
   * 创建新技能
   */
  async create(input: SkillCreateInput): Promise<SkillEntry> {
    // 1. 同步到服务器
    const result = await this.qimingClient.request<SkillDetailInfo>(
      "POST",
      "/api/skill/update",
      {
        name: input.name,
        description: input.description,
        content: input.content,
        type: input.type,
      },
    );

    // 2. 更新本地缓存
    const entry = this.mapServerToLocal(result);
    this.skills.set(entry.id, entry);
    this.syncStatus.set(entry.id, { synced: true, lastSyncAt: Date.now() });

    return entry;
  }

  /**
   * 更新技能
   */
  async update(skillId: string, updates: SkillUpdateInput): Promise<SkillEntry> {
    const existing = this.skills.get(skillId);
    if (!existing) {
      throw new Error(`Skill ${skillId} not found`);
    }

    // 1. 同步到服务器
    await this.qimingClient.request(
      "POST",
      "/api/skill/update",
      {
        id: skillId,
        name: updates.name,
        description: updates.description,
        content: updates.content,
      },
    );

    // 2. 更新本地缓存
    const updated: SkillEntry = {
      ...existing,
      ...updates,
      updatedAt: Date.now(),
    };
    this.skills.set(skillId, updated);
    this.syncStatus.set(skillId, { synced: true, lastSyncAt: Date.now() });

    return updated;
  }

  /**
   * 删除技能
   */
  async delete(skillId: string): Promise<void> {
    // Skills 删除通过导入接口的删除功能,或直接从 Agent 组件解绑
    this.skills.delete(skillId);
    this.syncStatus.delete(skillId);
  }

  /**
   * 导入技能(从文件或 zip
   */
  async import(skillZip: Buffer): Promise<SkillEntry> {
    const result = await this.qimingClient.request<SkillDetailInfo>(
      "POST",
      "/api/skill/import",
      skillZip,
      { headers: { "Content-Type": "multipart/form-data" } },
    );

    const entry = this.mapServerToLocal(result);
    this.skills.set(entry.id, entry);
    this.syncStatus.set(entry.id, { synced: true, lastSyncAt: Date.now() });

    return entry;
  }

  // ==================== Agent 组件绑定 ====================

  /**
   * 绑定技能到 Agent
   */
  async bindToAgent(agentId: number, skillIds: string[]): Promise<void> {
    await this.qimingClient.request(
      "POST",
      `/api/agent/component/skill/update`,
      {
        agentId,
        skillIds: skillIds.map((id) => parseInt(id)),
      },
    );
  }

  /**
   * 获取 Agent 绑定的技能
   */
  async getAgentSkills(agentId: number): Promise<SkillEntry[]> {
    const components = await this.qimingClient.request<AgentComponentInfo[]>(
      "GET",
      `/api/agent/component/list/${agentId}`,
    );

    const skillComponents = components.filter((c) => c.type === "skill");
    const results: SkillEntry[] = [];

    for (const sc of skillComponents) {
      const detail = await this.getDetail(sc.refId);
      results.push(detail);
    }

    return results;
  }

  // ==================== Agent 学习技能 ====================

  /**
   * Agent 自创建技能
   * → 创建后自动推送到服务器
   */
  async createLearnedSkill(definition: LearnedSkillDefinition): Promise<SkillEntry> {
    // 1. 构建技能内容
    const skillContent = this.buildSkillContent(definition);

    // 2. 创建并同步到服务器
    const entry = await this.create({
      name: definition.name,
      description: definition.description,
      content: skillContent,
      type: "learned",
    });

    // 3. 记录来源
    entry.origin = definition.origin;

    return entry;
  }

  // ==================== 辅助方法 ====================

  /**
   * 列出本地缓存的技能
   */
  listLocal(filter?: { type?: string; enabled?: boolean }): SkillEntry[] {
    let results = Array.from(this.skills.values());
    if (filter?.type) {
      results = results.filter((s) => s.type === filter.type);
    }
    if (filter?.enabled !== undefined) {
      results = results.filter((s) => s.enabled === filter.enabled);
    }
    return results;
  }

  /**
   * 获取同步状态
   */
  getSyncStatus(): Record<string, SyncStatus> {
    return Object.fromEntries(this.syncStatus);
  }

  private mapServerToLocal(server: SkillDetailInfo): SkillEntry {
    return {
      id: String(server.id),
      name: server.name,
      description: server.description || "",
      content: server.content,
      type: server.type || "custom",
      enabled: server.enabled ?? true,
      createdAt: server.createdAt,
      updatedAt: server.updatedAt,
    };
  }

  private buildSkillContent(definition: LearnedSkillDefinition): string {
    // 根据实现类型构建技能内容
    if (definition.implementation.type === "script") {
      return `# ${definition.name}\n\n${definition.description}\n\n\`\`\`${definition.implementation.language}\n${definition.implementation.code}\n\`\`\``;
    }
    // ... 其他类型
    return "";
  }
}

4.2 类型定义

/** 技能条目 */
interface SkillEntry {
  id: string;
  name: string;
  description: string;
  content: string; // 技能定义Markdown / JSON / 代码)
  type: "built-in" | "custom" | "learned" | "synced";
  enabled: boolean;
  createdAt: number;
  updatedAt: number;
  origin?: {
    memoryId?: string;
    sessionId?: string;
    reason?: string;
  };
}

/** 创建技能输入 */
interface SkillCreateInput {
  name: string;
  description?: string;
  content: string;
  type?: string;
}

/** 更新技能输入 */
interface SkillUpdateInput {
  name?: string;
  description?: string;
  content?: string;
  enabled?: boolean;
}

/** Agent 学习技能定义 */
interface LearnedSkillDefinition {
  name: string;
  description: string;
  implementation:
    | {
        type: "script";
        language: "bash" | "node" | "python";
        code: string;
        timeout?: number;
      }
    | {
        type: "composite";
        steps: { toolId: string; args: Record<string, string> }[];
      };
  origin: {
    memoryId?: string;
    sessionId?: string;
    reason: string;
  };
}

五、统一工具层

5.1 ToolRegistry工具注册中心

将 MCP Tools、Skills、Plugin、Workflow 统一管理:

/**
 * 工具注册中心
 * 
 * 统一管理所有工具来源,提供统一接口给 Agent 引擎
 */
class ToolRegistry {
  private mcpManager: McpManager;
  private skillManager: SkillManager;
  private tools: Map<string, UnifiedTool> = new Map();

  /**
   * 刷新所有工具(从 MCP servers 和 Skills 获取)
   */
  async refreshAll(): Promise<void> {
    // 1. 从 MCP servers 获取工具
    await this.refreshMcpTools();

    // 2. 从 Skills 获取工具
    await this.refreshSkillTools();
  }

  /**
   * 从 MCP servers 刷新工具列表
   */
  private async refreshMcpTools(): Promise<void> {
    const mcpConfigs = this.mcpManager.listLocal();
    
    for (const config of mcpConfigs) {
      if (config.enabled) {
        // 通过 mcp-proxy 获取工具列表
        const tools = await this.fetchMcpServerTools(config);
        for (const tool of tools) {
          this.tools.set(tool.id, tool);
        }
      }
    }
  }

  /**
   * 从 Skills 刷新工具列表
   */
  private async refreshSkillTools(): Promise<void> {
    const skills = this.skillManager.listLocal({ enabled: true });
    
    for (const skill of skills) {
      const tool = this.mapSkillToTool(skill);
      this.tools.set(tool.id, tool);
    }
  }

  /**
   * 列出所有工具
   */
  listTools(filter?: ToolFilter): UnifiedTool[] {
    let results = Array.from(this.tools.values());
    
    if (filter?.category) {
      results = results.filter((t) => t.category === filter.category);
    }
    if (filter?.source) {
      results = results.filter((t) => t.source.type === filter.source);
    }
    
    return results;
  }

  /**
   * 获取工具 schemas给 LLM 的 function calling
   */
  getToolSchemas(): ToolSchema[] {
    return this.listTools()
      .filter((t) => t.enabled)
      .map((t) => ({
        name: t.name,
        description: t.description,
        parameters: t.parameters,
      }));
  }

  /**
   * 执行工具
   */
  async execute(
    toolId: string,
    args: Record<string, unknown>,
    context: ToolContext,
  ): Promise<ToolResult> {
    const tool = this.tools.get(toolId);
    if (!tool) {
      throw new Error(`Tool ${toolId} not found`);
    }

    const startTime = Date.now();
    try {
      let result: unknown;

      switch (tool.source.type) {
        case "mcp":
          result = await this.executeMcpTool(tool, args);
          break;
        case "skill":
          result = await this.executeSkill(tool, args, context);
          break;
        case "built-in":
          result = await this.executeBuiltIn(tool, args, context);
          break;
        default:
          throw new Error(`Unknown tool source: ${tool.source.type}`);
      }

      return {
        success: true,
        output: result,
        executionTimeMs: Date.now() - startTime,
      };
    } catch (error) {
      return {
        success: false,
        output: null,
        error: String(error),
        executionTimeMs: Date.now() - startTime,
      };
    }
  }
}

5.2 统一工具接口

/** 统一工具定义 */
interface UnifiedTool {
  id: string;
  name: string;
  description: string;
  category: ToolCategory;
  source: ToolSource;
  parameters: {
    type: "object";
    properties: Record<string, JsonSchemaProperty>;
    required?: string[];
  };
  enabled: boolean;
  requiresPermission: boolean;
  metadata: {
    createdAt: number;
    updatedAt: number;
    usageCount: number;
    successRate: number;
  };
}

type ToolCategory =
  | "file"
  | "network"
  | "system"
  | "data"
  | "ai"
  | "mcp"
  | "skill"
  | "custom";

type ToolSource =
  | { type: "built-in" }
  | { type: "mcp"; serverId: string; serverName: string }
  | { type: "skill"; skillId: string; skillName: string }
  | { type: "plugin"; pluginId: number }
  | { type: "workflow"; workflowId: number };

六、IPC 接口设计

// ==================== MCP ====================

// 提前获取
'mcp:fetch'                McpConfigEntry[]        // 从服务器拉取列表
'mcp:detail'               McpConfigEntry          // 获取详情

// CRUD
'mcp:create'               McpConfigEntry          // 创建(同步到服务器)
'mcp:update'               McpConfigEntry          // 更新(同步到服务器)
'mcp:delete'               { success: boolean }    // 删除(同步到服务器)
'mcp:test'                 McpTestResult           // 本地试运行

// Agent 绑定
'mcp:bind:agent'           { success: boolean }    // 绑定到 Agent
'mcp:agent:list'           McpConfigEntry[]        // Agent 绑定的 MCP

// 状态
'mcp:list'                 McpConfigEntry[]        // 本地缓存列表
'mcp:status'               McpProxyStatus          // V1 已有
'mcp:sync:status'          Record<string, SyncStatus>

// ==================== Skills ====================

// 提前获取
'skill:fetch'              SkillEntry[]            // 从服务器拉取列表
'skill:detail'             SkillEntry              // 获取详情
'skill:export'             Buffer                  // 导出

// CRUD
'skill:create'             SkillEntry              // 创建(同步到服务器)
'skill:update'             SkillEntry              // 更新(同步到服务器)
'skill:delete'             { success: boolean }    // 删除
'skill:import'             SkillEntry              // 导入

// Agent 绑定
'skill:bind:agent'         { success: boolean }    // 绑定到 Agent
'skill:agent:list'         SkillEntry[]            // Agent 绑定的技能

// Agent 学习
'skill:learned:create'     SkillEntry              // Agent 自创建

// 状态
'skill:list'               SkillEntry[]            // 本地缓存列表
'skill:sync:status'        Record<string, SyncStatus>

// ==================== Tools ====================

'tools:list'               UnifiedTool[]
'tools:schemas'            ToolSchema[]
'tools:execute'            ToolResult
'tools:refresh'            { success: boolean }

七、UI 设计要点

MCP 管理页(完整 CRUD

┌─ MCP 服务器管理 ─────────────────────────────────────────────────────┐
│                                                                      │
│  当前 Space: 开发环境                              [从服务器同步]     │
│                                                                      │
│  ── 已配置的 MCP 服务器 ────────────────────────────  [+ 添加 MCP]    │
│                                                                      │
│  ● filesystem      ✓ 运行中  │ 3 工具  │ stdio         [编辑] [删除] │
│  ● github          ✓ 运行中  │ 8 工具  │ stdio         [编辑] [删除] │
│  ● remote-api      ✓ 连接中  │ 5 工具  │ sse           [编辑] [删除] │
│  ○ slack           ✗ 已停止  │ 0 工具  │ stdio         [编辑] [删除] │
│                                                                      │
│  ── 添加/编辑 MCP ─────────────────────────────────────────────────── │
│                                                                      │
│  名称:    [________________]                                        │
│  描述:    [________________]                                        │
│  类型:    ○ stdio  ○ SSE  ○ Streamable HTTP                        │
│                                                                      │
│  ── stdio 配置 ──────────────────────                                │
│  命令:    [npx -y @anthropic/mcp-server-filesystem____]             │
│  参数:    [/Users/apple/workspace_____________________]             │
│  环境变量: [+ 添加]                                                   │
│  ☑ 自动启动                                                         │
│                                                                      │
│  [测试连接]  [保存并同步到服务器]  [取消]                             │
│                                                                      │
│  上次同步: 5 分钟前   同步状态: ✓ 已同步                              │
└──────────────────────────────────────────────────────────────────────┘

技能管理页(完整 CRUD

┌─ 技能管理 ───────────────────────────────────────────────────────────┐
│                                                                      │
│  当前 Space: 开发环境                              [从服务器同步]     │
│                                                                      │
│  [全部] [内置] [服务器同步] [Agent 学习]              [+ 创建技能]    │
│                                                                      │
│  ── 已配置的技能 ────────────────────────────────────────────────────│
│                                                                      │
│  │ Web Search       │ 搜索网页信息      │ ✓ │ 内置      [查看]      │
│  │ json-parser      │ JSON 文件解析     │ ✓ │ 服务器    [编辑][删除] │
│  │ api-client       │ API 请求封装      │ ✓ │ 服务器    [编辑][删除] │
│  │ parse-log        │ 日志分析          │ ✓ │ Agent学习 [编辑][删除] │
│                                                                      │
│  ── 创建/编辑技能 ──────────────────────────────────────────────────  │
│                                                                      │
│  名称:    [________________]                                        │
│  描述:    [________________]                                        │
│  类型:    ○ 自定义  ○ 脚本  ○ 组合                                   │
│                                                                      │
│  ── 技能内容 ──────────────────────                                  │
│  ```python                                                           │
│  import json                                                         │
│  def parse_json(content):                                            │
│      return json.loads(content)                                      │
│  ```                                                                 │
│                                                                      │
│  [测试]  [保存并同步到服务器]  [取消]                                 │
│                                                                      │
│  上次同步: 2 分钟前   同步状态: ✓ 已同步                              │
└──────────────────────────────────────────────────────────────────────┘

Agent 工具绑定

┌─ Agent 工具配置 ─────────────────────────────────────────────────────┐
│                                                                      │
│  Agent: Claude Assistant                                             │
│                                                                      │
│  ── MCP 服务器 ─────────────────────────────────  [+ 添加 MCP]       │
│  ☑ filesystem      ✓ 运行中  │ 3 工具                               │
│  ☑ github          ✓ 运行中  │ 8 工具                               │
│  ☐ slack           ✗ 已停止  │ 0 工具                               │
│                                                                      │
│  ── 技能 ─────────────────────────────────────────  [+ 添加技能]     │
│  ☑ json-parser     │ JSON 文件解析                                  │
│  ☑ api-client      │ API 请求封装                                   │
│  ☐ code-review     │ 代码审查                                       │
│                                                                      │
│  ── 插件 ────────────────────────────────────────  [+ 添加插件]      │
│  (无)                                                                │
│                                                                      │
│  ── 工作流 ──────────────────────────────────────── [+ 添加工作流]   │
│  (无)                                                                │
│                                                                      │
│  [保存配置]                                                          │
└──────────────────────────────────────────────────────────────────────┘

相关文档