foliko 1.0.81 → 1.0.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/cli/bin/foliko.js +12 -12
  2. package/cli/src/commands/chat.js +143 -143
  3. package/cli/src/commands/list.js +93 -93
  4. package/cli/src/index.js +75 -75
  5. package/cli/src/ui/chat-ui.js +201 -201
  6. package/cli/src/utils/ansi.js +40 -40
  7. package/cli/src/utils/markdown.js +292 -292
  8. package/examples/ambient-example.js +194 -194
  9. package/examples/basic.js +115 -115
  10. package/examples/bootstrap.js +121 -121
  11. package/examples/mcp-example.js +56 -56
  12. package/examples/skill-example.js +49 -49
  13. package/examples/test-chat.js +137 -137
  14. package/examples/test-mcp.js +85 -85
  15. package/examples/test-reload.js +59 -59
  16. package/examples/test-telegram.js +50 -50
  17. package/examples/test-tg-bot.js +45 -45
  18. package/examples/test-tg-simple.js +47 -47
  19. package/examples/test-tg.js +62 -62
  20. package/examples/test-think.js +43 -43
  21. package/examples/test-web-plugin.js +103 -103
  22. package/examples/test-weixin-feishu.js +103 -103
  23. package/examples/workflow.js +158 -158
  24. package/package.json +83 -83
  25. package/plugins/ai-plugin.js +102 -102
  26. package/plugins/ambient-agent/EventWatcher.js +113 -113
  27. package/plugins/ambient-agent/ExplorerLoop.js +640 -640
  28. package/plugins/ambient-agent/GoalManager.js +197 -197
  29. package/plugins/ambient-agent/Reflector.js +95 -95
  30. package/plugins/ambient-agent/StateStore.js +90 -90
  31. package/plugins/ambient-agent/constants.js +101 -101
  32. package/plugins/ambient-agent/index.js +579 -579
  33. package/plugins/audit-plugin.js +187 -187
  34. package/plugins/default-plugins.js +548 -548
  35. package/plugins/email/constants.js +64 -64
  36. package/plugins/email/handlers.js +461 -461
  37. package/plugins/email/index.js +278 -278
  38. package/plugins/email/monitor.js +269 -269
  39. package/plugins/email/parser.js +138 -138
  40. package/plugins/email/reply.js +151 -151
  41. package/plugins/email/utils.js +124 -124
  42. package/plugins/extension-executor-plugin.js +326 -326
  43. package/plugins/feishu-plugin.js +481 -481
  44. package/plugins/file-system-plugin.js +920 -920
  45. package/plugins/gate-trading.js +747 -747
  46. package/plugins/install-plugin.js +199 -199
  47. package/plugins/python-executor-plugin.js +367 -367
  48. package/plugins/python-plugin-loader.js +651 -651
  49. package/plugins/rules-plugin.js +294 -294
  50. package/plugins/scheduler-plugin.js +691 -691
  51. package/plugins/session-plugin.js +494 -494
  52. package/plugins/shell-executor-plugin.js +197 -197
  53. package/plugins/storage-plugin.js +263 -263
  54. package/plugins/subagent-plugin.js +845 -845
  55. package/plugins/telegram-plugin.js +482 -482
  56. package/plugins/think-plugin.js +345 -345
  57. package/plugins/tools-plugin.js +196 -196
  58. package/plugins/web-plugin.js +637 -637
  59. package/plugins/weixin-plugin.js +545 -545
  60. package/src/capabilities/index.js +11 -11
  61. package/src/capabilities/skill-manager.js +609 -609
  62. package/src/capabilities/workflow-engine.js +1109 -1109
  63. package/src/core/agent.js +958 -958
  64. package/src/core/framework.js +465 -465
  65. package/src/core/index.js +19 -19
  66. package/src/core/plugin-base.js +262 -262
  67. package/src/core/plugin-manager.js +863 -863
  68. package/src/core/provider.js +114 -114
  69. package/src/core/sub-agent-config.js +264 -264
  70. package/src/core/system-prompt-builder.js +120 -120
  71. package/src/core/tool-registry.js +517 -517
  72. package/src/core/tool-router.js +297 -297
  73. package/src/executors/executor-base.js +58 -58
  74. package/src/executors/mcp-executor.js +845 -845
  75. package/src/index.js +25 -25
  76. package/src/utils/circuit-breaker.js +301 -301
  77. package/src/utils/error-boundary.js +363 -363
  78. package/src/utils/error.js +374 -374
  79. package/src/utils/event-emitter.js +97 -97
  80. package/src/utils/id.js +133 -133
  81. package/src/utils/index.js +217 -217
  82. package/src/utils/logger.js +181 -181
  83. package/src/utils/plugin-helpers.js +90 -90
  84. package/src/utils/retry.js +122 -122
  85. package/src/utils/sandbox.js +292 -292
  86. package/test/tool-registry-validation.test.js +218 -218
  87. package/website/script.js +136 -136
