foliko 1.1.93 → 2.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 (212) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/CLAUDE.md +56 -30
  3. package/REFACTORING_PLAN.md +645 -0
  4. package/docs/architecture.md +131 -0
  5. package/docs/migration.md +57 -0
  6. package/docs/public-api.md +138 -0
  7. package/docs/usage.md +385 -0
  8. package/examples/ambient-example.js +20 -137
  9. package/examples/basic.js +21 -48
  10. package/examples/bootstrap.js +16 -74
  11. package/examples/mcp-example.js +6 -29
  12. package/examples/skill-example.js +6 -19
  13. package/examples/workflow.js +8 -56
  14. package/package.json +8 -4
  15. package/plugins/README.md +49 -0
  16. package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
  17. package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
  18. package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
  19. package/plugins/ambient/README.md +14 -0
  20. package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
  21. package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
  22. package/plugins/{ambient-agent → ambient}/index.js +2 -2
  23. package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
  24. package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
  25. package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
  26. package/plugins/core/default/bootstrap.js +202 -0
  27. package/plugins/core/default/config.js +220 -0
  28. package/plugins/core/default/index.js +58 -0
  29. package/plugins/core/mcp/index.js +1 -0
  30. package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
  31. package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
  32. package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
  33. package/plugins/{session-plugin.js → core/session/index.js} +9 -73
  34. package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
  35. package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
  36. package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
  37. package/plugins/{think-plugin.js → core/think/index.js} +24 -91
  38. package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
  39. package/plugins/default-plugins.js +6 -720
  40. package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
  41. package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
  42. package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
  43. package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
  44. package/plugins/install/README.md +9 -0
  45. package/plugins/{install-plugin.js → install/index.js} +3 -3
  46. package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
  47. package/plugins/{web-plugin.js → io/web/index.js} +11 -113
  48. package/plugins/memory/README.md +13 -0
  49. package/plugins/{memory-plugin.js → memory/index.js} +4 -18
  50. package/plugins/messaging/email/README.md +19 -0
  51. package/plugins/{email → messaging/email}/index.js +2 -2
  52. package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +3 -3
  53. package/plugins/{qq-plugin.js → messaging/qq/index.js} +5 -16
  54. package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +3 -3
  55. package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +15 -15
  56. package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
  57. package/plugins/{tools-plugin.js → tools/index.js} +68 -116
  58. package/plugins/trading/README.md +15 -0
  59. package/plugins/{gate-trading.js → trading/index.js} +8 -8
  60. package/{examples → sandbox}/test-concurrent-chat.js +2 -2
  61. package/{examples → sandbox}/test-long-chat.js +2 -2
  62. package/{examples → sandbox}/test-session-chat.js +2 -2
  63. package/{examples → sandbox}/test-web-plugin.js +1 -1
  64. package/{examples → sandbox}/test-weixin-feishu.js +2 -2
  65. package/src/agent/base.js +56 -0
  66. package/src/{core/agent-chat.js → agent/chat.js} +11 -11
  67. package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
  68. package/src/agent/index.js +111 -0
  69. package/src/agent/main.js +337 -0
  70. package/src/agent/prompt.js +78 -0
  71. package/src/agent/sub.js +198 -0
  72. package/src/agent/worker.js +104 -0
  73. package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
  74. package/{cli/src → src/cli}/commands/chat.js +25 -21
  75. package/{cli/src → src/cli}/index.js +1 -0
  76. package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
  77. package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
  78. package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
  79. package/src/common/errors.js +402 -0
  80. package/src/{utils → common}/logger.js +33 -0
  81. package/src/{utils/chat-queue.js → common/queue.js} +2 -2
  82. package/src/config/plugin-config.js +50 -0
  83. package/src/context/agent.js +32 -0
  84. package/src/context/compaction-prompts.js +170 -0
  85. package/src/context/compaction-utils.js +191 -0
  86. package/src/context/compressor.js +413 -0
  87. package/src/context/index.js +9 -0
  88. package/src/{core/context-manager.js → context/manager.js} +1 -1
  89. package/src/context/request.js +50 -0
  90. package/src/context/session.js +33 -0
  91. package/src/context/storage.js +30 -0
  92. package/src/executors/mcp-client.js +153 -0
  93. package/src/executors/mcp-desc.js +236 -0
  94. package/src/executors/mcp-executor.js +91 -956
  95. package/src/{core → framework}/command-registry.js +1 -1
  96. package/src/framework/framework.js +300 -0
  97. package/src/framework/index.js +18 -0
  98. package/src/framework/lifecycle.js +203 -0
  99. package/src/framework/loader.js +78 -0
  100. package/src/framework/registry.js +86 -0
  101. package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
  102. package/src/index.js +130 -15
  103. package/src/llm/index.js +26 -0
  104. package/src/llm/provider.js +212 -0
  105. package/src/llm/registry.js +11 -0
  106. package/src/{core/token-counter.js → llm/tokens.js} +4 -37
  107. package/src/{core/plugin-base.js → plugin/base.js} +10 -136
  108. package/src/plugin/index.js +14 -0
  109. package/src/plugin/loader.js +101 -0
  110. package/src/plugin/manager.js +261 -0
  111. package/src/{core → session}/branch-summary-auto.js +2 -2
  112. package/src/{core/chat-session.js → session/chat.js} +2 -2
  113. package/src/session/index.js +7 -0
  114. package/src/{core/session-manager.js → session/session.js} +2 -2
  115. package/src/session/ttl.js +92 -0
  116. package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
  117. package/src/tool/executor.js +85 -0
  118. package/src/tool/index.js +15 -0
  119. package/src/tool/registry.js +143 -0
  120. package/src/{core/tool-router.js → tool/router.js} +17 -124
  121. package/src/tool/schema.js +108 -0
  122. package/src/utils/data-splitter.js +1 -1
  123. package/src/utils/download.js +1 -1
  124. package/src/utils/index.js +6 -6
  125. package/src/utils/message-validator.js +1 -1
  126. package/tests/core/context-storage.test.js +46 -0
  127. package/tests/core/llm.test.js +54 -0
  128. package/tests/core/plugin.test.js +42 -0
  129. package/tests/core/tool.test.js +60 -0
  130. package/tests/setup.js +10 -0
  131. package/tests/smoke.test.js +58 -0
  132. package/vitest.config.js +9 -0
  133. package/cli/src/daemon.js +0 -149
  134. package/docs/CONTEXT_DESIGN.md +0 -1596
  135. package/docs/ai-sdk-optimization.md +0 -655
  136. package/docs/features.md +0 -120
  137. package/docs/qq-bot.md +0 -976
  138. package/docs/quick-reference.md +0 -160
  139. package/docs/user-manual.md +0 -1391
  140. package/images/geometric_shapes.jpg +0 -0
  141. package/images/sunset_mountain_lake.jpg +0 -0
  142. package/skills/poster-guide/SKILL.md +0 -792
  143. package/src/capabilities/index.js +0 -11
  144. package/src/core/agent.js +0 -808
  145. package/src/core/context-compressor.js +0 -959
  146. package/src/core/enhanced-context-compressor.js +0 -210
  147. package/src/core/framework.js +0 -1422
  148. package/src/core/index.js +0 -30
  149. package/src/core/plugin-manager.js +0 -961
  150. package/src/core/provider-registry.js +0 -159
  151. package/src/core/provider.js +0 -156
  152. package/src/core/request-context.js +0 -98
  153. package/src/core/subagent.js +0 -442
  154. package/src/core/system-prompt-builder.js +0 -120
  155. package/src/core/tool-executor.js +0 -202
  156. package/src/core/tool-registry.js +0 -517
  157. package/src/core/worker-agent.js +0 -192
  158. package/src/executors/executor-base.js +0 -58
  159. package/src/utils/error-boundary.js +0 -363
  160. package/src/utils/error.js +0 -374
  161. package/system.md +0 -1645
  162. package/website_v2/README.md +0 -57
  163. package/website_v2/SPEC.md +0 -1
  164. package/website_v2/docs/api.html +0 -128
  165. package/website_v2/docs/configuration.html +0 -147
  166. package/website_v2/docs/plugin-development.html +0 -129
  167. package/website_v2/docs/project-structure.html +0 -89
  168. package/website_v2/docs/skill-development.html +0 -85
  169. package/website_v2/index.html +0 -489
  170. package/website_v2/scripts/main.js +0 -93
  171. package/website_v2/styles/animations.css +0 -8
  172. package/website_v2/styles/docs.css +0 -83
  173. package/website_v2/styles/main.css +0 -417
  174. package/xhs_auth.json +0 -268
  175. package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
  176. /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
  177. /package/plugins/{email → messaging/email}/constants.js +0 -0
  178. /package/plugins/{email → messaging/email}/handlers.js +0 -0
  179. /package/plugins/{email → messaging/email}/monitor.js +0 -0
  180. /package/plugins/{email → messaging/email}/parser.js +0 -0
  181. /package/plugins/{email → messaging/email}/reply.js +0 -0
  182. /package/plugins/{email → messaging/email}/utils.js +0 -0
  183. /package/{examples → sandbox}/test-chat.js +0 -0
  184. /package/{examples → sandbox}/test-mcp.js +0 -0
  185. /package/{examples → sandbox}/test-reload.js +0 -0
  186. /package/{examples → sandbox}/test-telegram.js +0 -0
  187. /package/{examples → sandbox}/test-tg-bot.js +0 -0
  188. /package/{examples → sandbox}/test-tg-simple.js +0 -0
  189. /package/{examples → sandbox}/test-tg.js +0 -0
  190. /package/{examples → sandbox}/test-think.js +0 -0
  191. /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
  192. /package/{cli/src → src/cli}/commands/daemon.js +0 -0
  193. /package/{cli/src → src/cli}/commands/list.js +0 -0
  194. /package/{cli/src → src/cli}/commands/plugin.js +0 -0
  195. /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
  196. /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
  197. /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
  198. /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
  199. /package/{cli/src → src/cli}/utils/ansi.js +0 -0
  200. /package/{cli/src → src/cli}/utils/config.js +0 -0
  201. /package/{cli/src → src/cli}/utils/markdown.js +0 -0
  202. /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
  203. /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
  204. /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
  205. /package/src/{core → common}/constants.js +0 -0
  206. /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
  207. /package/src/{utils/event-emitter.js → common/events.js} +0 -0
  208. /package/src/{utils → common}/id.js +0 -0
  209. /package/src/{utils → common}/retry.js +0 -0
  210. /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
  211. /package/src/{core/session-entry.js → session/entry.js} +0 -0
  212. /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
