1587 lines
58 KiB
Markdown
1587 lines
58 KiB
Markdown
---
|
||
version: 2.0
|
||
last-updated: 2026-03-09
|
||
status: design
|
||
---
|
||
|
||
# 03 — Skills & MCP 工具管理
|
||
|
||
## 一、现状分析
|
||
|
||
### Qiming 服务端已有能力
|
||
|
||
技能管理 API(`workspace/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 管理 API(`workspace/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 的工作流组件配置
|
||
|
||
插件管理 API(`workspace/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}` | 已发布插件信息 |
|
||
|
||
工作流管理 API(`workspace/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 实现
|
||
|
||
```typescript
|
||
/**
|
||
* 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 集成
|
||
|
||
```typescript
|
||
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 McpManager(V2 增强)
|
||
|
||
```typescript
|
||
/**
|
||
* 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 类型定义
|
||
|
||
```typescript
|
||
/** 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 SkillManager(V2 增强)
|
||
|
||
```typescript
|
||
/**
|
||
* 技能管理器 (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 类型定义
|
||
|
||
```typescript
|
||
/** 技能条目 */
|
||
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 统一管理:
|
||
|
||
```typescript
|
||
/**
|
||
* 工具注册中心
|
||
*
|
||
* 统一管理所有工具来源,提供统一接口给 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 统一工具接口
|
||
|
||
```typescript
|
||
/** 统一工具定义 */
|
||
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 接口设计
|
||
|
||
```typescript
|
||
// ==================== 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 │ 代码审查 │
|
||
│ │
|
||
│ ── 插件 ──────────────────────────────────────── [+ 添加插件] │
|
||
│ (无) │
|
||
│ │
|
||
│ ── 工作流 ──────────────────────────────────────── [+ 添加工作流] │
|
||
│ (无) │
|
||
│ │
|
||
│ [保存配置] │
|
||
└──────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 相关文档
|
||
|
||
- [总体架构](./01-ARCHITECTURE.md)
|
||
- [知识库 · 工作流 · 插件](./07-KNOWLEDGE-WORKFLOW-PLUGIN.md)
|
||
- [V1 MCP 实现](../../src/main/services/packages/mcp.ts)
|
||
- [V1 Skills](../../src/renderer/services/integrations/skills.ts)
|
||
- [Agent 自我进化](./06-SELF-EVOLUTION.md)
|