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.
- package/.claude/settings.local.json +30 -0
- package/22.txt +10 -0
- package/README.md +218 -0
- package/SPEC.md +452 -0
- package/cli/bin/foliko.js +12 -0
- package/cli/src/commands/chat.js +75 -0
- package/cli/src/index.js +64 -0
- package/cli/src/ui/chat-ui.js +272 -0
- package/cli/src/utils/ansi.js +40 -0
- package/cli/src/utils/markdown.js +296 -0
- package/docs/quick-reference.md +131 -0
- package/docs/user-manual.md +1205 -0
- package/examples/basic.js +110 -0
- package/examples/bootstrap.js +93 -0
- package/examples/mcp-example.js +53 -0
- package/examples/skill-example.js +49 -0
- package/examples/workflow.js +158 -0
- package/package.json +36 -0
- package/plugins/ai-plugin.js +89 -0
- package/plugins/audit-plugin.js +187 -0
- package/plugins/default-plugins.js +412 -0
- package/plugins/file-system-plugin.js +344 -0
- package/plugins/install-plugin.js +93 -0
- package/plugins/python-executor-plugin.js +331 -0
- package/plugins/rules-plugin.js +292 -0
- package/plugins/scheduler-plugin.js +426 -0
- package/plugins/session-plugin.js +343 -0
- package/plugins/shell-executor-plugin.js +196 -0
- package/plugins/storage-plugin.js +237 -0
- package/plugins/subagent-plugin.js +395 -0
- package/plugins/think-plugin.js +329 -0
- package/plugins/tools-plugin.js +114 -0
- package/skills/mcp-usage/SKILL.md +198 -0
- package/skills/vb-agent-dev/AGENTS.md +162 -0
- package/skills/vb-agent-dev/SKILL.md +370 -0
- package/src/capabilities/index.js +11 -0
- package/src/capabilities/skill-manager.js +319 -0
- package/src/capabilities/workflow-engine.js +401 -0
- package/src/core/agent-chat.js +311 -0
- package/src/core/agent.js +573 -0
- package/src/core/framework.js +255 -0
- package/src/core/index.js +19 -0
- package/src/core/plugin-base.js +205 -0
- package/src/core/plugin-manager.js +392 -0
- package/src/core/provider.js +108 -0
- package/src/core/tool-registry.js +134 -0
- package/src/core/tool-router.js +216 -0
- package/src/executors/executor-base.js +58 -0
- package/src/executors/mcp-executor.js +728 -0
- package/src/index.js +37 -0
- package/src/utils/event-emitter.js +97 -0
- package/test-chat.js +129 -0
- package/test-mcp.js +79 -0
- 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
|
+
}
|