@@ -1,845 +1,845 @@
1
- /**
2
- * MCPExecutor MCP 执行器
3
- * 使用 @ai-sdk/mcp 连接到 MCP 服务器
4
- */
5
-
6
- const { Plugin } = require('../core/plugin-base');
7
- const { spawn } = require('child_process');
8
- const fs = require('fs');
9
- const path = require('path');
10
- const { z } = require('zod');
11
- const { createMCPClient } = require('@ai-sdk/mcp');
12
- const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js');
13
- const { zodSchemaToMarkdown } = require('@chnak/zod-to-markdown');
14
- const { logger } = require('../utils/logger');
15
-
16
- const log = logger.child('MCPExecutor');
17
-
18
- /**
19
- * MCP 客户端包装器
20
- */
21
- class MCPClientWrapper {
22
- constructor(options = {}) {
23
- this.serverName = options.serverName || 'unknown';
24
- this.command = options.command;
25
- this.args = options.args || [];
26
- this.env = options.env || {};
27
- this.timeout = options.timeout || 30000;
28
- this.priority = 11;
29
- this.client = null;
30
- this.connected = false;
31
- this.tools = [];
32
- }
33
-
34
- /**
35
- * 连接 MCP 服务器
36
- */
37
- async connect() {
38
- if (this.connected) return;
39
-
40
- try {
41
- // 动态导入 @ai-sdk/mcp
42
- let createMCPClient;
43
-
44
- try {
45
- const mcpModule = require('@ai-sdk/mcp');
46
- createMCPClient = mcpModule.createMCPClient || mcpModule.experimental_createMCPClient;
47
- } catch (err) {
48
- throw new Error('@ai-sdk/mcp not installed. Please run: npm install @ai-sdk/mcp');
49
- }
50
-
51
- if (!createMCPClient) {
52
- throw new Error('createMCPClient not available');
53
- }
54
-
55
- // 解析命令路径
56
- const { which, shellResolve } = this._resolveCommand(this.command);
57
-
58
- // 使用正确的子路径导入
59
- try {
60
- const { Experimental_StdioMCPTransport } = require('@ai-sdk/mcp/mcp-stdio');
61
- const transport = new Experimental_StdioMCPTransport({
62
- command: which || this.command,
63
- args: shellResolve ? [shellResolve, ...this.args] : this.args,
64
- env: { ...process.env, ...this.env },
65
- });
66
- this.client = await createMCPClient({ transport });
67
- } catch (transportErr) {
68
- log.error(` Transport error:`, transportErr.message);
69
- throw transportErr;
70
- }
71
-
72
- // 获取工具列表
73
- // 新版 API: tools() 是函数
74
- if (typeof this.client.tools === 'function') {
75
- const toolsObject = await this.client.tools();
76
- this.tools = Object.entries(toolsObject)
77
- .filter(([, tool]) => tool != null)
78
- .map(([name, tool]) => ({ ...tool, name }));
79
- } else if (this.client.tools && typeof this.client.tools.list === 'function') {
80
- // 旧版 API: tools.list()
81
- const listResult = await this.client.tools.list();
82
- this.tools = listResult.tools || [];
83
- }
84
-
85
- this.connected = true;
86
- log.info(` Connected to ${this.serverName} with ${this.tools.length} tools`);
87
- } catch (err) {
88
- log.error(` Failed to connect to ${this.serverName}:`, err.message);
89
- // 不抛出错误,让框架继续运行
90
- }
91
- }
92
-
93
- /**
94
- * 解析命令路径
95
- */
96
- _resolveCommand(cmd) {
97
- const isWindows = process.platform === 'win32';
98
- const ext = isWindows ? '.cmd' : '';
99
- const withExt = cmd + ext;
100
-
101
- // 检查命令是否直接存在
102
- try {
103
- const result = spawn.sync(withExt, ['--version'], {
104
- stdio: 'ignore',
105
- timeout: 2000,
106
- });
107
- if (result.error?.code !== 'ENOENT') {
108
- return { which: withExt, shellResolve: null };
109
- }
110
- } catch (e) {
111
- // ignore
112
- }
113
-
114
- // 尝试解析 npm 全局 bin 目录
115
- const globalCommands = isWindows
116
- ? [
117
- path.join(process.env.APPDATA || '', 'npm', withExt),
118
- path.join(process.env.PROGRAMDATA || 'C:\\ProgramData', 'npm', withExt),
119
- ]
120
- : ['/usr/local/bin/' + cmd, '/usr/bin/' + cmd];
121
-
122
- for (const cmdPath of globalCommands) {
123
- try {
124
- if (fs.existsSync(cmdPath)) {
125
- return { which: cmdPath, shellResolve: null };
126
- }
127
- } catch (e) {
128
- // ignore
129
- }
130
- }
131
-
132
- return { which: cmd, shellResolve: null };
133
- }
134
-
135
- /**
136
- * 调用工具
137
- */
138
- async callTool(toolName, args) {
139
- if (!this.connected) {
140
- await this.connect();
141
- }
142
-
143
- if (!this.client) {
144
- throw new Error('MCP client not initialized');
145
- }
146
-
147
- try {
148
- // 新版 API: tools() 返回对象
149
- let toolsObject;
150
- if (typeof this.client.tools === 'function') {
151
- toolsObject = await this.client.tools();
152
- } else if (this.client.tools && typeof this.client.tools.list === 'function') {
153
- // 旧版 API
154
- const listResult = await this.client.tools.list();
155
- toolsObject = listResult.tools || {};
156
- } else {
157
- throw new Error('Cannot get tools from MCP client');
158
- }
159
-
160
- const tool = toolsObject[toolName];
161
- if (!tool) {
162
- throw new Error(`Tool not found: ${toolName}`);
163
- }
164
-
165
- const result = await tool.execute(args);
166
- return result;
167
- } catch (err) {
168
- log.error(` Tool call failed: ${toolName}:`, err.message);
169
- throw err;
170
- }
171
- }
172
-
173
- /**
174
- * 断开连接
175
- */
176
- async disconnect() {
177
- if (this.client) {
178
- try {
179
- await this.client.destroy();
180
- } catch (e) {
181
- // ignore
182
- }
183
- this.client = null;
184
- this.connected = false;
185
- this.tools = [];
186
- }
187
- }
188
- }
189
-
190
- /**
191
- * MCPExecutorPlugin
192
- */
193
- class MCPExecutorPlugin extends Plugin {
194
- constructor(config = {}) {
195
- super();
196
- this.name = 'mcp-executor';
197
- this.version = '1.0.0';
198
- this.description = 'MCP (Model Context Protocol) 执行器';
199
- this.priority = 11;
200
- this.system = true;
201
-
202
- this._framework = null;
203
- // serverName -> { client, tools: [{ name, description, inputSchema }], toolObjects: { toolName: actualTool } }
204
- this._clients = new Map();
205
- this._config = config;
206
- }
207
-
208
- install(framework) {
209
- this._framework = framework;
210
- return this;
211
- }
212
-
213
- async start(framework) {
214
- // 先连接所有 MCP 服务器
215
- if (this._config.servers) {
216
- for (const serverConfig of this._config.servers) {
217
- await this.addServer(serverConfig);
218
- }
219
- }
220
-
221
- // 注册 mcp_tool_schema 工具 - 直接返回可用的调用示例
222
- framework.registerTool({
223
- name: 'mcp_tool_schema',
224
- description: '查询工具的调用示例,直接复制 example 或 fullExample 字段使用',
225
- inputSchema: z.object({
226
- server: z.string().describe('MCP 服务器名称'),
227
- tool: z.string().describe('工具名称'),
228
- }),
229
- execute: async (args) => {
230
- const { server, tool } = args;
231
- const clientInfo = this._clients.get(server);
232
- if (!clientInfo) {
233
- return { success: false, error: `MCP server '${server}' not found` };
234
- }
235
-
236
- const toolInfo = clientInfo.tools.find((t) => t.name === tool);
237
- if (!toolInfo) {
238
- return { success: false, error: `Tool '${tool}' not found` };
239
- }
240
-
241
- const schema = this._extractSchema(toolInfo.inputSchema);
242
- const props = schema.properties || {};
243
- const required = schema.required || [];
244
-
245
- // 生成示例值的辅助函数
246
- const generateExample = (prop, depth = 0) => {
247
- if (!prop) return null;
248
- const type = prop.type || (prop.properties ? 'object' : 'string');
249
-
250
- if (type === 'string') {
251
- return prop.description?.split('.')[0] || `${prop.title || '值'}`;
252
- } else if (type === 'number' || type === 'integer') {
253
- return prop.default || 0;
254
- } else if (type === 'boolean') {
255
- return false;
256
- } else if (type === 'array') {
257
- // 数组类型:提供单个示例元素
258
- if (prop.items) {
259
- const itemExample = generateExample(prop.items, depth + 1);
260
- return depth === 0 ? [itemExample] : itemExample;
261
- }
262
- return ['示例'];
263
- } else if (type === 'object' || prop.properties) {
264
- // 对象类型:递归生成嵌套结构
265
- const obj = {};
266
- const objProps = prop.properties || {};
267
- for (const [key, val] of Object.entries(objProps)) {
268
- obj[key] = generateExample(val, depth + 1);
269
- }
270
- return obj;
271
- }
272
- return null;
273
- };
274
-
275
- // 生成可直接使用的调用示例
276
- const exampleArgs = {};
277
- for (const key of required) {
278
- const prop = props[key];
279
- if (prop) {
280
- exampleArgs[key] = generateExample(prop);
281
- }
282
- }
283
-
284
- return {
285
- success: true,
286
- result: {
287
- name: toolInfo.name,
288
- description: toolInfo.description,
289
- required,
290
- // 各参数的详细说明
291
- parameters: Object.fromEntries(
292
- required.map((key) => [
293
- key,
294
- {
295
- type: props[key]?.type || 'string',
296
- description: props[key]?.description || '',
297
- example: generateExample(props[key]),
298
- },
299
- ])
300
- ),
301
- // 直接返回可以复制使用的调用示例
302
- example: JSON.stringify(exampleArgs, null, 2),
303
- fullExample: `mcp_call({ server: "${server}", tool: "${tool}", args_json: ${JSON.stringify(JSON.stringify(exampleArgs))} })`,
304
- },
305
- };
306
- },
307
- });
308
-
309
- // 注册 mcp_call 工具
310
- framework.registerTool({
311
- name: 'mcp_call',
312
- description:
313
- '调用 MCP 服务器工具。args_json 是包含实际参数的 JSON 字符串,如 \'{"url": "https://news.baidu.com"}\'',
314
- inputSchema: z.object({
315
- server: z.string().describe('MCP 服务器名称'),
316
- tool: z.string().describe('工具名称'),
317
- args_json: z
318
- .string()
319
- .describe('参数 JSON 字符串,如 {"url": "https://..."},fetch 工具必填 url'),
320
- }),
321
- execute: async (args) => {
322
- const { server, tool, args_json } = args;
323
- log.info(` mcp_call: server=${server}, tool=${tool}, args_json=${args_json}`);
324
-
325
- const clientInfo = this._clients.get(server);
326
- if (!clientInfo) {
327
- return { success: false, error: `MCP server '${server}' not found` };
328
- }
329
-
330
- // 解析 args_json
331
- let finalArgs = {};
332
- if (args_json) {
333
- try {
334
- finalArgs = JSON.parse(args_json);
335
- } catch (e) {
336
- return { success: false, error: `args_json 格式错误,不是有效的 JSON: ${args_json}` };
337
- }
338
- }
339
-
340
- // 检查参数是否为空
341
- if (!finalArgs || Object.keys(finalArgs).length === 0) {
342
- const toolInfo = clientInfo.tools.find((t) => t.name === tool);
343
- if (toolInfo) {
344
- const schema = this._extractSchema(toolInfo.inputSchema);
345
- const required = schema.required || [];
346
- return {
347
- success: false,
348
- error: `参数为空!必须提供: ${required.join(', ')}`,
349
- hint: `正确格式: {"${required[0]}": "具体值"}`,
350
- };
351
- }
352
- }
353
-
354
- try {
355
- // 使用缓存的工具对象
356
- const mcpTool = clientInfo.toolObjects?.[tool];
357
- if (!mcpTool) {
358
- return { success: false, error: `Tool '${tool}' not found on server '${server}'` };
359
- }
360
- const execResult = await mcpTool.execute(finalArgs);
361
- return { success: true, result: execResult };
362
- } catch (err) {
363
- log.error(` Tool '${tool}' failed:`, err.message);
364
- return { success: false, error: err.message };
365
- }
366
- },
367
- });
368
-
369
- // 注册 mcp_list_servers 工具
370
- framework.registerTool({
371
- name: 'mcp_list_servers',
372
- description: '列出已连接的 MCP 服务器及其工具',
373
- inputSchema: z.object({}),
374
- execute: async () => {
375
- const servers = [];
376
- for (const [name, info] of this._clients) {
377
- servers.push({
378
- name,
379
- tools: info.tools.map((t) => ({ name: t.name, description: t.description })),
380
- });
381
- }
382
- return { success: true, servers };
383
- },
384
- });
385
-
386
- // 注册 mcp_reload 工具
387
- framework.registerTool({
388
- name: 'mcp_reload',
389
- description:
390
- '重新加载 MCP 服务器配置。当配置文件 .agent/mcp_config.json 修改后使用此工具重载配置',
391
- inputSchema: z.object({}),
392
- execute: async () => {
393
- log.info(' mcp_reload called');
394
- try {
395
- const fs = require('fs');
396
- const path = require('path');
397
- const configPath = path.resolve('.agent/mcp_config.json');
398
-
399
- log.info(' Reading config from:', configPath);
400
- if (!fs.existsSync(configPath)) {
401
- return { success: false, error: `配置文件不存在: ${configPath}` };
402
- }
403
-
404
- const configContent = fs.readFileSync(configPath, 'utf8');
405
- const config = JSON.parse(configContent);
406
- log.info(' Config loaded, reloading...');
407
-
408
- await this.reloadConfig(config);
409
- log.info(' Reload complete');
410
-
411
- return {
412
- success: true,
413
- message: 'MCP 配置已重载',
414
- servers: Object.keys(config.mcpServers || {}),
415
- };
416
- } catch (err) {
417
- log.error(' Reload error:', err);
418
- return { success: false, error: `重载失败: ${err.message}` };
419
- }
420
- },
421
- });
422
-
423
- // 监听 agent 创建事件,附加 MCP 信息到系统提示词
424
- framework.on('agent:created', (agent) => {
425
- if (!this._mainAgent) {
426
- this._mainAgent = agent;
427
- }
428
- this._refreshAgentMCPPrompt(agent);
429
- });
430
-
431
- // 等待框架就绪后,刷新所有已有 agent 的 MCP 提示词
432
- if (framework._ready) {
433
- this._refreshAllAgentsMCPPrompt(framework);
434
- } else {
435
- framework.once('framework:ready', () => {
436
- this._refreshAllAgentsMCPPrompt(framework);
437
- });
438
- }
439
-
440
- return this;
441
- }
442
-
443
- /**
444
- * 刷新所有 agent 的 MCP 提示词(包括子 agent)
445
- */
446
- _refreshAllAgentsMCPPrompt(framework) {
447
- const visited = new Set();
448
-
449
- const traverse = (agent) => {
450
- if (!agent || visited.has(agent)) return;
451
- visited.add(agent);
452
- this._refreshAgentMCPPrompt(agent);
453
-
454
- // 递归处理子 agent
455
- const subAgents = agent.getSubAgents?.() || agent._subAgents || new Map();
456
- for (const [name, subAgentInfo] of subAgents) {
457
- traverse(subAgentInfo.agent);
458
- }
459
- };
460
-
461
- for (const agent of framework._agents || []) {
462
- traverse(agent);
463
- }
464
- }
465
-
466
- /**
467
- * 刷新单个 agent 的 MCP 提示词
468
- */
469
- _refreshAgentMCPPrompt(agent) {
470
- // 检查是否已刷新过(通过检查系统提示词是否已包含 MCP 描述)
471
- const existingPrompt = agent._originalPrompt || '';
472
- if (existingPrompt.includes('[MCP Servers]')) {
473
- return;
474
- }
475
-
476
- const mcpDesc = this._buildMCPServersDescription();
477
- if (!mcpDesc) return;
478
-
479
- // 将 MCP 描述追加到系统提示词
480
- agent.setSystemPrompt(existingPrompt + '\n\n' + mcpDesc);
481
- }
482
-
483
- /**
484
- * 从 inputSchema 中提取实际 schema
485
- */
486
- _extractSchema(inputSchema) {
487
- if (!inputSchema) return {};
488
- // 处理 {jsonSchema: {...}} 格式
489
- if (inputSchema.jsonSchema) return inputSchema.jsonSchema;
490
- // 处理直接是 schema 的格式
491
- return inputSchema;
492
- }
493
-
494
- /**
495
- * 构建 MCP 服务器描述
496
- */
497
- _buildMCPServersDescription() {
498
- if (this._clients.size === 0) {
499
- return '';
500
- }
501
-
502
- let desc = '[MCP Servers]\n\n';
503
- desc += '你可以通过 `mcp_call` 工具调用以下 MCP 服务器的工具。\n\n';
504
-
505
- for (const [serverName, info] of this._clients) {
506
- desc += `### ${serverName}\n\n`;
507
- for (const tool of info.tools) {
508
- desc += `#### ${tool.name}\n`;
509
- desc += `${tool.description || '无描述'}\n\n`;
510
-
511
- // 使用 zodSchemaToMarkdown 生成参数文档
512
- const schema = tool.inputSchema;
513
- if (schema) {
514
- try {
515
- if (typeof schema.toJSON === 'function') {
516
- // Zod schema 直接转换
517
- desc += `**参数:**\n\n`;
518
- desc += zodSchemaToMarkdown(schema) + '\n\n';
519
- } else if (schema.jsonSchema) {
520
- // JSON Schema 格式,尝试转换为 Zod schema
521
- const zodSchema = this._jsonSchemaToZod(schema.jsonSchema || schema);
522
- if (zodSchema) {
523
- desc += `**参数:**\n\n`;
524
- desc += zodSchemaToMarkdown(zodSchema) + '\n\n';
525
- }
526
- } else if (schema.properties) {
527
- // 已经是对象格式
528
- const zodSchema = this._jsonSchemaToZod(schema);
529
- if (zodSchema) {
530
- desc += `**参数:**\n\n`;
531
- desc += zodSchemaToMarkdown(zodSchema) + '\n\n';
532
- }
533
- }
534
- } catch (e) {
535
- // 如果转换失败,使用简化格式
536
- desc += this._fallbackParamsDesc(schema);
537
- }
538
- }
539
- }
540
- desc += '---\n\n';
541
- }
542
-
543
- desc += '**调用格式:**\n';
544
- desc += '```\nmcp_call({ server: "服务器", tool: "工具", args_json: \'{"参数": "值"}\' })\n';
545
- desc += '```\n';
546
- return desc.trim();
547
- }
548
-
549
- /**
550
- * 将 JSON Schema 转换为 Zod schema
551
- */
552
- _jsonSchemaToZod(jsonSchema) {
553
- if (!jsonSchema || !jsonSchema.properties) {
554
- return null;
555
- }
556
-
557
- try {
558
- const shape = {};
559
- const properties = jsonSchema.properties;
560
- const required = jsonSchema.required || [];
561
-
562
- for (const [key, prop] of Object.entries(properties)) {
563
- shape[key] = this._jsonSchemaPropToZod(prop, required.includes(key));
564
- }
565
-
566
- return z.object(shape);
567
- } catch (e) {
568
- return null;
569
- }
570
- }
571
-
572
- /**
573
- * 将 JSON Schema 属性转换为 Zod 类型
574
- */
575
- _jsonSchemaPropToZod(prop, isRequired) {
576
- if (prop.$ref) {
577
- return isRequired ? z.any() : z.any().optional();
578
- }
579
-
580
- if (prop.enum) {
581
- let zodType = z.string().enum(prop.enum);
582
- if (prop.nullable) {
583
- zodType = zodType.nullable();
584
- }
585
- return isRequired ? zodType : zodType.optional();
586
- }
587
-
588
- const type = prop.type || 'string';
589
-
590
- switch (type) {
591
- case 'string':
592
- return isRequired ? z.string() : z.string().optional();
593
- case 'number':
594
- case 'integer':
595
- return isRequired ? z.number() : z.number().optional();
596
- case 'boolean':
597
- return isRequired ? z.boolean() : z.boolean().optional();
598
- case 'array':
599
- return isRequired ? z.array(z.any()) : z.array(z.any()).optional();
600
- case 'object':
601
- if (prop.properties) {
602
- const nested = {};
603
- for (const [k, v] of Object.entries(prop.properties)) {
604
- nested[k] = this._jsonSchemaPropToZod(v, prop.required?.includes(k) || false);
605
- }
606
- return isRequired ? z.object(nested) : z.object(nested).optional();
607
- }
608
- return isRequired ? z.record(z.any()) : z.record(z.any()).optional();
609
- default:
610
- return isRequired ? z.any() : z.any().optional();
611
- }
612
- }
613
-
614
- /**
615
- * 回退的参数描述(当 zodSchemaToMarkdown 失败时)
616
- */
617
- _fallbackParamsDesc(schema) {
618
- const props = schema.properties || {};
619
- const required = schema.required || [];
620
-
621
- if (Object.keys(props).length === 0) {
622
- return '';
623
- }
624
-
625
- let desc = '**参数:**\n\n';
626
- for (const [key, prop] of Object.entries(props)) {
627
- const isRequired = required.includes(key);
628
- const type = prop.type || 'any';
629
- const desc_text = prop.description || '';
630
- desc += `- \`${key}\`${isRequired ? ' (必填)' : ''}: ${type} ${desc_text}\n`;
631
- }
632
- return desc + '\n';
633
- }
634
-
635
- _getParentAgent() {
636
- // 优先返回已缓存的主 agent
637
- if (this._mainAgent) {
638
- return this._mainAgent;
639
- }
640
-
641
- // 尝试从 framework 获取
642
- if (this._framework._mainAgent) {
643
- this._mainAgent = this._framework._mainAgent;
644
- return this._mainAgent;
645
- }
646
-
647
- // 查找最后一个创建的 agent 作为父 agent
648
- const agents = this._framework._agents || [];
649
- if (agents.length > 0) {
650
- this._mainAgent = agents[agents.length - 1];
651
- return this._mainAgent;
652
- }
653
-
654
- return null;
655
- }
656
-
657
- /**
658
- * 带超时的 Promise
659
- */
660
- _withTimeout(promise, ms, name) {
661
- return Promise.race([
662
- promise,
663
- new Promise((_, reject) =>
664
- setTimeout(() => reject(new Error(`${name} timeout (${ms}ms)`)), ms)
665
- ),
666
- ]);
667
- }
668
-
669
- /**
670
- * 添加 MCP 服务器
671
- */
672
- async addServer(config) {
673
- const { name, command, args = [], env, url, type, headers = {}, authProvider } = config;
674
- if (this._clients.has(name)) {
675
- log.warn(` Server '${name}' already exists, skipping`);
676
- return;
677
- }
678
- log.info(` Connecting to ${name}...`);
679
- let client;
680
- try {
681
- if (['sse', 'http'].includes(type)) {
682
- client = await this._withTimeout(
683
- createMCPClient({
684
- transport: {
685
- type: type,
686
- url: url,
687
- headers: headers,
688
- authProvider: authProvider,
689
- redirect: 'error',
690
- env: env,
691
- },
692
- }),
693
- 10000,
694
- `createMCPClient ${name}`
695
- );
696
- } else {
697
- client = await this._withTimeout(
698
- createMCPClient({
699
- transport: new StdioClientTransport({
700
- command: command,
701
- args: args,
702
- env: env,
703
- }),
704
- }),
705
- 10000,
706
- `createMCPClient ${name}`
707
- );
708
- }
709
- } catch (err) {
710
- log.error(` Failed to create client '${name}':`, err.message);
711
- return;
712
- }
713
-
714
- // 获取 MCP 服务器的工具列表
715
- let mcpTools;
716
- try {
717
- mcpTools = await this._withTimeout(client.tools(), 10000, `client.tools ${name}`);
718
- } catch (err) {
719
- log.error(` Failed to get tools from '${name}':`, err.message);
720
- return;
721
- }
722
-
723
- // 提取工具信息和实际工具对象
724
- const toolsInfo = [];
725
- const toolObjects = {}; // 缓存实际工具对象
726
- if (mcpTools && typeof mcpTools === 'object') {
727
- for (const [toolName, tool] of Object.entries(mcpTools)) {
728
- if (!tool) continue;
729
- toolObjects[toolName] = tool; // 保存实际工具对象
730
- toolsInfo.push({
731
- name: toolName,
732
- description: tool.description || '',
733
- inputSchema: tool.inputSchema || {},
734
- });
735
- }
736
- }
737
-
738
- // 保存客户端、工具信息和实际工具对象
739
- this._clients.set(name, {
740
- client,
741
- tools: toolsInfo,
742
- toolObjects, // 实际工具对象,供 mcp_call 直接调用
743
- });
744
-
745
- log.info(` Added server '${name}' with ${toolsInfo.length} tools`);
746
- }
747
-
748
- /**
749
- * 移除 MCP 服务器
750
- */
751
- async removeServer(name) {
752
- const clientInfo = this._clients.get(name);
753
- if (clientInfo) {
754
- try {
755
- // 添加超时保护,避免 close/destroy 阻塞
756
- const closePromise = clientInfo.client.close?.() || clientInfo.client.destroy?.();
757
- if (closePromise && typeof closePromise.then === 'function') {
758
- await Promise.race([
759
- closePromise,
760
- new Promise((resolve) => setTimeout(() => resolve(), 3000)),
761
- ]);
762
- }
763
- } catch (e) {
764
- // ignore
765
- }
766
- this._clients.delete(name);
767
- }
768
- }
769
-
770
- /**
771
- * 获取所有服务器
772
- */
773
- getServers() {
774
- return Array.from(this._clients.entries()).map(([name, info]) => ({
775
- name,
776
- tools: info.tools.map((t) => t.name),
777
- }));
778
- }
779
-
780
- reload(framework) {
781
- log.info(' Reloading...');
782
- this._framework = framework;
783
- }
784
-
785
- /**
786
- * 重新加载 MCP 配置
787
- * @param {Object} config - 新的配置对象,包含 mcpServers
788
- */
789
- async reloadConfig(config) {
790
- log.info(' reloadConfig start');
791
- const newServers = config?.mcpServers || {};
792
-
793
- // 1. 先快速清理所有旧服务器(不等待 close 完成)
794
- log.info(' Clearing old servers...');
795
- for (const [name, clientInfo] of this._clients) {
796
- try {
797
- // 不等待 close,直接删除
798
- clientInfo.client.close?.()?.catch?.(() => {});
799
- } catch (e) {}
800
- }
801
- this._clients.clear();
802
-
803
- // 2. 并行添加新服务器
804
- log.info(' Adding new servers...');
805
- const addPromises = Object.entries(newServers).map(async ([name, serverConfig]) => {
806
- const normalizedConfig = {
807
- name,
808
- type: serverConfig.type,
809
- url: serverConfig.url,
810
- headers: serverConfig.headers,
811
- authProvider: serverConfig.authProvider,
812
- command: serverConfig.command,
813
- args: serverConfig.args,
814
- env: serverConfig.env,
815
- };
816
- await this.addServer(normalizedConfig);
817
- });
818
-
819
- await Promise.all(addPromises);
820
- log.info(' reloadConfig complete');
821
-
822
- // 3. 刷新所有 agent 的 MCP 提示词
823
- if (this._framework) {
824
- this._refreshAllAgentsMCPPrompt(this._framework);
825
- }
826
- }
827
-
828
- async uninstall(framework) {
829
- // 断开所有 MCP 连接
830
- for (const [name, clientInfo] of this._clients) {
831
- try {
832
- (await clientInfo.client.close?.()) || clientInfo.client.destroy?.();
833
- } catch (e) {
834
- // ignore
835
- }
836
- }
837
- this._clients.clear();
838
- this._framework = null;
839
- }
840
- }
841
-
842
- module.exports = {
843
- MCPExecutorPlugin,
844
- MCPClientWrapper,
845
- };
1
+ /**
2
+ * MCPExecutor MCP 执行器
3
+ * 使用 @ai-sdk/mcp 连接到 MCP 服务器
4
+ */
5
+
6
+ const { Plugin } = require('../core/plugin-base');
7
+ const { spawn } = require('child_process');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { z } = require('zod');
11
+ const { createMCPClient } = require('@ai-sdk/mcp');
12
+ const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js');
13
+ const { zodSchemaToMarkdown } = require('@chnak/zod-to-markdown');
14
+ const { logger } = require('../utils/logger');
15
+
16
+ const log = logger.child('MCPExecutor');
17
+
18
+ /**
19
+ * MCP 客户端包装器
20
+ */
21
+ class MCPClientWrapper {
22
+ constructor(options = {}) {
23
+ this.serverName = options.serverName || 'unknown';
24
+ this.command = options.command;
25
+ this.args = options.args || [];
26
+ this.env = options.env || {};
27
+ this.timeout = options.timeout || 30000;
28
+ this.priority = 11;
29
+ this.client = null;
30
+ this.connected = false;
31
+ this.tools = [];
32
+ }
33
+
34
+ /**
35
+ * 连接 MCP 服务器
36
+ */
37
+ async connect() {
38
+ if (this.connected) return;
39
+
40
+ try {
41
+ // 动态导入 @ai-sdk/mcp
42
+ let createMCPClient;
43
+
44
+ try {
45
+ const mcpModule = require('@ai-sdk/mcp');
46
+ createMCPClient = mcpModule.createMCPClient || mcpModule.experimental_createMCPClient;
47
+ } catch (err) {
48
+ throw new Error('@ai-sdk/mcp not installed. Please run: npm install @ai-sdk/mcp');
49
+ }
50
+
51
+ if (!createMCPClient) {
52
+ throw new Error('createMCPClient not available');
53
+ }
54
+
55
+ // 解析命令路径
56
+ const { which, shellResolve } = this._resolveCommand(this.command);
57
+
58
+ // 使用正确的子路径导入
59
+ try {
60
+ const { Experimental_StdioMCPTransport } = require('@ai-sdk/mcp/mcp-stdio');
61
+ const transport = new Experimental_StdioMCPTransport({
62
+ command: which || this.command,
63
+ args: shellResolve ? [shellResolve, ...this.args] : this.args,
64
+ env: { ...process.env, ...this.env },
65
+ });
66
+ this.client = await createMCPClient({ transport });
67
+ } catch (transportErr) {
68
+ log.error(` Transport error:`, transportErr.message);
69
+ throw transportErr;
70
+ }
71
+
72
+ // 获取工具列表
73
+ // 新版 API: tools() 是函数
74
+ if (typeof this.client.tools === 'function') {
75
+ const toolsObject = await this.client.tools();
76
+ this.tools = Object.entries(toolsObject)
77
+ .filter(([, tool]) => tool != null)
78
+ .map(([name, tool]) => ({ ...tool, name }));
79
+ } else if (this.client.tools && typeof this.client.tools.list === 'function') {
80
+ // 旧版 API: tools.list()
81
+ const listResult = await this.client.tools.list();
82
+ this.tools = listResult.tools || [];
83
+ }
84
+
85
+ this.connected = true;
86
+ log.info(` Connected to ${this.serverName} with ${this.tools.length} tools`);
87
+ } catch (err) {
88
+ log.error(` Failed to connect to ${this.serverName}:`, err.message);
89
+ // 不抛出错误,让框架继续运行
90
+ }
91
+ }
92
+
93
+ /**
94
+ * 解析命令路径
95
+ */
96
+ _resolveCommand(cmd) {
97
+ const isWindows = process.platform === 'win32';
98
+ const ext = isWindows ? '.cmd' : '';
99
+ const withExt = cmd + ext;
100
+
101
+ // 检查命令是否直接存在
102
+ try {
103
+ const result = spawn.sync(withExt, ['--version'], {
104
+ stdio: 'ignore',
105
+ timeout: 2000,
106
+ });
107
+ if (result.error?.code !== 'ENOENT') {
108
+ return { which: withExt, shellResolve: null };
109
+ }
110
+ } catch (e) {
111
+ // ignore
112
+ }
113
+
114
+ // 尝试解析 npm 全局 bin 目录
115
+ const globalCommands = isWindows
116
+ ? [
117
+ path.join(process.env.APPDATA || '', 'npm', withExt),
118
+ path.join(process.env.PROGRAMDATA || 'C:\\ProgramData', 'npm', withExt),
119
+ ]
120
+ : ['/usr/local/bin/' + cmd, '/usr/bin/' + cmd];
121
+
122
+ for (const cmdPath of globalCommands) {
123
+ try {
124
+ if (fs.existsSync(cmdPath)) {
125
+ return { which: cmdPath, shellResolve: null };
126
+ }
127
+ } catch (e) {
128
+ // ignore
129
+ }
130
+ }
131
+
132
+ return { which: cmd, shellResolve: null };
133
+ }
134
+
135
+ /**
136
+ * 调用工具
137
+ */
138
+ async callTool(toolName, args) {
139
+ if (!this.connected) {
140
+ await this.connect();
141
+ }
142
+
143
+ if (!this.client) {
144
+ throw new Error('MCP client not initialized');
145
+ }
146
+
147
+ try {
148
+ // 新版 API: tools() 返回对象
149
+ let toolsObject;
150
+ if (typeof this.client.tools === 'function') {
151
+ toolsObject = await this.client.tools();
152
+ } else if (this.client.tools && typeof this.client.tools.list === 'function') {
153
+ // 旧版 API
154
+ const listResult = await this.client.tools.list();
155
+ toolsObject = listResult.tools || {};
156
+ } else {
157
+ throw new Error('Cannot get tools from MCP client');
158
+ }
159
+
160
+ const tool = toolsObject[toolName];
161
+ if (!tool) {
162
+ throw new Error(`Tool not found: ${toolName}`);
163
+ }
164
+
165
+ const result = await tool.execute(args);
166
+ return result;
167
+ } catch (err) {
168
+ log.error(` Tool call failed: ${toolName}:`, err.message);
169
+ throw err;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * 断开连接
175
+ */
176
+ async disconnect() {
177
+ if (this.client) {
178
+ try {
179
+ await this.client.destroy();
180
+ } catch (e) {
181
+ // ignore
182
+ }
183
+ this.client = null;
184
+ this.connected = false;
185
+ this.tools = [];
186
+ }
187
+ }
188
+ }
189
+
190
+ /**
191
+ * MCPExecutorPlugin
192
+ */
193
+ class MCPExecutorPlugin extends Plugin {
194
+ constructor(config = {}) {
195
+ super();
196
+ this.name = 'mcp-executor';
197
+ this.version = '1.0.0';
198
+ this.description = 'MCP (Model Context Protocol) 执行器';
199
+ this.priority = 11;
200
+ this.system = true;
201
+
202
+ this._framework = null;
203
+ // serverName -> { client, tools: [{ name, description, inputSchema }], toolObjects: { toolName: actualTool } }
204
+ this._clients = new Map();
205
+ this._config = config;
206
+ }
207
+
208
+ install(framework) {
209
+ this._framework = framework;
210
+ return this;
211
+ }
212
+
213
+ async start(framework) {
214
+ // 先连接所有 MCP 服务器
215
+ if (this._config.servers) {
216
+ for (const serverConfig of this._config.servers) {
217
+ await this.addServer(serverConfig);
218
+ }
219
+ }
220
+
221
+ // 注册 mcp_tool_schema 工具 - 直接返回可用的调用示例
222
+ framework.registerTool({
223
+ name: 'mcp_tool_schema',
224
+ description: '查询工具的调用示例,直接复制 example 或 fullExample 字段使用',
225
+ inputSchema: z.object({
226
+ server: z.string().describe('MCP 服务器名称'),
227
+ tool: z.string().describe('工具名称'),
228
+ }),
229
+ execute: async (args) => {
230
+ const { server, tool } = args;
231
+ const clientInfo = this._clients.get(server);
232
+ if (!clientInfo) {
233
+ return { success: false, error: `MCP server '${server}' not found` };
234
+ }
235
+
236
+ const toolInfo = clientInfo.tools.find((t) => t.name === tool);
237
+ if (!toolInfo) {
238
+ return { success: false, error: `Tool '${tool}' not found` };
239
+ }
240
+
241
+ const schema = this._extractSchema(toolInfo.inputSchema);
242
+ const props = schema.properties || {};
243
+ const required = schema.required || [];
244
+
245
+ // 生成示例值的辅助函数
246
+ const generateExample = (prop, depth = 0) => {
247
+ if (!prop) return null;
248
+ const type = prop.type || (prop.properties ? 'object' : 'string');
249
+
250
+ if (type === 'string') {
251
+ return prop.description?.split('.')[0] || `${prop.title || '值'}`;
252
+ } else if (type === 'number' || type === 'integer') {
253
+ return prop.default || 0;
254
+ } else if (type === 'boolean') {
255
+ return false;
256
+ } else if (type === 'array') {
257
+ // 数组类型:提供单个示例元素
258
+ if (prop.items) {
259
+ const itemExample = generateExample(prop.items, depth + 1);
260
+ return depth === 0 ? [itemExample] : itemExample;
261
+ }
262
+ return ['示例'];
263
+ } else if (type === 'object' || prop.properties) {
264
+ // 对象类型:递归生成嵌套结构
265
+ const obj = {};
266
+ const objProps = prop.properties || {};
267
+ for (const [key, val] of Object.entries(objProps)) {
268
+ obj[key] = generateExample(val, depth + 1);
269
+ }
270
+ return obj;
271
+ }
272
+ return null;
273
+ };
274
+
275
+ // 生成可直接使用的调用示例
276
+ const exampleArgs = {};
277
+ for (const key of required) {
278
+ const prop = props[key];
279
+ if (prop) {
280
+ exampleArgs[key] = generateExample(prop);
281
+ }
282
+ }
283
+
284
+ return {
285
+ success: true,
286
+ result: {
287
+ name: toolInfo.name,
288
+ description: toolInfo.description,
289
+ required,
290
+ // 各参数的详细说明
291
+ parameters: Object.fromEntries(
292
+ required.map((key) => [
293
+ key,
294
+ {
295
+ type: props[key]?.type || 'string',
296
+ description: props[key]?.description || '',
297
+ example: generateExample(props[key]),
298
+ },
299
+ ])
300
+ ),
301
+ // 直接返回可以复制使用的调用示例
302
+ example: JSON.stringify(exampleArgs, null, 2),
303
+ fullExample: `mcp_call({ server: "${server}", tool: "${tool}", args_json: ${JSON.stringify(JSON.stringify(exampleArgs))} })`,
304
+ },
305
+ };
306
+ },
307
+ });
308
+
309
+ // 注册 mcp_call 工具
310
+ framework.registerTool({
311
+ name: 'mcp_call',
312
+ description:
313
+ '调用 MCP 服务器工具。args_json 是包含实际参数的 JSON 字符串,如 \'{"url": "https://news.baidu.com"}\'',
314
+ inputSchema: z.object({
315
+ server: z.string().describe('MCP 服务器名称'),
316
+ tool: z.string().describe('工具名称'),
317
+ args_json: z
318
+ .string()
319
+ .describe('参数 JSON 字符串,如 {"url": "https://..."},fetch 工具必填 url'),
320
+ }),
321
+ execute: async (args) => {
322
+ const { server, tool, args_json } = args;
323
+ log.info(` mcp_call: server=${server}, tool=${tool}, args_json=${args_json}`);
324
+
325
+ const clientInfo = this._clients.get(server);
326
+ if (!clientInfo) {
327
+ return { success: false, error: `MCP server '${server}' not found` };
328
+ }
329
+
330
+ // 解析 args_json
331
+ let finalArgs = {};
332
+ if (args_json) {
333
+ try {
334
+ finalArgs = JSON.parse(args_json);
335
+ } catch (e) {
336
+ return { success: false, error: `args_json 格式错误,不是有效的 JSON: ${args_json}` };
337
+ }
338
+ }
339
+
340
+ // 检查参数是否为空
341
+ if (!finalArgs || Object.keys(finalArgs).length === 0) {
342
+ const toolInfo = clientInfo.tools.find((t) => t.name === tool);
343
+ if (toolInfo) {
344
+ const schema = this._extractSchema(toolInfo.inputSchema);
345
+ const required = schema.required || [];
346
+ return {
347
+ success: false,
348
+ error: `参数为空!必须提供: ${required.join(', ')}`,
349
+ hint: `正确格式: {"${required[0]}": "具体值"}`,
350
+ };
351
+ }
352
+ }
353
+
354
+ try {
355
+ // 使用缓存的工具对象
356
+ const mcpTool = clientInfo.toolObjects?.[tool];
357
+ if (!mcpTool) {
358
+ return { success: false, error: `Tool '${tool}' not found on server '${server}'` };
359
+ }
360
+ const execResult = await mcpTool.execute(finalArgs);
361
+ return { success: true, result: execResult };
362
+ } catch (err) {
363
+ log.error(` Tool '${tool}' failed:`, err.message);
364
+ return { success: false, error: err.message };
365
+ }
366
+ },
367
+ });
368
+
369
+ // 注册 mcp_list_servers 工具
370
+ framework.registerTool({
371
+ name: 'mcp_list_servers',
372
+ description: '列出已连接的 MCP 服务器及其工具',
373
+ inputSchema: z.object({}),
374
+ execute: async () => {
375
+ const servers = [];
376
+ for (const [name, info] of this._clients) {
377
+ servers.push({
378
+ name,
379
+ tools: info.tools.map((t) => ({ name: t.name, description: t.description })),
380
+ });
381
+ }
382
+ return { success: true, servers };
383
+ },
384
+ });
385
+
386
+ // 注册 mcp_reload 工具
387
+ framework.registerTool({
388
+ name: 'mcp_reload',
389
+ description:
390
+ '重新加载 MCP 服务器配置。当配置文件 .agent/mcp_config.json 修改后使用此工具重载配置',
391
+ inputSchema: z.object({}),
392
+ execute: async () => {
393
+ log.info(' mcp_reload called');
394
+ try {
395
+ const fs = require('fs');
396
+ const path = require('path');
397
+ const configPath = path.resolve('.agent/mcp_config.json');
398
+
399
+ log.info(' Reading config from:', configPath);
400
+ if (!fs.existsSync(configPath)) {
401
+ return { success: false, error: `配置文件不存在: ${configPath}` };
402
+ }
403
+
404
+ const configContent = fs.readFileSync(configPath, 'utf8');
405
+ const config = JSON.parse(configContent);
406
+ log.info(' Config loaded, reloading...');
407
+
408
+ await this.reloadConfig(config);
409
+ log.info(' Reload complete');
410
+
411
+ return {
412
+ success: true,
413
+ message: 'MCP 配置已重载',
414
+ servers: Object.keys(config.mcpServers || {}),
415
+ };
416
+ } catch (err) {
417
+ log.error(' Reload error:', err);
418
+ return { success: false, error: `重载失败: ${err.message}` };
419
+ }
420
+ },
421
+ });
422
+
423
+ // 监听 agent 创建事件,附加 MCP 信息到系统提示词
424
+ framework.on('agent:created', (agent) => {
425
+ if (!this._mainAgent) {
426
+ this._mainAgent = agent;
427
+ }
428
+ this._refreshAgentMCPPrompt(agent);
429
+ });
430
+
431
+ // 等待框架就绪后,刷新所有已有 agent 的 MCP 提示词
432
+ if (framework._ready) {
433
+ this._refreshAllAgentsMCPPrompt(framework);
434
+ } else {
435
+ framework.once('framework:ready', () => {
436
+ this._refreshAllAgentsMCPPrompt(framework);
437
+ });
438
+ }
439
+
440
+ return this;
441
+ }
442
+
443
+ /**
444
+ * 刷新所有 agent 的 MCP 提示词(包括子 agent)
445
+ */
446
+ _refreshAllAgentsMCPPrompt(framework) {
447
+ const visited = new Set();
448
+
449
+ const traverse = (agent) => {
450
+ if (!agent || visited.has(agent)) return;
451
+ visited.add(agent);
452
+ this._refreshAgentMCPPrompt(agent);
453
+
454
+ // 递归处理子 agent
455
+ const subAgents = agent.getSubAgents?.() || agent._subAgents || new Map();
456
+ for (const [name, subAgentInfo] of subAgents) {
457
+ traverse(subAgentInfo.agent);
458
+ }
459
+ };
460
+
461
+ for (const agent of framework._agents || []) {
462
+ traverse(agent);
463
+ }
464
+ }
465
+
466
+ /**
467
+ * 刷新单个 agent 的 MCP 提示词
468
+ */
469
+ _refreshAgentMCPPrompt(agent) {
470
+ // 检查是否已刷新过(通过检查系统提示词是否已包含 MCP 描述)
471
+ const existingPrompt = agent._originalPrompt || '';
472
+ if (existingPrompt.includes('[MCP Servers]')) {
473
+ return;
474
+ }
475
+
476
+ const mcpDesc = this._buildMCPServersDescription();
477
+ if (!mcpDesc) return;
478
+
479
+ // 将 MCP 描述追加到系统提示词
480
+ agent.setSystemPrompt(existingPrompt + '\n\n' + mcpDesc);
481
+ }
482
+
483
+ /**
484
+ * 从 inputSchema 中提取实际 schema
485
+ */
486
+ _extractSchema(inputSchema) {
487
+ if (!inputSchema) return {};
488
+ // 处理 {jsonSchema: {...}} 格式
489
+ if (inputSchema.jsonSchema) return inputSchema.jsonSchema;
490
+ // 处理直接是 schema 的格式
491
+ return inputSchema;
492
+ }
493
+
494
+ /**
495
+ * 构建 MCP 服务器描述
496
+ */
497
+ _buildMCPServersDescription() {
498
+ if (this._clients.size === 0) {
499
+ return '';
500
+ }
501
+
502
+ let desc = '[MCP Servers]\n\n';
503
+ desc += '你可以通过 `mcp_call` 工具调用以下 MCP 服务器的工具。\n\n';
504
+
505
+ for (const [serverName, info] of this._clients) {
506
+ desc += `### ${serverName}\n\n`;
507
+ for (const tool of info.tools) {
508
+ desc += `#### ${tool.name}\n`;
509
+ desc += `${tool.description || '无描述'}\n\n`;
510
+
511
+ // 使用 zodSchemaToMarkdown 生成参数文档
512
+ const schema = tool.inputSchema;
513
+ if (schema) {
514
+ try {
515
+ if (typeof schema.toJSON === 'function') {
516
+ // Zod schema 直接转换
517
+ desc += `**参数:**\n\n`;
518
+ desc += zodSchemaToMarkdown(schema) + '\n\n';
519
+ } else if (schema.jsonSchema) {
520
+ // JSON Schema 格式,尝试转换为 Zod schema
521
+ const zodSchema = this._jsonSchemaToZod(schema.jsonSchema || schema);
522
+ if (zodSchema) {
523
+ desc += `**参数:**\n\n`;
524
+ desc += zodSchemaToMarkdown(zodSchema) + '\n\n';
525
+ }
526
+ } else if (schema.properties) {
527
+ // 已经是对象格式
528
+ const zodSchema = this._jsonSchemaToZod(schema);
529
+ if (zodSchema) {
530
+ desc += `**参数:**\n\n`;
531
+ desc += zodSchemaToMarkdown(zodSchema) + '\n\n';
532
+ }
533
+ }
534
+ } catch (e) {
535
+ // 如果转换失败,使用简化格式
536
+ desc += this._fallbackParamsDesc(schema);
537
+ }
538
+ }
539
+ }
540
+ desc += '---\n\n';
541
+ }
542
+
543
+ desc += '**调用格式:**\n';
544
+ desc += '```\nmcp_call({ server: "服务器", tool: "工具", args_json: \'{"参数": "值"}\' })\n';
545
+ desc += '```\n';
546
+ return desc.trim();
547
+ }
548
+
549
+ /**
550
+ * 将 JSON Schema 转换为 Zod schema
551
+ */
552
+ _jsonSchemaToZod(jsonSchema) {
553
+ if (!jsonSchema || !jsonSchema.properties) {
554
+ return null;
555
+ }
556
+
557
+ try {
558
+ const shape = {};
559
+ const properties = jsonSchema.properties;
560
+ const required = jsonSchema.required || [];
561
+
562
+ for (const [key, prop] of Object.entries(properties)) {
563
+ shape[key] = this._jsonSchemaPropToZod(prop, required.includes(key));
564
+ }
565
+
566
+ return z.object(shape);
567
+ } catch (e) {
568
+ return null;
569
+ }
570
+ }
571
+
572
+ /**
573
+ * 将 JSON Schema 属性转换为 Zod 类型
574
+ */
575
+ _jsonSchemaPropToZod(prop, isRequired) {
576
+ if (prop.$ref) {
577
+ return isRequired ? z.any() : z.any().optional();
578
+ }
579
+
580
+ if (prop.enum) {
581
+ let zodType = z.string().enum(prop.enum);
582
+ if (prop.nullable) {
583
+ zodType = zodType.nullable();
584
+ }
585
+ return isRequired ? zodType : zodType.optional();
586
+ }
587
+
588
+ const type = prop.type || 'string';
589
+
590
+ switch (type) {
591
+ case 'string':
592
+ return isRequired ? z.string() : z.string().optional();
593
+ case 'number':
594
+ case 'integer':
595
+ return isRequired ? z.number() : z.number().optional();
596
+ case 'boolean':
597
+ return isRequired ? z.boolean() : z.boolean().optional();
598
+ case 'array':
599
+ return isRequired ? z.array(z.any()) : z.array(z.any()).optional();
600
+ case 'object':
601
+ if (prop.properties) {
602
+ const nested = {};
603
+ for (const [k, v] of Object.entries(prop.properties)) {
604
+ nested[k] = this._jsonSchemaPropToZod(v, prop.required?.includes(k) || false);
605
+ }
606
+ return isRequired ? z.object(nested) : z.object(nested).optional();
607
+ }
608
+ return isRequired ? z.record(z.any()) : z.record(z.any()).optional();
609
+ default:
610
+ return isRequired ? z.any() : z.any().optional();
611
+ }
612
+ }
613
+
614
+ /**
615
+ * 回退的参数描述(当 zodSchemaToMarkdown 失败时)
616
+ */
617
+ _fallbackParamsDesc(schema) {
618
+ const props = schema.properties || {};
619
+ const required = schema.required || [];
620
+
621
+ if (Object.keys(props).length === 0) {
622
+ return '';
623
+ }
624
+
625
+ let desc = '**参数:**\n\n';
626
+ for (const [key, prop] of Object.entries(props)) {
627
+ const isRequired = required.includes(key);
628
+ const type = prop.type || 'any';
629
+ const desc_text = prop.description || '';
630
+ desc += `- \`${key}\`${isRequired ? ' (必填)' : ''}: ${type} ${desc_text}\n`;
631
+ }
632
+ return desc + '\n';
633
+ }
634
+
635
+ _getParentAgent() {
636
+ // 优先返回已缓存的主 agent
637
+ if (this._mainAgent) {
638
+ return this._mainAgent;
639
+ }
640
+
641
+ // 尝试从 framework 获取
642
+ if (this._framework._mainAgent) {
643
+ this._mainAgent = this._framework._mainAgent;
644
+ return this._mainAgent;
645
+ }
646
+
647
+ // 查找最后一个创建的 agent 作为父 agent
648
+ const agents = this._framework._agents || [];
649
+ if (agents.length > 0) {
650
+ this._mainAgent = agents[agents.length - 1];
651
+ return this._mainAgent;
652
+ }
653
+
654
+ return null;
655
+ }
656
+
657
+ /**
658
+ * 带超时的 Promise
659
+ */
660
+ _withTimeout(promise, ms, name) {
661
+ return Promise.race([
662
+ promise,
663
+ new Promise((_, reject) =>
664
+ setTimeout(() => reject(new Error(`${name} timeout (${ms}ms)`)), ms)
665
+ ),
666
+ ]);
667
+ }
668
+
669
+ /**
670
+ * 添加 MCP 服务器
671
+ */
672
+ async addServer(config) {
673
+ const { name, command, args = [], env, url, type, headers = {}, authProvider } = config;
674
+ if (this._clients.has(name)) {
675
+ log.warn(` Server '${name}' already exists, skipping`);
676
+ return;
677
+ }
678
+ log.info(` Connecting to ${name}...`);
679
+ let client;
680
+ try {
681
+ if (['sse', 'http'].includes(type)) {
682
+ client = await this._withTimeout(
683
+ createMCPClient({
684
+ transport: {
685
+ type: type,
686
+ url: url,
687
+ headers: headers,
688
+ authProvider: authProvider,
689
+ redirect: 'error',
690
+ env: env,
691
+ },
692
+ }),
693
+ 10000,
694
+ `createMCPClient ${name}`
695
+ );
696
+ } else {
697
+ client = await this._withTimeout(
698
+ createMCPClient({
699
+ transport: new StdioClientTransport({
700
+ command: command,
701
+ args: args,
702
+ env: env,
703
+ }),
704
+ }),
705
+ 10000,
706
+ `createMCPClient ${name}`
707
+ );
708
+ }
709
+ } catch (err) {
710
+ log.error(` Failed to create client '${name}':`, err.message);
711
+ return;
712
+ }
713
+
714
+ // 获取 MCP 服务器的工具列表
715
+ let mcpTools;
716
+ try {
717
+ mcpTools = await this._withTimeout(client.tools(), 10000, `client.tools ${name}`);
718
+ } catch (err) {
719
+ log.error(` Failed to get tools from '${name}':`, err.message);
720
+ return;
721
+ }
722
+
723
+ // 提取工具信息和实际工具对象
724
+ const toolsInfo = [];
725
+ const toolObjects = {}; // 缓存实际工具对象
726
+ if (mcpTools && typeof mcpTools === 'object') {
727
+ for (const [toolName, tool] of Object.entries(mcpTools)) {
728
+ if (!tool) continue;
729
+ toolObjects[toolName] = tool; // 保存实际工具对象
730
+ toolsInfo.push({
731
+ name: toolName,
732
+ description: tool.description || '',
733
+ inputSchema: tool.inputSchema || {},
734
+ });
735
+ }
736
+ }
737
+
738
+ // 保存客户端、工具信息和实际工具对象
739
+ this._clients.set(name, {
740
+ client,
741
+ tools: toolsInfo,
742
+ toolObjects, // 实际工具对象,供 mcp_call 直接调用
743
+ });
744
+
745
+ log.info(` Added server '${name}' with ${toolsInfo.length} tools`);
746
+ }
747
+
748
+ /**
749
+ * 移除 MCP 服务器
750
+ */
751
+ async removeServer(name) {
752
+ const clientInfo = this._clients.get(name);
753
+ if (clientInfo) {
754
+ try {
755
+ // 添加超时保护,避免 close/destroy 阻塞
756
+ const closePromise = clientInfo.client.close?.() || clientInfo.client.destroy?.();
757
+ if (closePromise && typeof closePromise.then === 'function') {
758
+ await Promise.race([
759
+ closePromise,
760
+ new Promise((resolve) => setTimeout(() => resolve(), 3000)),
761
+ ]);
762
+ }
763
+ } catch (e) {
764
+ // ignore
765
+ }
766
+ this._clients.delete(name);
767
+ }
768
+ }
769
+
770
+ /**
771
+ * 获取所有服务器
772
+ */
773
+ getServers() {
774
+ return Array.from(this._clients.entries()).map(([name, info]) => ({
775
+ name,
776
+ tools: info.tools.map((t) => t.name),
777
+ }));
778
+ }
779
+
780
+ reload(framework) {
781
+ log.info(' Reloading...');
782
+ this._framework = framework;
783
+ }
784
+
785
+ /**
786
+ * 重新加载 MCP 配置
787
+ * @param {Object} config - 新的配置对象,包含 mcpServers
788
+ */
789
+ async reloadConfig(config) {
790
+ log.info(' reloadConfig start');
791
+ const newServers = config?.mcpServers || {};
792
+
793
+ // 1. 先快速清理所有旧服务器(不等待 close 完成)
794
+ log.info(' Clearing old servers...');
795
+ for (const [name, clientInfo] of this._clients) {
796
+ try {
797
+ // 不等待 close,直接删除
798
+ clientInfo.client.close?.()?.catch?.(() => {});
799
+ } catch (e) {}
800
+ }
801
+ this._clients.clear();
802
+
803
+ // 2. 并行添加新服务器
804
+ log.info(' Adding new servers...');
805
+ const addPromises = Object.entries(newServers).map(async ([name, serverConfig]) => {
806
+ const normalizedConfig = {
807
+ name,
808
+ type: serverConfig.type,
809
+ url: serverConfig.url,
810
+ headers: serverConfig.headers,
811
+ authProvider: serverConfig.authProvider,
812
+ command: serverConfig.command,
813
+ args: serverConfig.args,
814
+ env: serverConfig.env,
815
+ };
816
+ await this.addServer(normalizedConfig);
817
+ });
818
+
819
+ await Promise.all(addPromises);
820
+ log.info(' reloadConfig complete');
821
+
822
+ // 3. 刷新所有 agent 的 MCP 提示词
823
+ if (this._framework) {
824
+ this._refreshAllAgentsMCPPrompt(this._framework);
825
+ }
826
+ }
827
+
828
+ async uninstall(framework) {
829
+ // 断开所有 MCP 连接
830
+ for (const [name, clientInfo] of this._clients) {
831
+ try {
832
+ (await clientInfo.client.close?.()) || clientInfo.client.destroy?.();
833
+ } catch (e) {
834
+ // ignore
835
+ }
836
+ }
837
+ this._clients.clear();
838
+ this._framework = null;
839
+ }
840
+ }
841
+
842
+ module.exports = {
843
+ MCPExecutorPlugin,
844
+ MCPClientWrapper,
845
+ };