@@ -1,208 +1,24 @@
1
+ 'use strict';
2
+
1
3
  /**
2
4
  * MCPExecutor MCP 执行器
3
5
  * 使用 @ai-sdk/mcp 连接到 MCP 服务器
4
6
  */
5
7
 
6
- const { Plugin } = require('../core/plugin-base');
7
- const { spawn } = require('child_process');
8
+ const { Plugin } = require('../plugin/base');
8
9
  const fs = require('fs');
9
10
  const path = require('path');
10
11
  const { z } = require('zod');
11
12
  const { createMCPClient } = require('@ai-sdk/mcp');
12
13
  const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js');
13
14
  const { zodSchemaToMarkdown } = require('@chnak/zod-to-markdown');
14
- const { logger } = require('../utils/logger');
15
+ const { logger } = require('../common/logger');
16
+ const { MCPClientWrapper } = require('./mcp-client');
17
+ const { extractSchema, buildMCPServersDescription, bindMcpParamsDesc } = require('./mcp-desc');
18
+ const { jsonSchemaToZod } = require('../tool/schema');
15
19
 
16
20
  const log = logger.child('MCPExecutor');
17
21
 
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
- const controller = new AbortController();
41
- const timeoutMs = this.timeout || 30000;
42
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
43
-
44
- try {
45
- // 动态导入 @ai-sdk/mcp
46
- let createMCPClient;
47
-
48
- try {
49
- const mcpModule = require('@ai-sdk/mcp');
50
- createMCPClient = mcpModule.createMCPClient || mcpModule.experimental_createMCPClient;
51
- } catch (err) {
52
- throw new Error('@ai-sdk/mcp not installed. Please run: npm install @ai-sdk/mcp');
53
- }
54
-
55
- if (!createMCPClient) {
56
- throw new Error('createMCPClient not available');
57
- }
58
-
59
- // 解析命令路径
60
- const { which, shellResolve } = this._resolveCommand(this.command);
61
-
62
- // 使用正确的子路径导入
63
- try {
64
- const { Experimental_StdioMCPTransport } = require('@ai-sdk/mcp/mcp-stdio');
65
- const transport = new Experimental_StdioMCPTransport({
66
- command: which || this.command,
67
- args: shellResolve ? [shellResolve, ...this.args] : this.args,
68
- env: { ...process.env, ...this.env },
69
- });
70
- this.client = await createMCPClient({
71
- transport,
72
- signal: controller.signal,
73
- });
74
- } catch (transportErr) {
75
- log.error(` Transport error:`, transportErr.message);
76
- throw transportErr;
77
- }
78
-
79
- // 获取工具列表
80
- // 新版 API: tools() 是函数
81
- if (typeof this.client.tools === 'function') {
82
- const toolsObject = await this.client.tools();
83
- this.tools = Object.entries(toolsObject)
84
- .filter(([, tool]) => tool != null)
85
- .map(([name, tool]) => ({ ...tool, name }));
86
- } else if (this.client.tools && typeof this.client.tools.list === 'function') {
87
- // 旧版 API: tools.list()
88
- const listResult = await this.client.tools.list();
89
- this.tools = listResult.tools || [];
90
- }
91
-
92
- this.connected = true;
93
- // log.info(` Connected to ${this.serverName} with ${this.tools.length} tools`);
94
- } catch (err) {
95
- if (controller.signal.aborted) {
96
- log.error(` Connection timeout (${timeoutMs}ms) for ${this.serverName}`);
97
- throw new Error(`连接超时 (${timeoutMs}ms): ${this.serverName}`);
98
- }
99
- log.error(` Failed to connect to ${this.serverName}:`, err.message);
100
- // 不抛出错误,让框架继续运行
101
- } finally {
102
- clearTimeout(timeoutId);
103
- }
104
- }
105
-
106
- /**
107
- * 解析命令路径
108
- */
109
- _resolveCommand(cmd) {
110
- const isWindows = process.platform === 'win32';
111
- const ext = isWindows ? '.cmd' : '';
112
- const withExt = cmd + ext;
113
-
114
- // 检查命令是否直接存在
115
- try {
116
- const result = spawn.sync(withExt, ['--version'], {
117
- stdio: 'ignore',
118
- timeout: 2000,
119
- });
120
- if (result.error?.code !== 'ENOENT') {
121
- return { which: withExt, shellResolve: null };
122
- }
123
- } catch (e) {
124
- // ignore
125
- }
126
-
127
- // 尝试解析 npm 全局 bin 目录
128
- const globalCommands = isWindows
129
- ? [
130
- path.join(process.env.APPDATA || '', 'npm', withExt),
131
- path.join(process.env.PROGRAMDATA || 'C:\\ProgramData', 'npm', withExt),
132
- ]
133
- : ['/usr/local/bin/' + cmd, '/usr/bin/' + cmd];
134
-
135
- for (const cmdPath of globalCommands) {
136
- try {
137
- if (fs.existsSync(cmdPath)) {
138
- return { which: cmdPath, shellResolve: null };
139
- }
140
- } catch (e) {
141
- // ignore
142
- }
143
- }
144
-
145
- return { which: cmd, shellResolve: null };
146
- }
147
-
148
- /**
149
- * 调用工具
150
- */
151
- async callTool(toolName, args) {
152
- if (!this.connected) {
153
- await this.connect();
154
- }
155
-
156
- if (!this.client) {
157
- throw new Error('MCP client not initialized');
158
- }
159
-
160
- try {
161
- // 新版 API: tools() 返回对象
162
- let toolsObject;
163
- if (typeof this.client.tools === 'function') {
164
- toolsObject = await this.client.tools();
165
- } else if (this.client.tools && typeof this.client.tools.list === 'function') {
166
- // 旧版 API
167
- const listResult = await this.client.tools.list();
168
- toolsObject = listResult.tools || {};
169
- } else {
170
- throw new Error('Cannot get tools from MCP client');
171
- }
172
-
173
- const tool = toolsObject[toolName];
174
- if (!tool) {
175
- throw new Error(`Tool not found: ${toolName}`);
176
- }
177
-
178
- const result = await tool.execute(args);
179
- return result;
180
- } catch (err) {
181
- log.error(` Tool call failed: ${toolName}:`, err.message);
182
- throw err;
183
- }
184
- }
185
-
186
- /**
187
- * 断开连接
188
- */
189
- async disconnect() {
190
- if (this.client) {
191
- try {
192
- await this.client.destroy();
193
- } catch (e) {
194
- // ignore
195
- }
196
- this.client = null;
197
- this.connected = false;
198
- this.tools = [];
199
- }
200
- }
201
- }
202
-
203
- /**
204
- * MCPExecutorPlugin
205
- */
206
22
  class MCPExecutorPlugin extends Plugin {
207
23
  constructor(config = {}) {
208
24
  super();
@@ -213,13 +29,9 @@ class MCPExecutorPlugin extends Plugin {
213
29
  this.system = true;
214
30
 
215
31
  this._framework = null;
216
- // serverName -> { client, tools: [{ name, description, inputSchema }], toolObjects: { toolName: actualTool } }
217
32
  this._clients = new Map();
218
- // 保存服务器配置,用于重新启用
219
33
  this._serverConfigs = new Map();
220
34
  this._config = config;
221
- // 注册的工具对象(extension-executor 会扫描这个)
222
- // 格式: { 'server_toolname': { name, description, inputSchema, execute } }
223
35
  this.tools = {};
224
36
  }
225
37
 
@@ -229,23 +41,18 @@ class MCPExecutorPlugin extends Plugin {
229
41
  }
230
42
 
231
43
  async start(framework) {
232
- // 异步加载所有 MCP 服务器,不阻塞框架启动
233
44
  if (this._config.servers) {
234
45
  for (const serverConfig of this._config.servers) {
235
46
  if (serverConfig.enabled === false) {
236
- // log.info(` Skipping disabled server: ${serverConfig.name}`);
237
- // 保存完整配置,以便后续动态启用
238
47
  this._serverConfigs.set(serverConfig.name, { ...serverConfig });
239
48
  continue;
240
49
  }
241
- // 异步连接,不 await
242
50
  this.addServer(serverConfig).catch(err => {
243
51
  log.error(`[MCP] Async server connection failed for ${serverConfig.name}:`, err.message);
244
52
  });
245
53
  }
246
54
  }
247
55
 
248
- // 注册 mcp_tool_schema 工具 - 直接返回可用的调用示例
249
56
  framework.registerTool({
250
57
  name: 'mcp_tool_schema',
251
58
  description: '查询工具的调用示例,直接复制 example 或 fullExample 字段使用',
@@ -256,42 +63,31 @@ class MCPExecutorPlugin extends Plugin {
256
63
  execute: async (args) => {
257
64
  const { server, tool } = args;
258
65
  const clientInfo = this._clients.get(server);
259
- if (!clientInfo) {
260
- return { success: false, error: `MCP server '${server}' not found` };
261
- }
66
+ if (!clientInfo) return { success: false, error: `MCP server '${server}' not found` };
262
67
 
263
68
  const toolInfo = clientInfo.tools.find((t) => t.name === tool);
264
- if (!toolInfo) {
265
- return { success: false, error: `Tool '${tool}' not found` };
266
- }
69
+ if (!toolInfo) return { success: false, error: `Tool '${tool}' not found` };
267
70
 
268
- const schema = this._extractSchema(toolInfo.inputSchema);
71
+ const schema = extractSchema(toolInfo.inputSchema);
269
72
  const props = schema.properties || {};
270
73
  const required = schema.required || [];
271
74
 
272
- // 生成示例值的辅助函数
273
75
  const generateExample = (prop, depth = 0) => {
274
76
  if (!prop) return null;
275
77
  const type = prop.type || (prop.properties ? 'object' : 'string');
276
-
277
- if (type === 'string') {
278
- return prop.description?.split('.')[0] || `${prop.title || '值'}`;
279
- } else if (type === 'number' || type === 'integer') {
280
- return prop.default || 0;
281
- } else if (type === 'boolean') {
282
- return false;
283
- } else if (type === 'array') {
284
- // 数组类型:提供单个示例元素
78
+ if (type === 'string') return prop.description?.split('.')[0] || `${prop.title || '值'}`;
79
+ if (type === 'number' || type === 'integer') return prop.default || 0;
80
+ if (type === 'boolean') return false;
81
+ if (type === 'array') {
285
82
  if (prop.items) {
286
83
  const itemExample = generateExample(prop.items, depth + 1);
287
84
  return depth === 0 ? [itemExample] : itemExample;
288
85
  }
289
86
  return ['示例'];
290
- } else if (type === 'object' || prop.properties) {
291
- // 对象类型:递归生成嵌套结构
87
+ }
88
+ if (type === 'object' || prop.properties) {
292
89
  const obj = {};
293
- const objProps = prop.properties || {};
294
- for (const [key, val] of Object.entries(objProps)) {
90
+ for (const [key, val] of Object.entries(prop.properties || {})) {
295
91
  obj[key] = generateExample(val, depth + 1);
296
92
  }
297
93
  return obj;
@@ -299,33 +95,23 @@ class MCPExecutorPlugin extends Plugin {
299
95
  return null;
300
96
  };
301
97
 
302
- // 生成可直接使用的调用示例
303
98
  const exampleArgs = {};
304
99
  for (const key of required) {
305
100
  const prop = props[key];
306
- if (prop) {
307
- exampleArgs[key] = generateExample(prop);
308
- }
101
+ if (prop) exampleArgs[key] = generateExample(prop);
309
102
  }
310
103
 
311
104
  return {
312
105
  success: true,
313
106
  data: {
314
- name: toolInfo.name,
315
- description: toolInfo.description,
316
- required,
317
- // 各参数的详细说明
107
+ name: toolInfo.name, description: toolInfo.description, required,
318
108
  parameters: Object.fromEntries(
319
- required.map((key) => [
320
- key,
321
- {
322
- type: props[key]?.type || 'string',
323
- description: props[key]?.description || '',
324
- example: generateExample(props[key]),
325
- },
326
- ])
109
+ required.map((key) => [key, {
110
+ type: props[key]?.type || 'string',
111
+ description: props[key]?.description || '',
112
+ example: generateExample(props[key]),
113
+ }])
327
114
  ),
328
- // 直接返回可以复制使用的调用示例
329
115
  example: JSON.stringify(exampleArgs, null, 2),
330
116
  fullExample: `mcp_call({ server: "${server}", tool: "${tool}", args_json: ${JSON.stringify(JSON.stringify(exampleArgs))} })`,
331
117
  },
@@ -333,162 +119,81 @@ class MCPExecutorPlugin extends Plugin {
333
119
  },
334
120
  });
335
121
 
336
- // 注册 mcp_call 工具
337
122
  framework.registerTool({
338
123
  name: 'mcp_call',
339
- description:
340
- '调用 MCP 服务器工具。args_json 是包含实际参数的 JSON 字符串,如 \'{"url": "https://news.baidu.com"}\'',
124
+ description: '调用 MCP 服务器工具。args_json 是包含实际参数的 JSON 字符串',
341
125
  inputSchema: z.object({
342
126
  server: z.string().describe('MCP 服务器名称'),
343
127
  tool: z.string().describe('工具名称'),
344
- args_json: z
345
- .string()
346
- .describe('参数 JSON 字符串,如 {"url": "https://..."},fetch 工具必填 url'),
128
+ args_json: z.string().describe('参数 JSON 字符串'),
347
129
  }),
348
130
  execute: async (args) => {
349
131
  const { server, tool, args_json } = args;
350
- // log.info(` mcp_call: server=${server}, tool=${tool}, args_json=${args_json}`);
351
-
352
132
  const clientInfo = this._clients.get(server);
353
- if (!clientInfo) {
354
- return { success: false, error: `MCP server '${server}' not found` };
355
- }
133
+ if (!clientInfo) return { success: false, error: `MCP server '${server}' not found` };
356
134
 
357
- // 解析 args_json
358
135
  let finalArgs = {};
359
136
  if (args_json) {
360
- try {
361
- finalArgs = JSON.parse(args_json);
362
- } catch (e) {
363
- return { success: false, error: `args_json 格式错误,不是有效的 JSON: ${args_json}` };
364
- }
137
+ try { finalArgs = JSON.parse(args_json); }
138
+ catch (e) { return { success: false, error: `args_json 格式错误: ${args_json}` }; }
365
139
  }
366
140
 
367
- // 检查参数是否为空
368
141
  if (!finalArgs || Object.keys(finalArgs).length === 0) {
369
142
  const toolInfo = clientInfo.tools.find((t) => t.name === tool);
370
143
  if (toolInfo) {
371
- const schema = this._extractSchema(toolInfo.inputSchema);
144
+ const schema = extractSchema(toolInfo.inputSchema);
372
145
  const required = schema.required || [];
373
- return {
374
- success: false,
375
- error: `参数为空!必须提供: ${required.join(', ')}`,
376
- hint: `正确格式: {"${required[0]}": "具体值"}`,
377
- };
146
+ return { success: false, error: `参数为空!必须提供: ${required.join(', ')}`, hint: `正确格式: {"${required[0]}": "具体值"}` };
378
147
  }
379
148
  }
380
149
 
381
150
  try {
382
- // 使用缓存的工具对象
383
151
  const mcpTool = clientInfo.toolObjects?.[tool];
384
- if (!mcpTool) {
385
- return { success: false, error: `Tool '${tool}' not found on server '${server}'` };
386
- }
152
+ if (!mcpTool) return { success: false, error: `Tool '${tool}' not found on server '${server}'` };
387
153
 
388
- // 触发 MCP 工具开始事件
389
154
  const mcpToolName = `${server}:${tool}`;
390
- framework.emit('tool:call', {
391
- name: mcpToolName,
392
- args: finalArgs,
393
- source: 'mcp',
394
- });
395
- framework.emit('tool-call', {
396
- name: mcpToolName,
397
- args: finalArgs,
398
- source: 'mcp',
399
- });
155
+ framework.emit('tool:call', { name: mcpToolName, args: finalArgs, source: 'mcp' });
156
+ framework.emit('tool-call', { name: mcpToolName, args: finalArgs, source: 'mcp' });
400
157
 
401
158
  const execResult = await mcpTool.execute(finalArgs);
402
159
 
403
- // 触发 MCP 工具完成事件
404
- framework.emit('tool:result', {
405
- name: mcpToolName,
406
- args: finalArgs,
407
- result: execResult,
408
- source: 'mcp',
409
- });
410
-
160
+ framework.emit('tool:result', { name: mcpToolName, args: finalArgs, result: execResult, source: 'mcp' });
411
161
  return { success: true, data: execResult };
412
162
  } catch (err) {
413
163
  log.error(` Tool '${tool}' failed:`, err.message);
414
-
415
- // 触发 MCP 工具错误事件
416
- const mcpToolName = `${server}:${tool}`;
417
- framework.emit('tool:error', {
418
- name: mcpToolName,
419
- args: finalArgs,
420
- error: err.message,
421
- source: 'mcp',
422
- });
423
-
164
+ framework.emit('tool:error', { name: `${server}:${tool}`, args: finalArgs, error: err.message, source: 'mcp' });
424
165
  return { success: false, error: err.message };
425
166
  }
426
167
  },
427
168
  });
428
169
 
429
- // 注册 mcp_list_servers 工具
430
170
  framework.registerTool({
431
171
  name: 'mcp_list_servers',
432
172
  description: '列出已连接的 MCP 服务器及其工具',
433
173
  inputSchema: z.object({}),
434
174
  execute: async () => {
435
175
  const servers = [];
436
- // 已连接的服务器
437
176
  for (const [name, info] of this._clients) {
438
- servers.push({
439
- name,
440
- enabled: true,
441
- connected: true,
442
- tools: info.tools.map((t) => ({ name: t.name, description: t.description })),
443
- });
177
+ servers.push({ name, enabled: true, connected: true, tools: info.tools.map((t) => ({ name: t.name, description: t.description })) });
444
178
  }
445
- // 未连接的服务器(配置中存在但未启用)
446
179
  for (const [name, config] of this._serverConfigs) {
447
- if (!this._clients.has(name)) {
448
- servers.push({
449
- name,
450
- enabled: false,
451
- connected: false,
452
- tools: [],
453
- });
454
- }
180
+ if (!this._clients.has(name)) servers.push({ name, enabled: false, connected: false, tools: [] });
455
181
  }
456
182
  return { success: true, data: servers };
457
183
  },
458
184
  });
459
185
 
460
- // 注册 mcp_reload 工具
461
186
  framework.registerTool({
462
187
  name: 'mcp_reload',
463
- description:
464
- '重新加载 MCP 服务器配置。当配置文件 .foliko/mcp_config.json 修改后使用此工具重载配置',
188
+ description: '重新加载 MCP 服务器配置。当配置文件 .foliko/mcp_config.json 修改后使用此工具重载配置',
465
189
  inputSchema: z.object({}),
466
190
  execute: async () => {
467
- // log.info(' mcp_reload called');
468
191
  try {
469
- const fs = require('fs');
470
- const path = require('path');
471
192
  const configPath = path.resolve('.foliko/mcp_config.json');
472
-
473
- // log.info(' Reading config from:', configPath);
474
- if (!fs.existsSync(configPath)) {
475
- return { success: false, error: `配置文件不存在: ${configPath}` };
476
- }
477
-
478
- const configContent = fs.readFileSync(configPath, 'utf8');
479
- const config = JSON.parse(configContent);
480
- // log.info(' Config loaded, reloading...');
481
-
193
+ if (!fs.existsSync(configPath)) return { success: false, error: `配置文件不存在: ${configPath}` };
194
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
482
195
  await this.reloadConfig(config);
483
- // log.info(' Reload complete');
484
-
485
- return {
486
- success: true,
487
- data: 'MCP 配置已重载',
488
- metadata: {
489
- servers: Object.keys(config.mcpServers || {}),
490
- },
491
- };
196
+ return { success: true, data: 'MCP 配置已重载', metadata: { servers: Object.keys(config.mcpServers || {}) } };
492
197
  } catch (err) {
493
198
  log.error(' Reload error:', err);
494
199
  return { success: false, error: `重载失败: ${err.message}` };
@@ -496,7 +201,6 @@ class MCPExecutorPlugin extends Plugin {
496
201
  },
497
202
  });
498
203
 
499
- // 注册 mcp_set_enabled 工具 - 动态开启/关闭 MCP 服务器
500
204
  framework.registerTool({
501
205
  name: 'mcp_set_enabled',
502
206
  description: '动态开启或关闭某个 MCP 服务器',
@@ -510,70 +214,42 @@ class MCPExecutorPlugin extends Plugin {
510
214
  const serverConfig = this._serverConfigs.get(server);
511
215
 
512
216
  if (enabled) {
513
- // 启用服务器
514
- if (clientInfo && clientInfo.enabled) {
515
- return { success: true, data: `MCP 服务器 '${server}' 已经是开启状态` };
516
- }
517
- // 如果服务器从未连接过,也检查配置中的 enabled 状态
518
- if (!clientInfo && serverConfig && serverConfig.enabled === false) {
519
- // 服务器是被禁用的,可以尝试启用
520
- }
521
- // log.info(`[MCP] Enabling server: ${server}, clientInfo=${!!clientInfo}, serverConfig.enabled=${serverConfig?.enabled}`);
217
+ if (clientInfo && clientInfo.enabled) return { success: true, data: `MCP 服务器 '${server}' 已经是开启状态` };
522
218
  try {
523
219
  if (serverConfig) {
524
220
  const result = await this.addServer({ ...serverConfig, enabled: true });
525
- // log.info(`[MCP] addServer result:`, JSON.stringify(result));
526
221
  if (!result || result.success === false) {
527
222
  this._refreshAllAgentsMCPPrompt(this._framework);
528
223
  return result;
529
224
  }
530
- // 保存 enabled 状态到配置文件
531
225
  await this._saveMCPServerEnabled(server, true);
532
226
  this._refreshAllAgentsMCPPrompt(this._framework);
533
227
  return { success: true, data: `MCP 服务器 '${server}' 已开启` };
534
- } else {
535
- return { success: false, error: '服务器配置不存在,需要重载配置' };
536
228
  }
229
+ return { success: false, error: '服务器配置不存在,需要重载配置' };
537
230
  } catch (err) {
538
231
  log.error(`[MCP] Enabling failed:`, err.message);
539
232
  return { success: false, error: `开启失败: ${err.message}` };
540
233
  }
541
234
  } else {
542
- // 禁用服务器
543
- if (!clientInfo) {
544
- return { success: false, error: `MCP server '${server}' not found` };
545
- }
546
- // log.info(` Disabling MCP server: ${server}`);
547
- try {
548
- clientInfo.client.close?.();
549
- } catch (e) {}
550
- // 从 clients 中移除
235
+ if (!clientInfo) return { success: false, error: `MCP server '${server}' not found` };
236
+ try { clientInfo.client.close?.(); } catch (e) { /* ignore */ }
551
237
  this._clients.delete(server);
552
- // 移除该服务器注册的工具
553
238
  for (const key of Object.keys(this.tools)) {
554
- if (key.startsWith(`${server}_`)) {
555
- delete this.tools[key];
556
- }
239
+ if (key.startsWith(`${server}_`)) delete this.tools[key];
557
240
  }
558
- // 保存 enabled 状态到配置文件
559
241
  await this._saveMCPServerEnabled(server, false);
560
242
  this._refreshAllAgentsMCPPrompt(this._framework);
561
243
  return { success: true, data: `MCP 服务器 '${server}' 已关闭` };
562
244
  }
563
-
564
- return { success: true, data: `MCP 服务器 '${server}' 状态未变化` };
565
245
  },
566
246
  });
567
247
 
568
- // 监听 agent 创建事件,附加 MCP 信息到系统提示词
569
248
  framework.on('agent:created', (agent) => {
570
- if (!this._mainAgent) {
571
- this._mainAgent = agent;
572
- }
249
+ if (!this._mainAgent) this._mainAgent = agent;
573
250
  this._refreshAgentMCPPrompt(agent);
574
251
  });
575
252
 
576
- // 等待框架就绪后,刷新所有已有 agent 的 MCP 提示词
577
253
  if (framework._ready) {
578
254
  this._refreshAllAgentsMCPPrompt(framework);
579
255
  } else {
@@ -585,515 +261,64 @@ class MCPExecutorPlugin extends Plugin {
585
261
  return this;
586
262
  }
587
263
 
588
- /**
589
- * 刷新所有 agent 的 MCP 提示词(包括子 agent)
590
- */
591
264
  _refreshAllAgentsMCPPrompt(framework) {
592
265
  const visited = new Set();
593
-
594
266
  const traverse = (agent) => {
595
267
  if (!agent || visited.has(agent)) return;
596
268
  visited.add(agent);
597
- //this._refreshAgentMCPPrompt(agent);
598
-
599
- // 递归处理子 agent
600
269
  const subAgents = agent.getSubAgents?.() || agent._subAgents || new Map();
601
- for (const [name, subAgentInfo] of subAgents) {
602
- traverse(subAgentInfo.agent);
603
- }
270
+ for (const [, subAgentInfo] of subAgents) traverse(subAgentInfo.agent);
604
271
  };
605
-
606
- for (const agent of framework._agents || []) {
607
- traverse(agent);
608
- }
272
+ for (const agent of framework._agents || []) traverse(agent);
609
273
  }
610
274
 
611
- /**
612
- * 刷新单个 agent 的 MCP 提示词
613
- */
614
275
  _refreshAgentMCPPrompt(agent) {
615
- // 检查是否已刷新过(通过检查系统提示词是否已包含 MCP 描述)
616
276
  const existingPrompt = (typeof agent.getOriginalPrompt === 'function' ? agent.getOriginalPrompt() : agent._originalPrompt) || '';
617
- if (existingPrompt.includes('【MCP Servers】')) {
618
- return;
619
- }
620
-
621
- // const mcpDesc = this._buildMCPServersDescription();
622
- // if (!mcpDesc) return;
623
-
624
- // 将 MCP 描述追加到系统提示词
625
- //agent.setSystemPrompt(existingPrompt + '\n\n' + mcpDesc);
626
- }
627
-
628
- /**
629
- * 从 inputSchema 中提取实际 schema
630
- */
631
- _extractSchema(inputSchema) {
632
- if (!inputSchema) return {};
633
- // 处理 {jsonSchema: {...}} 格式
634
- if (inputSchema.jsonSchema) return inputSchema.jsonSchema;
635
- // 处理 {inputSchema: {...}} 格式 (某些 MCP 服务器返回这种格式)
636
- if (inputSchema.inputSchema) return inputSchema.inputSchema;
637
- // 处理直接是 schema 的格式
638
- return inputSchema;
639
- }
640
-
641
- /**
642
- * 构建 MCP 服务器描述
643
- */
644
- _buildMCPServersDescription() {
645
- if (this._clients.size === 0) {
646
- return '';
647
- }
648
-
649
- let desc = '## 【MCP Servers】 工具合集\n\n';
650
- desc += 'MCP 服务器工具已注册为 `服务器_工具名` 格式(如 github_search)。\n';
651
- desc += '通过 `ext_call` 调用:\n\n';
652
-
653
- for (const [serverName, info] of this._clients) {
654
- desc += `### ${serverName}\n\n`;
655
- for (const tool of info.tools) {
656
- const fullName = `${serverName}_${tool.name}`;
657
- desc += `- **${fullName}**: ${tool.description || '无描述'}\n`;
658
-
659
- // 使用 zodSchemaToMarkdown 生成参数文档
660
- const rawSchema = tool.inputSchema;
661
- const schema = this._extractSchema(rawSchema);
662
- if (schema) {
663
- try {
664
- let schemaResult = null;
665
- let zodSchemaForFallback = null; // 保存 Zod schema 供 fallback 使用
666
- // 检查是否是 Zod schema (有 shape 方法或 _def 属性)
667
- const isZodSchema =
668
- typeof schema.shape === 'function' || (schema._def && schema._def.typeName);
669
- if (isZodSchema) {
670
- // Zod schema 直接转换
671
- schemaResult = zodSchemaToMarkdown(schema);
672
- zodSchemaForFallback = schema;
673
- } else if (schema.jsonSchema) {
674
- // JSON Schema 格式,尝试转换为 Zod schema
675
- const zodSchema = this._jsonSchemaToZod(schema.jsonSchema || schema);
676
- if (zodSchema) {
677
- schemaResult = zodSchemaToMarkdown(zodSchema);
678
- zodSchemaForFallback = zodSchema;
679
- }
680
- } else if (schema.properties) {
681
- // 已经是对象格式
682
- const zodSchema = this._jsonSchemaToZod(schema);
683
- if (zodSchema) {
684
- schemaResult = zodSchemaToMarkdown(zodSchema);
685
- zodSchemaForFallback = zodSchema;
686
- }
687
- }
688
-
689
- // 检查是否包含未解析的原始 Zod 类型名或只有类型信息没有参数
690
- if (schemaResult) {
691
- const hasUnparsedTypes = /\|.*\bZod\w+\b.*\|/.test(schemaResult);
692
- // 检查是否只有类型信息而没有实际参数(如 "Type: Object" 或 "Type: ZodAny")
693
- const hasOnlyType =
694
- /^-\s+\w+:\s+.+\n?$/.test(schemaResult.trim()) && !schemaResult.includes('`');
695
- if (hasUnparsedTypes || hasOnlyType) {
696
- // zodSchemaToMarkdown 未正确解析,使用 fallback
697
- desc += this._fallbackParamsDesc(zodSchemaForFallback || schema);
698
- } else {
699
- desc += `**参数:**\n\n`;
700
- desc += schemaResult + '\n\n';
701
- }
702
- }
703
- } catch (e) {
704
- // 如果转换失败,使用 fallback
705
- try {
706
- desc += this._fallbackParamsDesc(zodSchemaForFallback || schema);
707
- } catch (e2) {
708
- // fallback 也失败,忽略参数描述
709
- }
710
- }
711
- }
712
- }
713
- desc += '\n';
714
- }
715
-
716
- desc += '**调用格式:**\n';
717
- desc += '```\next_call({ plugin: "mcp", tool: "服务器_工具名", args: {参数} })\n';
718
- desc += '```\n';
719
- return desc.trim();
720
- }
721
-
722
- _bindMcpParamsDesc(rawSchema) {
723
- let desc = '';
724
- const schema = this._extractSchema(rawSchema);
725
- if (!schema) return desc;
726
- try {
727
- let schemaResult = null;
728
- let zodSchemaForFallback = null; // 保存 Zod schema 供 fallback 使用
729
- // 检查是否是 Zod schema (有 shape 方法或 _def 属性)
730
- const isZodSchema =
731
- typeof schema.shape === 'function' || (schema._def && schema._def.typeName);
732
- if (isZodSchema) {
733
- // Zod schema 直接转换
734
- schemaResult = zodSchemaToMarkdown(schema);
735
- zodSchemaForFallback = schema;
736
- } else if (schema.jsonSchema) {
737
- // JSON Schema 格式,尝试转换为 Zod schema
738
- const zodSchema = this._jsonSchemaToZod(schema.jsonSchema || schema);
739
- if (zodSchema) {
740
- schemaResult = zodSchemaToMarkdown(zodSchema);
741
- zodSchemaForFallback = zodSchema;
742
- }
743
- } else if (schema.properties) {
744
- // 已经是对象格式
745
- const zodSchema = this._jsonSchemaToZod(schema);
746
- if (zodSchema) {
747
- schemaResult = zodSchemaToMarkdown(zodSchema);
748
- zodSchemaForFallback = zodSchema;
749
- }
750
- }
751
-
752
- // 检查是否包含未解析的原始 Zod 类型名或只有类型信息没有参数
753
- if (schemaResult) {
754
- const hasUnparsedTypes = /\|.*\bZod\w+\b.*\|/.test(schemaResult);
755
- // 检查是否只有类型信息而没有实际参数(如 "Type: Object" 或 "Type: ZodAny")
756
- const hasOnlyType =
757
- /^-\s+\w+:\s+.+\n?$/.test(schemaResult.trim()) && !schemaResult.includes('`');
758
- if (hasUnparsedTypes || hasOnlyType) {
759
- // zodSchemaToMarkdown 未正确解析,使用 fallback
760
- desc += this._fallbackParamsDesc(zodSchemaForFallback || schema);
761
- } else {
762
- desc += `**参数:**\n\n`;
763
- desc += schemaResult + '\n\n';
764
- }
765
- }
766
- } catch (e) {
767
- // 如果转换失败,使用 fallback
768
- try {
769
- desc += this._fallbackParamsDesc(zodSchemaForFallback || schema);
770
- } catch (e2) {
771
- // fallback 也失败,忽略参数描述
772
- }
773
- }
774
- return desc;
277
+ if (existingPrompt.includes('【MCP Servers】')) return;
775
278
  }
776
279
 
777
- /**
778
- * 将 JSON Schema 转换为 Zod schema
779
- */
780
280
  _jsonSchemaToZod(jsonSchema) {
781
- if (!jsonSchema || !jsonSchema.properties) {
782
- return null;
783
- }
784
-
785
- try {
786
- const shape = {};
787
- const properties = jsonSchema.properties;
788
- const required = jsonSchema.required || [];
789
-
790
- for (const [key, prop] of Object.entries(properties)) {
791
- shape[key] = this._jsonSchemaPropToZod(prop, required.includes(key));
792
- }
793
-
794
- return z.object(shape);
795
- } catch (e) {
796
- return null;
797
- }
798
- }
799
-
800
- /**
801
- * 将 JSON Schema 属性转换为 Zod 类型
802
- */
803
- _jsonSchemaPropToZod(prop, isRequired) {
804
- if (prop.$ref) {
805
- return isRequired ? z.any() : z.any().optional();
806
- }
807
-
808
- if (prop.enum) {
809
- let zodType = z.string().enum(prop.enum);
810
- if (prop.nullable) {
811
- zodType = zodType.nullable();
812
- }
813
- return isRequired ? zodType : zodType.optional();
814
- }
815
-
816
- const type = prop.type || 'string';
817
-
818
- switch (type) {
819
- case 'string':
820
- return isRequired ? z.string() : z.string().optional();
821
- case 'number':
822
- case 'integer':
823
- return isRequired ? z.number() : z.number().optional();
824
- case 'boolean':
825
- return isRequired ? z.boolean() : z.boolean().optional();
826
- case 'array':
827
- return isRequired ? z.array(z.any()) : z.array(z.any()).optional();
828
- case 'object':
829
- if (prop.properties) {
830
- const nested = {};
831
- for (const [k, v] of Object.entries(prop.properties)) {
832
- nested[k] = this._jsonSchemaPropToZod(v, prop.required?.includes(k) || false);
833
- }
834
- return isRequired ? z.object(nested) : z.object(nested).optional();
835
- }
836
- return isRequired ? z.record(z.any()) : z.record(z.any()).optional();
837
- default:
838
- return isRequired ? z.any() : z.any().optional();
839
- }
840
- }
841
-
842
- /**
843
- * 回退的参数描述(当 zodSchemaToMarkdown 失败时)
844
- */
845
- _fallbackParamsDesc(schema) {
846
- // 优先尝试 JSON Schema 格式
847
- const props = schema.properties || {};
848
- const required = schema.required || [];
849
-
850
- if (Object.keys(props).length > 0) {
851
- let desc = '**参数:**\n\n';
852
- for (const [key, prop] of Object.entries(props)) {
853
- const isRequired = required.includes(key);
854
- const type = prop.type || 'any';
855
- const desc_text = prop.description || '';
856
- desc += `- \`${key}\`${isRequired ? ' (必填)' : ''}: ${type} ${desc_text}\n`;
857
- }
858
- return desc + '\n';
859
- }
860
-
861
- // 尝试 Zod schema 格式
862
- if (typeof schema.shape === 'function' || (schema._def && schema._def.shape)) {
863
- const shape = typeof schema.shape === 'function' ? schema.shape() : schema._def.shape();
864
- if (shape && typeof shape === 'object') {
865
- let desc = '**参数:**\n\n';
866
- for (const [key, field] of Object.entries(shape)) {
867
- const typeInfo = this._extractZodFieldType(field);
868
- const isOptional = typeInfo.isOptional ? ' (可选)' : ' (必填)';
869
- desc += `- \`${key}\`${isOptional}: ${typeInfo.type}\n`;
870
- }
871
- return desc + '\n';
872
- }
873
- }
874
-
875
- return '';
876
- }
877
-
878
- /**
879
- * 从 Zod schema 字段提取类型信息
880
- */
881
- _extractZodFieldType(field) {
882
- if (!field) {
883
- return { type: 'unknown', isOptional: false, description: '' };
884
- }
885
-
886
- const typeName = field._def?.typeName || field.constructor?.name || '';
887
- const description = field.description || field._def?.description || '';
888
-
889
- switch (typeName) {
890
- case 'ZodOptional': {
891
- const inner = field._def?.innerType;
892
- if (!inner) return { type: 'optional', isOptional: true, description };
893
- return this._extractZodFieldType(inner);
894
- }
895
- case 'ZodArray': {
896
- const inner = field._def?.type;
897
- if (!inner) return { type: 'array', isOptional: false, description };
898
- const innerInfo = this._extractZodFieldType(inner);
899
- return { type: `${innerInfo.type}[]`, isOptional: false, description };
900
- }
901
- case 'ZodObject': {
902
- const shapeFn = field._def?.shape;
903
- if (typeof shapeFn !== 'function')
904
- return { type: 'object', isOptional: false, description };
905
- const shape = shapeFn();
906
- if (!shape) return { type: 'object', isOptional: false, description };
907
- const props = Object.keys(shape).join(', ');
908
- return { type: `object{${props}}`, isOptional: false, description };
909
- }
910
- case 'ZodString':
911
- return { type: 'string', isOptional: false, description };
912
- case 'ZodNumber':
913
- return { type: 'number', isOptional: false, description };
914
- case 'ZodBoolean':
915
- return { type: 'boolean', isOptional: false, description };
916
- case 'ZodEnum': {
917
- const values = field._def?.values;
918
- if (!values) return { type: 'enum', isOptional: false, description };
919
- return {
920
- type: values.map((v) => `"${v}"`).join(' | '),
921
- isOptional: false,
922
- description,
923
- };
924
- }
925
- case 'ZodUnion': {
926
- const options = field._def?.options;
927
- if (!options) return { type: 'union', isOptional: false, description };
928
- const types = options.map((opt) => this._extractZodFieldType(opt).type);
929
- return { type: `union(${types.join(' | ')})`, isOptional: false, description };
930
- }
931
- case 'ZodLiteral':
932
- return { type: JSON.stringify(field._def?.value), isOptional: false, description };
933
- case 'ZodRecord': {
934
- const keyType = this._extractZodFieldType(field._def?.keyType);
935
- const valueType = this._extractZodFieldType(field._def?.valueType);
936
- return {
937
- type: `record<${keyType?.type || 'any'}, ${valueType?.type || 'any'}>`,
938
- isOptional: false,
939
- description,
940
- };
941
- }
942
- case 'ZodMap': {
943
- const keyType = this._extractZodFieldType(field._def?.keyType);
944
- const valueType = this._extractZodFieldType(field._def?.valueType);
945
- return {
946
- type: `map<${keyType?.type || 'any'}, ${valueType?.type || 'any'}>`,
947
- isOptional: false,
948
- description,
949
- };
950
- }
951
- case 'ZodSet': {
952
- const valueType = this._extractZodFieldType(field._def?.valueType);
953
- return { type: `set<${valueType?.type || 'any'}>`, isOptional: false, description };
954
- }
955
- case 'ZodAny':
956
- return { type: 'any', isOptional: false, description };
957
- case 'ZodUnknown':
958
- return { type: 'unknown', isOptional: false, description };
959
- case 'ZodNever':
960
- return { type: 'never', isOptional: false, description };
961
- case 'ZodNull':
962
- return { type: 'null', isOptional: false, description };
963
- case 'ZodUndefined':
964
- return { type: 'undefined', isOptional: false, description };
965
- case 'ZodDate':
966
- return { type: 'date', isOptional: false, description };
967
- case 'ZodBigInt':
968
- return { type: 'bigint', isOptional: false, description };
969
- case 'ZodFunction':
970
- return { type: 'function', isOptional: false, description };
971
- case 'ZodLazy':
972
- return { type: 'lazy', isOptional: false, description };
973
- case 'ZodNativeEnum':
974
- return { type: 'nativeEnum', isOptional: false, description };
975
- case 'ZodDefault': {
976
- const inner = field._def?.innerType;
977
- if (!inner) return { type: 'default', isOptional: false, description };
978
- const innerInfo = this._extractZodFieldType(inner);
979
- return { type: innerInfo.type, isOptional: false, description };
980
- }
981
- case 'ZodEffects':
982
- return { type: 'effects', isOptional: false, description };
983
- case 'ZodPipeline':
984
- return { type: 'pipeline', isOptional: false, description };
985
- default:
986
- return {
987
- type: typeName.replace('Zod', '').toLowerCase() || 'any',
988
- isOptional: false,
989
- description,
990
- };
991
- }
281
+ return jsonSchemaToZod(jsonSchema);
992
282
  }
993
283
 
994
284
  _getParentAgent() {
995
- // 优先返回已缓存的主 agent
996
- if (this._mainAgent) {
997
- return this._mainAgent;
998
- }
999
-
1000
- // 尝试从 framework 获取
285
+ if (this._mainAgent) return this._mainAgent;
1001
286
  if (this._framework._mainAgent) {
1002
287
  this._mainAgent = this._framework._mainAgent;
1003
288
  return this._mainAgent;
1004
289
  }
1005
-
1006
- // 查找最后一个创建的 agent 作为父 agent
1007
290
  const agents = this._framework._agents || [];
1008
291
  if (agents.length > 0) {
1009
292
  this._mainAgent = agents[agents.length - 1];
1010
293
  return this._mainAgent;
1011
294
  }
1012
-
1013
295
  return null;
1014
296
  }
1015
297
 
1016
- /**
1017
- * 带超时的 Promise
1018
- */
1019
- _withTimeout(promise, ms, name) {
1020
- return Promise.race([
1021
- promise,
1022
- new Promise((_, reject) =>
1023
- setTimeout(() => reject(new Error(`${name} timeout (${ms}ms)`)), ms)
1024
- ),
1025
- ]);
1026
- }
1027
-
1028
- /**
1029
- * 添加 MCP 服务器
1030
- */
1031
298
  async addServer(config) {
1032
- const {
1033
- name,
1034
- command,
1035
- args = [],
1036
- env,
1037
- url,
1038
- type,
1039
- headers = {},
1040
- authProvider,
1041
- enabled,
1042
- } = config;
299
+ const { name, command, args = [], env, url, type, headers = {}, authProvider, enabled } = config;
1043
300
  log.debug(`[MCP] addServer called: ${name}, enabled=${enabled}, hasClient=${this._clients.has(name)}`);
1044
301
 
1045
- if (this._clients.has(name)) {
1046
- log.warn(` Server '${name}' already exists, skipping`);
1047
- return { success: false, error: `Server '${name}' already connected` };
1048
- }
302
+ if (this._clients.has(name)) return { success: false, error: `Server '${name}' already connected` };
303
+ if (enabled === false) return { success: false, error: `Server '${name}' is disabled` };
1049
304
 
1050
- // 如果 enabled === false,跳过连接(用于 mcp_set_enabled 禁用后再开启的场景)
1051
- if (enabled === false) {
1052
- // log.info(` Server '${name}' is disabled, skipping connection`);
1053
- return { success: false, error: `Server '${name}' is disabled` };
1054
- }
305
+ this._serverConfigs.set(name, { name, command, args, env, url, type, headers, authProvider, enabled });
1055
306
 
1056
- // 保存配置,用于重新启用
1057
- this._serverConfigs.set(name, {
1058
- name,
1059
- command,
1060
- args,
1061
- env,
1062
- url,
1063
- type,
1064
- headers,
1065
- authProvider,
1066
- enabled,
1067
- });
1068
- // log.info(` Connecting to ${name}...`);
1069
307
  let client;
1070
308
  try {
1071
309
  if (['sse', 'http'].includes(type)) {
1072
- client = await this._withTimeout(
310
+ client = await withTimeout(
1073
311
  createMCPClient({
1074
- transport: {
1075
- type: type,
1076
- url: url,
1077
- headers: headers,
1078
- authProvider: authProvider,
1079
- redirect: 'error',
1080
- env: env,
1081
- },
312
+ transport: { type, url, headers, authProvider, redirect: 'error', env },
1082
313
  }),
1083
- 10000,
1084
- `createMCPClient ${name}`
314
+ 10000, `createMCPClient ${name}`
1085
315
  );
1086
316
  } else {
1087
- client = await this._withTimeout(
317
+ client = await withTimeout(
1088
318
  createMCPClient({
1089
- transport: new StdioClientTransport({
1090
- command: command,
1091
- args: args,
1092
- env: env,
1093
- }),
319
+ transport: new StdioClientTransport({ command, args, env }),
1094
320
  }),
1095
- 10000,
1096
- `createMCPClient ${name}`
321
+ 10000, `createMCPClient ${name}`
1097
322
  );
1098
323
  }
1099
324
  } catch (err) {
@@ -1101,188 +326,97 @@ class MCPExecutorPlugin extends Plugin {
1101
326
  return { success: false, error: `创建客户端失败: ${err.message}` };
1102
327
  }
1103
328
 
1104
- // 获取 MCP 服务器的工具列表
1105
329
  let mcpTools;
1106
330
  try {
1107
- mcpTools = await this._withTimeout(client.tools(), 10000, `client.tools ${name}`);
331
+ mcpTools = await withTimeout(client.tools(), 10000, `client.tools ${name}`);
1108
332
  } catch (err) {
1109
333
  log.error(` Failed to get tools from '${name}':`, err.message);
1110
334
  client.close?.();
1111
335
  return { success: false, error: `获取工具列表失败: ${err.message}` };
1112
336
  }
1113
337
 
1114
- // 提取工具信息和实际工具对象
1115
338
  const toolsInfo = [];
1116
- const toolObjects = {}; // 缓存实际工具对象
339
+ const toolObjects = {};
1117
340
  if (mcpTools && typeof mcpTools === 'object') {
1118
341
  for (const [toolName, tool] of Object.entries(mcpTools)) {
1119
342
  if (!tool) continue;
1120
- toolObjects[toolName] = tool; // 保存实际工具对象
1121
- toolsInfo.push({
1122
- name: toolName,
1123
- description: tool.description || '',
1124
- inputSchema: tool.inputSchema || {},
1125
- });
343
+ toolObjects[toolName] = tool;
344
+ toolsInfo.push({ name: toolName, description: tool.description || '', inputSchema: tool.inputSchema || {} });
1126
345
 
1127
- // 注册为 server_toolname 格式,供 ext_call 使用
1128
346
  const registeredName = `${name}_${toolName}`;
1129
347
  this.tools[registeredName] = {
1130
348
  name: registeredName,
1131
349
  description: `[${name}] ${tool.description || ''}`,
1132
350
  inputSchema: tool.inputSchema || {},
1133
351
  execute: async (args) => {
1134
- // 路由到 mcp_call
1135
352
  return await this._framework.executeTool('mcp_call', {
1136
- server: name,
1137
- tool: toolName,
1138
- args_json: JSON.stringify(args || {}),
353
+ server: name, tool: toolName, args_json: JSON.stringify(args || {}),
1139
354
  });
1140
355
  },
1141
356
  };
1142
357
  }
1143
358
  }
1144
359
 
1145
- // 保存客户端、工具信息和实际工具对象
1146
- this._clients.set(name, {
1147
- client,
1148
- tools: toolsInfo,
1149
- toolObjects, // 实际工具对象,供 mcp_call 直接调用
1150
- enabled: config.enabled !== false,
1151
- });
1152
-
1153
- log.debug(
1154
- ` Added server '${name}' with ${toolsInfo.length} tools (ext_call names: ${Object.keys(
1155
- this.tools
1156
- )
1157
- .filter((k) => k.startsWith(name + '_'))
1158
- .join(', ')})`
1159
- );
360
+ this._clients.set(name, { client, tools: toolsInfo, toolObjects, enabled: config.enabled !== false });
1160
361
 
362
+ log.debug(` Added server '${name}' with ${toolsInfo.length} tools`);
1161
363
  return { success: true, name, toolsCount: toolsInfo.length };
1162
364
  }
1163
365
 
1164
- /**
1165
- * 移除 MCP 服务器
1166
- */
1167
366
  async removeServer(name) {
1168
367
  const clientInfo = this._clients.get(name);
1169
368
  if (clientInfo) {
1170
369
  try {
1171
- // 添加超时保护,避免 close/destroy 阻塞
1172
370
  const closePromise = clientInfo.client.close?.() || clientInfo.client.destroy?.();
1173
371
  if (closePromise && typeof closePromise.then === 'function') {
1174
- await Promise.race([
1175
- closePromise,
1176
- new Promise((resolve) => setTimeout(() => resolve(), 3000)),
1177
- ]);
372
+ await Promise.race([closePromise, new Promise((resolve) => setTimeout(() => resolve(), 3000))]);
1178
373
  }
1179
- } catch (e) {
1180
- // ignore
1181
- }
1182
-
1183
- // 清理已注册的工具
374
+ } catch (e) { /* ignore */ }
1184
375
  for (const toolName of Object.keys(this.tools)) {
1185
- if (toolName.startsWith(name + '_')) {
1186
- delete this.tools[toolName];
1187
- }
376
+ if (toolName.startsWith(name + '_')) delete this.tools[toolName];
1188
377
  }
1189
-
1190
378
  this._clients.delete(name);
1191
379
  }
1192
380
  }
1193
381
 
1194
- /**
1195
- * 获取所有服务器
1196
- */
1197
382
  getServers() {
1198
- return Array.from(this._clients.entries()).map(([name, info]) => ({
1199
- name,
1200
- tools: info.tools.map((t) => t.name),
1201
- }));
383
+ return Array.from(this._clients.entries()).map(([name, info]) => ({ name, tools: info.tools.map((t) => t.name) }));
1202
384
  }
1203
385
 
1204
386
  reload(framework) {
1205
- // log.info(' Reloading...');
1206
387
  this._framework = framework;
1207
388
  }
1208
389
 
1209
- /**
1210
- * 重新加载 MCP 配置
1211
- * @param {Object} config - 新的配置对象,包含 mcpServers
1212
- */
1213
390
  async reloadConfig(config) {
1214
- // log.info(' reloadConfig start');
1215
391
  const newServers = config?.mcpServers || {};
1216
392
 
1217
- // 1. 先快速清理所有旧服务器(不等待 close 完成)
1218
- // log.info(' Clearing old servers...');
1219
- for (const [name, clientInfo] of this._clients) {
1220
- try {
1221
- // 不等待 close,直接删除
1222
- clientInfo.client.close?.()?.catch?.(() => {});
1223
- } catch (e) {}
393
+ for (const [, clientInfo] of this._clients) {
394
+ try { clientInfo.client.close?.()?.catch?.(() => {}); } catch (e) { /* ignore */ }
1224
395
  }
1225
396
  this._clients.clear();
1226
- // 清理服务器配置
1227
397
  this._serverConfigs.clear();
1228
398
 
1229
- // 2. 并行添加新服务器
1230
- // log.info(' Adding new servers...');
1231
399
  const addPromises = Object.entries(newServers).map(async ([name, serverConfig]) => {
1232
400
  if (serverConfig.enabled === false) {
1233
- // log.info(` Skipping disabled server: ${name}`);
1234
- // 仍保存配置,以便后续动态启用
1235
401
  this._serverConfigs.set(name, { name, ...serverConfig });
1236
402
  return;
1237
403
  }
1238
- const normalizedConfig = {
1239
- name,
1240
- type: serverConfig.type,
1241
- url: serverConfig.url,
1242
- headers: serverConfig.headers,
1243
- authProvider: serverConfig.authProvider,
1244
- command: serverConfig.command,
1245
- args: serverConfig.args,
1246
- env: serverConfig.env,
1247
- enabled: true,
1248
- };
1249
- await this.addServer(normalizedConfig);
404
+ await this.addServer({ name, ...serverConfig, enabled: true });
1250
405
  });
1251
406
 
1252
407
  await Promise.all(addPromises);
1253
- // log.info(' reloadConfig complete');
1254
408
 
1255
- // 3. 刷新所有 agent 的 MCP 提示词
1256
- if (this._framework) {
1257
- this._refreshAllAgentsMCPPrompt(this._framework);
1258
- }
409
+ if (this._framework) this._refreshAllAgentsMCPPrompt(this._framework);
1259
410
  }
1260
411
 
1261
- /**
1262
- * 保存 MCP 服务器的 enabled 状态到配置文件
1263
- */
1264
412
  async _saveMCPServerEnabled(serverName, enabled) {
1265
- const fs = require('fs');
1266
- const path = require('path');
1267
413
  const configPath = path.resolve('.foliko/mcp_config.json');
1268
-
1269
414
  try {
1270
- if (!fs.existsSync(configPath)) {
1271
- log.warn(` MCP config file not found: ${configPath}`);
1272
- return false;
1273
- }
1274
-
1275
- const configContent = fs.readFileSync(configPath, 'utf8');
1276
- const config = JSON.parse(configContent);
1277
-
1278
- if (!config.mcpServers || !config.mcpServers[serverName]) {
1279
- log.warn(` Server '${serverName}' not found in MCP config`);
1280
- return false;
1281
- }
1282
-
415
+ if (!fs.existsSync(configPath)) return false;
416
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
417
+ if (!config.mcpServers || !config.mcpServers[serverName]) return false;
1283
418
  config.mcpServers[serverName].enabled = enabled;
1284
419
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
1285
- // log.info(` Saved enabled=${enabled} for server '${serverName}' to config`);
1286
420
  return true;
1287
421
  } catch (err) {
1288
422
  log.error(` Failed to save MCP config:`, err.message);
@@ -1291,25 +425,26 @@ class MCPExecutorPlugin extends Plugin {
1291
425
  }
1292
426
 
1293
427
  async uninstall(framework) {
1294
- // 断开所有 MCP 连接
1295
- for (const [name, clientInfo] of this._clients) {
428
+ for (const [, clientInfo] of this._clients) {
1296
429
  try {
1297
430
  const closePromise = clientInfo.client.close?.() || clientInfo.client.destroy?.();
1298
431
  if (closePromise && typeof closePromise.then === 'function') {
1299
- await Promise.race([
1300
- closePromise,
1301
- new Promise((resolve) => setTimeout(() => resolve(), 3000)),
1302
- ]);
432
+ await Promise.race([closePromise, new Promise((resolve) => setTimeout(() => resolve(), 3000))]);
1303
433
  }
1304
- } catch (e) {
1305
- // ignore
1306
- }
434
+ } catch (e) { /* ignore */ }
1307
435
  }
1308
436
  this._clients.clear();
1309
437
  this._framework = null;
1310
438
  }
1311
439
  }
1312
440
 
441
+ function withTimeout(promise, ms, name) {
442
+ return Promise.race([
443
+ promise,
444
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`${name} timeout (${ms}ms)`)), ms)),
445
+ ]);
446
+ }
447
+
1313
448
  module.exports = {
1314
449
  MCPExecutorPlugin,
1315
450
  MCPClientWrapper,