foliko 1.0.0

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