flex-mcp 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/LICENSE +21 -0
- package/README.md +154 -0
- package/dist/api/auth.d.ts +6 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/auth.js +111 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/configs.d.ts +6 -0
- package/dist/api/configs.d.ts.map +1 -0
- package/dist/api/configs.js +693 -0
- package/dist/api/configs.js.map +1 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +24 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/mcp.d.ts +7 -0
- package/dist/api/mcp.d.ts.map +1 -0
- package/dist/api/mcp.js +46 -0
- package/dist/api/mcp.js.map +1 -0
- package/dist/api/middleware.d.ts +25 -0
- package/dist/api/middleware.d.ts.map +1 -0
- package/dist/api/middleware.js +65 -0
- package/dist/api/middleware.js.map +1 -0
- package/dist/api/prompts.d.ts +6 -0
- package/dist/api/prompts.d.ts.map +1 -0
- package/dist/api/prompts.js +260 -0
- package/dist/api/prompts.js.map +1 -0
- package/dist/api/resources.d.ts +6 -0
- package/dist/api/resources.d.ts.map +1 -0
- package/dist/api/resources.js +159 -0
- package/dist/api/resources.js.map +1 -0
- package/dist/api/tables.d.ts +15 -0
- package/dist/api/tables.d.ts.map +1 -0
- package/dist/api/tables.js +702 -0
- package/dist/api/tables.js.map +1 -0
- package/dist/api/users.d.ts +6 -0
- package/dist/api/users.d.ts.map +1 -0
- package/dist/api/users.js +96 -0
- package/dist/api/users.js.map +1 -0
- package/dist/auth/index.d.ts +15 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +126 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +288 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +54 -0
- package/dist/config.js.map +1 -0
- package/dist/db/index.d.ts +11 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +164 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +646 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +73 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +153 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/aggregator.d.ts +78 -0
- package/dist/mcp/aggregator.d.ts.map +1 -0
- package/dist/mcp/aggregator.js +1266 -0
- package/dist/mcp/aggregator.js.map +1 -0
- package/dist/mcp/component-loader.d.ts +17 -0
- package/dist/mcp/component-loader.d.ts.map +1 -0
- package/dist/mcp/component-loader.js +131 -0
- package/dist/mcp/component-loader.js.map +1 -0
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +107 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/mcp-client.d.ts +53 -0
- package/dist/mcp/mcp-client.d.ts.map +1 -0
- package/dist/mcp/mcp-client.js +418 -0
- package/dist/mcp/mcp-client.js.map +1 -0
- package/dist/mcp/post-sse-transport.d.ts +52 -0
- package/dist/mcp/post-sse-transport.d.ts.map +1 -0
- package/dist/mcp/post-sse-transport.js +375 -0
- package/dist/mcp/post-sse-transport.js.map +1 -0
- package/dist/mcp/service.d.ts +49 -0
- package/dist/mcp/service.d.ts.map +1 -0
- package/dist/mcp/service.js +358 -0
- package/dist/mcp/service.js.map +1 -0
- package/dist/mcp/tool-sync.d.ts +27 -0
- package/dist/mcp/tool-sync.d.ts.map +1 -0
- package/dist/mcp/tool-sync.js +200 -0
- package/dist/mcp/tool-sync.js.map +1 -0
- package/dist/script/compiler.d.ts +15 -0
- package/dist/script/compiler.d.ts.map +1 -0
- package/dist/script/compiler.js +163 -0
- package/dist/script/compiler.js.map +1 -0
- package/dist/script/context.d.ts +11 -0
- package/dist/script/context.d.ts.map +1 -0
- package/dist/script/context.js +786 -0
- package/dist/script/context.js.map +1 -0
- package/dist/script/executor.d.ts +43 -0
- package/dist/script/executor.d.ts.map +1 -0
- package/dist/script/executor.js +126 -0
- package/dist/script/executor.js.map +1 -0
- package/dist/static.d.ts +21 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +95 -0
- package/dist/static.js.map +1 -0
- package/dist/types/index.d.ts +337 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/id.d.ts +5 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +8 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/logger.d.ts +21 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +31 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/prompt-template.d.ts +15 -0
- package/dist/utils/prompt-template.d.ts.map +1 -0
- package/dist/utils/prompt-template.js +82 -0
- package/dist/utils/prompt-template.js.map +1 -0
- package/dist/utils/telemetry.d.ts +45 -0
- package/dist/utils/telemetry.d.ts.map +1 -0
- package/dist/utils/telemetry.js +245 -0
- package/dist/utils/telemetry.js.map +1 -0
- package/dist/utils/variables.d.ts +24 -0
- package/dist/utils/variables.d.ts.map +1 -0
- package/dist/utils/variables.js +45 -0
- package/dist/utils/variables.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,1266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 聚合器
|
|
3
|
+
* 聚合多个 MCP 组件,管理工具和资源
|
|
4
|
+
*/
|
|
5
|
+
import { ScriptExecutor } from '../script/executor.js';
|
|
6
|
+
import { loadComponent } from './component-loader.js';
|
|
7
|
+
import { replaceVariablesInObject, createVariableMap } from '../utils/variables.js';
|
|
8
|
+
import { processPromptTemplate } from '../utils/prompt-template.js';
|
|
9
|
+
import { db } from '../db/index.js';
|
|
10
|
+
import { prompts, mcpComponentTools, mcpConfigs, tableDefinitions } from '../db/schema.js';
|
|
11
|
+
import { eq, and } from 'drizzle-orm';
|
|
12
|
+
import { report } from '../utils/telemetry.js';
|
|
13
|
+
export class MCPAggregator {
|
|
14
|
+
config;
|
|
15
|
+
scriptExecutor;
|
|
16
|
+
components = new Map();
|
|
17
|
+
failedComponents = new Map(); // 记录加载失败的组件
|
|
18
|
+
variableMap;
|
|
19
|
+
mcpClientConfig;
|
|
20
|
+
isInitialized = false; // 标记是否已初始化
|
|
21
|
+
userId; // 用户 ID,用于加载用户自定义的 prompts
|
|
22
|
+
configUpdatedAt; // 配置更新时间,用于检测配置是否已更新
|
|
23
|
+
constructor(config, mcpClientConfig, userId) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
this.mcpClientConfig = mcpClientConfig || null;
|
|
26
|
+
this.userId = userId || config.userId;
|
|
27
|
+
this.scriptExecutor = new ScriptExecutor(config, mcpClientConfig, this);
|
|
28
|
+
this.variableMap = createVariableMap(config.variables);
|
|
29
|
+
this.configUpdatedAt = config.updatedAt || new Date();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 检查是否已初始化
|
|
33
|
+
*/
|
|
34
|
+
get initialized() {
|
|
35
|
+
return this.isInitialized;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 初始化聚合器
|
|
39
|
+
* 注意:不再在初始化时加载组件,组件将在调用工具时按需加载(延迟加载)
|
|
40
|
+
*/
|
|
41
|
+
async initialize() {
|
|
42
|
+
console.log(`[MCP] Initializing aggregator for user ${this.userId}, ${this.config.components.length} components configured`);
|
|
43
|
+
// 不再在初始化时加载组件,组件将在调用工具时按需加载
|
|
44
|
+
// 这样可以避免初始化时查询工具列表,工具列表现在从数据库读取
|
|
45
|
+
console.log(`[MCP] Components will be loaded on-demand when tools are called`);
|
|
46
|
+
// 标记为已初始化
|
|
47
|
+
this.isInitialized = true;
|
|
48
|
+
// 执行初始化脚本
|
|
49
|
+
// 注意:初始化脚本的错误不应该阻止 MCP 连接,只记录错误并继续
|
|
50
|
+
if (this.config.initScript) {
|
|
51
|
+
try {
|
|
52
|
+
await this.scriptExecutor.executeInitScript(this.config.initScript);
|
|
53
|
+
console.log(`[MCP] Init script executed successfully`);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const errorMessage = error.message || String(error);
|
|
57
|
+
console.error(`[MCP] Init script execution failed (non-blocking):`, errorMessage);
|
|
58
|
+
// 不抛出错误,允许 MCP 连接继续
|
|
59
|
+
// 初始化脚本的错误不应该影响主程序运行
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 按需加载组件(延迟加载)
|
|
65
|
+
*/
|
|
66
|
+
async ensureComponentLoaded(componentName, allowDisabled = false) {
|
|
67
|
+
// 如果组件已加载,直接返回
|
|
68
|
+
if (this.components.has(componentName)) {
|
|
69
|
+
return this.components.get(componentName);
|
|
70
|
+
}
|
|
71
|
+
// 查找组件配置
|
|
72
|
+
const component = this.config.components.find(c => c.name === componentName);
|
|
73
|
+
if (!component) {
|
|
74
|
+
throw new Error(`Component ${componentName} not found in config`);
|
|
75
|
+
}
|
|
76
|
+
// 检查组件是否被禁用(如果 allowDisabled 为 false)
|
|
77
|
+
if (!allowDisabled && component.disabled) {
|
|
78
|
+
throw new Error(`Component ${componentName} is disabled`);
|
|
79
|
+
}
|
|
80
|
+
// 替换组件配置中的变量
|
|
81
|
+
const processedComponent = {
|
|
82
|
+
...component,
|
|
83
|
+
disabled: component.disabled,
|
|
84
|
+
env: component.env ? replaceVariablesInObject(component.env, this.variableMap) : undefined,
|
|
85
|
+
config: component.config ? replaceVariablesInObject(component.config, this.variableMap) : undefined,
|
|
86
|
+
};
|
|
87
|
+
// 加载组件
|
|
88
|
+
console.log(`[MCP] Loading component on-demand: ${componentName} (type: ${component.type})`);
|
|
89
|
+
try {
|
|
90
|
+
const loaded = await loadComponent(processedComponent);
|
|
91
|
+
this.components.set(componentName, loaded);
|
|
92
|
+
this.failedComponents.delete(componentName);
|
|
93
|
+
// 上报组件加载统计
|
|
94
|
+
report('component_loaded', {
|
|
95
|
+
component_type: component.type,
|
|
96
|
+
component_name: componentName,
|
|
97
|
+
has_namespace: !!component.namespace,
|
|
98
|
+
tools_count: loaded.tools?.length || 0,
|
|
99
|
+
}, this.userId);
|
|
100
|
+
return loaded;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
const errorMessage = error.message || String(error);
|
|
104
|
+
console.error(`[MCP] Failed to load component ${componentName}:`, errorMessage);
|
|
105
|
+
this.failedComponents.set(componentName, errorMessage);
|
|
106
|
+
// 上报组件加载失败
|
|
107
|
+
report('component_load_failed', {
|
|
108
|
+
component_type: component.type,
|
|
109
|
+
component_name: componentName,
|
|
110
|
+
error_type: error.constructor?.name || 'Unknown',
|
|
111
|
+
}, this.userId);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 生成 explore_table 工具的描述(基于数据库中的表定义)
|
|
117
|
+
*/
|
|
118
|
+
async generateExploreTableDescription() {
|
|
119
|
+
try {
|
|
120
|
+
// 获取当前用户的所有表定义,只包含允许探索的表
|
|
121
|
+
const tables = await db
|
|
122
|
+
.select()
|
|
123
|
+
.from(tableDefinitions)
|
|
124
|
+
.where(and(eq(tableDefinitions.userId, this.userId), eq(tableDefinitions.allowExplore, true) // 只包含允许探索的表
|
|
125
|
+
));
|
|
126
|
+
if (tables.length === 0) {
|
|
127
|
+
return '目前没有可探索的数据表,该工具暂时不可用。';
|
|
128
|
+
}
|
|
129
|
+
let description = '可通过该工具自由探索多个业务相关的数据表,找到需要的信息。使用方式类似 MongoDB 查询语法。\n\n';
|
|
130
|
+
description += '# 使用示例:\n';
|
|
131
|
+
description += ' - 查询: { table: "users", query: { age: { $gt: 18 } } }\n';
|
|
132
|
+
description += ' - 限制返回数量: { table: "users", query: { age: { $gt: 18 }, $limit: 10 } } (默认最多返回5条)\n';
|
|
133
|
+
description += ' - 批量插入: { table: "users", operation: "insert", data: [{ name: "Alice", age: 25 }, { name: "Bob", age: 30 }] }\n\n';
|
|
134
|
+
description += '# 可用表列表:\n';
|
|
135
|
+
for (const table of tables) {
|
|
136
|
+
const tableDef = {
|
|
137
|
+
id: table.id,
|
|
138
|
+
name: table.name,
|
|
139
|
+
description: table.description || undefined,
|
|
140
|
+
columns: JSON.parse(table.columns),
|
|
141
|
+
allowExplore: table.allowExplore !== undefined ? Boolean(table.allowExplore) : true,
|
|
142
|
+
createdAt: table.createdAt,
|
|
143
|
+
updatedAt: table.updatedAt,
|
|
144
|
+
};
|
|
145
|
+
description += `表: ${tableDef.name}\n`;
|
|
146
|
+
if (tableDef.description) {
|
|
147
|
+
description += `表说明: ${tableDef.description}\n`;
|
|
148
|
+
}
|
|
149
|
+
description += '列定义:\n';
|
|
150
|
+
for (const col of tableDef.columns) {
|
|
151
|
+
description += ` - ${col.name}[${col.type}] ${col.description || ''}\n`;
|
|
152
|
+
}
|
|
153
|
+
description += '\n';
|
|
154
|
+
}
|
|
155
|
+
return description;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.error(`[MCP] Failed to generate explore_table description:`, error);
|
|
159
|
+
return '无法加载表定义,该工具暂时不可用。';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 生成 explore_table 工具的执行脚本
|
|
164
|
+
*/
|
|
165
|
+
generateExploreTableScript() {
|
|
166
|
+
return `export default async function(args: { table: string; query?: any; operation?: string; data?: any }, context: any) {
|
|
167
|
+
const { table, query, operation = 'find', data } = args;
|
|
168
|
+
|
|
169
|
+
if (!table || typeof table !== 'string') {
|
|
170
|
+
throw new Error('Table parameter is required and must be a string');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// MongoDB 风格的查询匹配函数
|
|
174
|
+
function matchQuery(row: Record<string, any>, conditions: Record<string, any>): boolean {
|
|
175
|
+
// 处理逻辑操作符
|
|
176
|
+
if (conditions.$and) {
|
|
177
|
+
return conditions.$and.every((cond: Record<string, any>) => matchQuery(row, cond));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (conditions.$or) {
|
|
181
|
+
return conditions.$or.some((cond: Record<string, any>) => matchQuery(row, cond));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (conditions.$not) {
|
|
185
|
+
return !matchQuery(row, conditions.$not);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 处理字段条件
|
|
189
|
+
for (const [field, condition] of Object.entries(conditions)) {
|
|
190
|
+
if (field.startsWith('$')) continue; // 跳过操作符字段
|
|
191
|
+
|
|
192
|
+
const fieldValue = row[field];
|
|
193
|
+
|
|
194
|
+
// 简单值匹配
|
|
195
|
+
if (typeof condition !== 'object' || condition === null || Array.isArray(condition)) {
|
|
196
|
+
if (fieldValue !== condition) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 操作符匹配
|
|
203
|
+
for (const [op, opValue] of Object.entries(condition)) {
|
|
204
|
+
switch (op) {
|
|
205
|
+
case '$eq':
|
|
206
|
+
if (fieldValue !== opValue) return false;
|
|
207
|
+
break;
|
|
208
|
+
case '$ne':
|
|
209
|
+
if (fieldValue === opValue) return false;
|
|
210
|
+
break;
|
|
211
|
+
case '$gt':
|
|
212
|
+
if (!(fieldValue > opValue)) return false;
|
|
213
|
+
break;
|
|
214
|
+
case '$gte':
|
|
215
|
+
if (!(fieldValue >= opValue)) return false;
|
|
216
|
+
break;
|
|
217
|
+
case '$lt':
|
|
218
|
+
if (!(fieldValue < opValue)) return false;
|
|
219
|
+
break;
|
|
220
|
+
case '$lte':
|
|
221
|
+
if (!(fieldValue <= opValue)) return false;
|
|
222
|
+
break;
|
|
223
|
+
case '$in':
|
|
224
|
+
if (!Array.isArray(opValue) || !opValue.includes(fieldValue)) return false;
|
|
225
|
+
break;
|
|
226
|
+
case '$nin':
|
|
227
|
+
if (Array.isArray(opValue) && opValue.includes(fieldValue)) return false;
|
|
228
|
+
break;
|
|
229
|
+
case '$like':
|
|
230
|
+
if (typeof fieldValue !== 'string' || typeof opValue !== 'string') return false;
|
|
231
|
+
const pattern = opValue.replace(/%/g, '.*').replace(/_/g, '.');
|
|
232
|
+
const regex = new RegExp(\`^\${pattern}$\`, 'i');
|
|
233
|
+
if (!regex.test(fieldValue)) return false;
|
|
234
|
+
break;
|
|
235
|
+
case '$regex':
|
|
236
|
+
// 支持正则表达式匹配
|
|
237
|
+
// opValue 可以是字符串(正则表达式)或对象 { pattern: string, flags?: string }
|
|
238
|
+
// 支持对字符串和数字类型进行匹配(数字会转换为字符串)
|
|
239
|
+
const fieldValueStr = String(fieldValue);
|
|
240
|
+
let regexPattern: string;
|
|
241
|
+
let regexFlags: string = 'i'; // 默认不区分大小写
|
|
242
|
+
|
|
243
|
+
if (typeof opValue === 'string') {
|
|
244
|
+
regexPattern = opValue;
|
|
245
|
+
} else if (typeof opValue === 'object' && opValue !== null) {
|
|
246
|
+
regexPattern = opValue.pattern || opValue.$regex || String(opValue);
|
|
247
|
+
regexFlags = opValue.flags || opValue.$options || 'i';
|
|
248
|
+
} else {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const regex = new RegExp(regexPattern, regexFlags);
|
|
254
|
+
if (!regex.test(fieldValueStr)) return false;
|
|
255
|
+
} catch (e) {
|
|
256
|
+
// 正则表达式无效,返回 false
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
default:
|
|
261
|
+
throw new Error(\`Unsupported operator: \${op}\`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// MongoDB 风格的查询函数
|
|
270
|
+
async function findWithMongoQuery(tableName: string, queryObj?: Record<string, any>) {
|
|
271
|
+
// 获取所有数据
|
|
272
|
+
let allRows = await context.tables.get(tableName);
|
|
273
|
+
|
|
274
|
+
// 默认 limit 为 5
|
|
275
|
+
const DEFAULT_LIMIT = 5;
|
|
276
|
+
|
|
277
|
+
if (!queryObj || Object.keys(queryObj).length === 0) {
|
|
278
|
+
// 即使没有查询条件,也应用默认限制
|
|
279
|
+
return allRows.slice(0, DEFAULT_LIMIT);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 分离查询条件和选项
|
|
283
|
+
const { $sort, $limit, $skip, ...conditions } = queryObj;
|
|
284
|
+
|
|
285
|
+
// 应用过滤条件
|
|
286
|
+
if (Object.keys(conditions).length > 0) {
|
|
287
|
+
allRows = await context.tables.query(tableName, (row) => matchQuery(row, conditions));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 应用排序
|
|
291
|
+
if ($sort) {
|
|
292
|
+
const sortKeys = Object.keys($sort);
|
|
293
|
+
allRows.sort((a, b) => {
|
|
294
|
+
for (const key of sortKeys) {
|
|
295
|
+
const order = $sort[key];
|
|
296
|
+
const aVal = a[key];
|
|
297
|
+
const bVal = b[key];
|
|
298
|
+
|
|
299
|
+
if (aVal === bVal) continue;
|
|
300
|
+
|
|
301
|
+
const comparison = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
302
|
+
return order === 1 ? comparison : -comparison;
|
|
303
|
+
}
|
|
304
|
+
return 0;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 应用跳过
|
|
309
|
+
if ($skip) {
|
|
310
|
+
allRows = allRows.slice($skip);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 应用限制:如果没有指定 $limit,使用默认值 5
|
|
314
|
+
const limit = $limit !== undefined ? $limit : DEFAULT_LIMIT;
|
|
315
|
+
allRows = allRows.slice(0, limit);
|
|
316
|
+
|
|
317
|
+
return allRows;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
let result;
|
|
322
|
+
|
|
323
|
+
switch (operation) {
|
|
324
|
+
case 'find':
|
|
325
|
+
// 查询数据:使用 MongoDB 风格的查询对象
|
|
326
|
+
result = await findWithMongoQuery(table, query || {});
|
|
327
|
+
break;
|
|
328
|
+
|
|
329
|
+
case 'get':
|
|
330
|
+
// 获取所有数据(应用默认限制)
|
|
331
|
+
result = await context.tables.get(table);
|
|
332
|
+
result = result.slice(0, 5); // 默认最多返回5条
|
|
333
|
+
break;
|
|
334
|
+
|
|
335
|
+
case 'insert':
|
|
336
|
+
// 插入数据:data 应该是对象或对象数组
|
|
337
|
+
if (!data) {
|
|
338
|
+
throw new Error('Data parameter is required for insert operation');
|
|
339
|
+
}
|
|
340
|
+
const rowsToInsert = Array.isArray(data) ? data : [data];
|
|
341
|
+
result = [];
|
|
342
|
+
for (const row of rowsToInsert) {
|
|
343
|
+
const inserted = await context.tables.create(table, row);
|
|
344
|
+
result.push(inserted);
|
|
345
|
+
}
|
|
346
|
+
break;
|
|
347
|
+
|
|
348
|
+
case 'update':
|
|
349
|
+
// 更新数据:query 指定条件,data 指定更新内容
|
|
350
|
+
if (!query || !data) {
|
|
351
|
+
throw new Error('Query and data parameters are required for update operation');
|
|
352
|
+
}
|
|
353
|
+
const rowsToUpdate = await findWithMongoQuery(table, query);
|
|
354
|
+
result = { matched: rowsToUpdate.length, updated: 0 };
|
|
355
|
+
for (const row of rowsToUpdate) {
|
|
356
|
+
await context.tables.update(table, row.id, data);
|
|
357
|
+
result.updated++;
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
|
|
361
|
+
case 'delete':
|
|
362
|
+
// 删除数据:query 指定条件
|
|
363
|
+
if (!query) {
|
|
364
|
+
throw new Error('Query parameter is required for delete operation');
|
|
365
|
+
}
|
|
366
|
+
const rowsToDelete = await findWithMongoQuery(table, query);
|
|
367
|
+
result = { matched: rowsToDelete.length, deleted: 0 };
|
|
368
|
+
for (const row of rowsToDelete) {
|
|
369
|
+
await context.tables.delete(table, row.id);
|
|
370
|
+
result.deleted++;
|
|
371
|
+
}
|
|
372
|
+
break;
|
|
373
|
+
|
|
374
|
+
default:
|
|
375
|
+
throw new Error(\`Unsupported operation: \${operation}\`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
success: true,
|
|
380
|
+
data: result,
|
|
381
|
+
};
|
|
382
|
+
} catch (error: any) {
|
|
383
|
+
return {
|
|
384
|
+
success: false,
|
|
385
|
+
error: error.message || String(error),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}`;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* 获取所有工具(从数据库读取,不实时查询 MCP 组件)
|
|
392
|
+
* 每次调用时都从数据库重新加载最新配置,确保返回最新的工具列表
|
|
393
|
+
*/
|
|
394
|
+
async getAllTools() {
|
|
395
|
+
const tools = [];
|
|
396
|
+
// 从数据库重新加载最新配置,确保获取最新的自定义工具
|
|
397
|
+
let latestConfig;
|
|
398
|
+
try {
|
|
399
|
+
const configResult = await db
|
|
400
|
+
.select()
|
|
401
|
+
.from(mcpConfigs)
|
|
402
|
+
.where(eq(mcpConfigs.id, this.config.id))
|
|
403
|
+
.limit(1);
|
|
404
|
+
if (configResult.length === 0) {
|
|
405
|
+
console.warn(`[MCP] getAllTools: Config ${this.config.id} not found, using cached config`);
|
|
406
|
+
latestConfig = this.config;
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
const row = configResult[0];
|
|
410
|
+
const customToolsFromDB = JSON.parse(row.customTools);
|
|
411
|
+
latestConfig = {
|
|
412
|
+
id: row.id,
|
|
413
|
+
userId: row.userId,
|
|
414
|
+
name: row.name,
|
|
415
|
+
components: JSON.parse(row.components),
|
|
416
|
+
customTools: customToolsFromDB,
|
|
417
|
+
variables: JSON.parse(row.variables),
|
|
418
|
+
initScript: row.initScript || undefined,
|
|
419
|
+
enableExploreTable: row.enableExploreTable !== undefined ? Boolean(row.enableExploreTable) : true, // 默认为 true
|
|
420
|
+
createdAt: row.createdAt,
|
|
421
|
+
updatedAt: row.updatedAt,
|
|
422
|
+
};
|
|
423
|
+
// 打印调试信息:比较数据库中的配置和缓存的配置
|
|
424
|
+
const exploreTableToolFromDB = customToolsFromDB.find((t) => t.name === 'explore_table');
|
|
425
|
+
const exploreTableToolFromCache = this.config.customTools.find((t) => t.name === 'explore_table');
|
|
426
|
+
if (exploreTableToolFromDB) {
|
|
427
|
+
console.log(`[MCP] getAllTools: explore_table description from DB (first 100 chars):`, exploreTableToolFromDB.description?.substring(0, 100));
|
|
428
|
+
}
|
|
429
|
+
if (exploreTableToolFromCache) {
|
|
430
|
+
console.log(`[MCP] getAllTools: explore_table description from cache (first 100 chars):`, exploreTableToolFromCache.description?.substring(0, 100));
|
|
431
|
+
}
|
|
432
|
+
// 始终使用从数据库加载的最新配置,无论是否更新
|
|
433
|
+
// 这样可以确保每次调用 getAllTools 都返回最新的工具描述
|
|
434
|
+
if (latestConfig.updatedAt.getTime() !== this.configUpdatedAt.getTime()) {
|
|
435
|
+
console.log(`[MCP] getAllTools: Config timestamp changed (DB: ${latestConfig.updatedAt.toISOString()}, Cache: ${this.configUpdatedAt.toISOString()}), refreshing cached config`);
|
|
436
|
+
this.config = latestConfig;
|
|
437
|
+
// 更新 configUpdatedAt 以反映新的配置时间
|
|
438
|
+
this.configUpdatedAt = latestConfig.updatedAt;
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
// 即使时间戳相同,也使用数据库配置以确保一致性
|
|
442
|
+
console.log(`[MCP] getAllTools: Using DB config (timestamp same: ${latestConfig.updatedAt.toISOString()})`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
console.error(`[MCP] getAllTools: Failed to reload config from database:`, error);
|
|
448
|
+
latestConfig = this.config; // 使用缓存的配置作为后备
|
|
449
|
+
}
|
|
450
|
+
console.log(`[MCP] getAllTools: Loading tools from database for config ${latestConfig.id}, customTools count: ${latestConfig.customTools.length}`);
|
|
451
|
+
try {
|
|
452
|
+
// 从数据库读取 MCP 组件工具(只读取未禁用的工具)
|
|
453
|
+
const dbTools = await db
|
|
454
|
+
.select()
|
|
455
|
+
.from(mcpComponentTools)
|
|
456
|
+
.where(and(eq(mcpComponentTools.configId, latestConfig.id), eq(mcpComponentTools.disabled, false)));
|
|
457
|
+
console.log(`[MCP] getAllTools: Found ${dbTools.length} MCP component tools in database (before filtering disabled components)`);
|
|
458
|
+
// 创建一个组件ID到组件配置的映射,用于快速查找组件是否被禁用
|
|
459
|
+
const componentMap = new Map();
|
|
460
|
+
for (const comp of latestConfig.components) {
|
|
461
|
+
componentMap.set(comp.id, comp);
|
|
462
|
+
}
|
|
463
|
+
// 转换为 MCPTool 格式,同时过滤掉被禁用组件的工具和被禁用的工具
|
|
464
|
+
let filteredCount = 0;
|
|
465
|
+
for (const dbTool of dbTools) {
|
|
466
|
+
// 检查组件是否被禁用
|
|
467
|
+
const component = componentMap.get(dbTool.componentId);
|
|
468
|
+
if (component?.disabled) {
|
|
469
|
+
filteredCount++;
|
|
470
|
+
console.log(`[MCP] getAllTools: Skipping tool ${dbTool.toolName} from disabled component ${component.name}`);
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
// 检查工具是否在组件的 disabledTools 列表中
|
|
474
|
+
// disabledTools 中存储的是原始工具名称(不带命名空间),需要检查 originalToolName
|
|
475
|
+
const disabledTools = Array.isArray(component?.config?.disabledTools)
|
|
476
|
+
? component.config.disabledTools
|
|
477
|
+
: [];
|
|
478
|
+
if (disabledTools.length > 0 && disabledTools.includes(dbTool.originalToolName)) {
|
|
479
|
+
filteredCount++;
|
|
480
|
+
console.log(`[MCP] getAllTools: Skipping disabled tool ${dbTool.toolName} (original: ${dbTool.originalToolName}, disabledTools: [${disabledTools.join(', ')}])`);
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
// 确保工具名称包含命名空间前缀
|
|
484
|
+
// 如果数据库中的 toolName 没有命名空间前缀(向后兼容),则动态添加
|
|
485
|
+
let finalToolName = dbTool.toolName;
|
|
486
|
+
if (component) {
|
|
487
|
+
// 确定命名空间:如果设置了命名空间则使用,否则使用组件名称
|
|
488
|
+
const namespace = component.namespace && component.namespace.trim() !== ''
|
|
489
|
+
? component.namespace.trim()
|
|
490
|
+
: component.name;
|
|
491
|
+
// 检查 toolName 是否已经包含命名空间前缀
|
|
492
|
+
// 如果 toolName 不包含命名空间前缀,则添加
|
|
493
|
+
// 使用下横线连接命名空间和工具名称(Cursor 兼容性)
|
|
494
|
+
if (!finalToolName.startsWith(`${namespace}_`)) {
|
|
495
|
+
// 如果 toolName 已经是原始工具名称(不带命名空间),则添加命名空间前缀
|
|
496
|
+
if (finalToolName === dbTool.originalToolName) {
|
|
497
|
+
finalToolName = `${namespace}_${dbTool.originalToolName}`;
|
|
498
|
+
console.log(`[MCP] getAllTools: Adding namespace prefix to tool: ${dbTool.originalToolName} -> ${finalToolName}`);
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
// 如果 toolName 包含其他命名空间,保持原样(可能是旧数据)
|
|
502
|
+
console.log(`[MCP] getAllTools: Tool ${finalToolName} already has a namespace prefix, keeping as is`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// 应用工具补丁(如果存在)
|
|
507
|
+
// toolPatches 的结构是:{ "originalToolName": { description: "...", inputSchema: {...}, fieldDescriptions: {...} } }
|
|
508
|
+
// 优先使用组件配置中的 toolPatches(最新),如果没有则使用数据库中的 toolPatches
|
|
509
|
+
let inputSchema = dbTool.inputSchema || { type: 'object', properties: {} };
|
|
510
|
+
let description = dbTool.description;
|
|
511
|
+
// 优先从组件配置中获取 toolPatches
|
|
512
|
+
let toolPatches = null;
|
|
513
|
+
if (component?.config?.toolPatches && typeof component.config.toolPatches === 'object') {
|
|
514
|
+
toolPatches = component.config.toolPatches;
|
|
515
|
+
}
|
|
516
|
+
else if (dbTool.toolPatches && typeof dbTool.toolPatches === 'object' && dbTool.toolPatches !== null) {
|
|
517
|
+
toolPatches = dbTool.toolPatches;
|
|
518
|
+
}
|
|
519
|
+
if (toolPatches) {
|
|
520
|
+
// 使用 originalToolName 查找对应的补丁
|
|
521
|
+
const toolPatch = toolPatches[dbTool.originalToolName];
|
|
522
|
+
if (toolPatch) {
|
|
523
|
+
// 应用 description 补丁(如果存在)
|
|
524
|
+
if (toolPatch.description !== undefined && toolPatch.description !== null && toolPatch.description !== '') {
|
|
525
|
+
description = toolPatch.description;
|
|
526
|
+
}
|
|
527
|
+
// 应用 inputSchema 补丁(如果存在)
|
|
528
|
+
if (toolPatch.inputSchema) {
|
|
529
|
+
inputSchema = {
|
|
530
|
+
...inputSchema,
|
|
531
|
+
...toolPatch.inputSchema,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
// 应用 fieldDescriptions 补丁(如果存在)
|
|
535
|
+
if (toolPatch.fieldDescriptions && typeof toolPatch.fieldDescriptions === 'object') {
|
|
536
|
+
// 更新 inputSchema 中对应字段的 description
|
|
537
|
+
if (inputSchema.properties) {
|
|
538
|
+
for (const [fieldName, fieldDescription] of Object.entries(toolPatch.fieldDescriptions)) {
|
|
539
|
+
if (inputSchema.properties[fieldName]) {
|
|
540
|
+
inputSchema.properties[fieldName] = {
|
|
541
|
+
...inputSchema.properties[fieldName],
|
|
542
|
+
description: fieldDescription,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
tools.push({
|
|
551
|
+
name: finalToolName, // 确保包含命名空间前缀
|
|
552
|
+
description: description,
|
|
553
|
+
inputSchema: inputSchema,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
if (filteredCount > 0) {
|
|
557
|
+
console.log(`[MCP] getAllTools: Filtered out ${filteredCount} tools (disabled components or disabled tools)`);
|
|
558
|
+
}
|
|
559
|
+
console.log(`[MCP] getAllTools: Returning ${tools.length} MCP component tools (after filtering disabled components and tools)`);
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
console.error(`[MCP] Failed to load tools from database:`, error);
|
|
563
|
+
// 如果数据库查询失败,返回空数组而不是抛出错误
|
|
564
|
+
}
|
|
565
|
+
// 添加自定义工具(使用最新加载的配置,但排除 explore_table,因为它是内置特殊工具)
|
|
566
|
+
const mcpToolsCount = tools.length;
|
|
567
|
+
const customToolsFiltered = latestConfig.customTools.filter(t => t.name !== 'explore_table');
|
|
568
|
+
console.log(`[MCP] getAllTools: Adding ${customToolsFiltered.length} custom tools (excluding explore_table)`);
|
|
569
|
+
if (customToolsFiltered.length > 0) {
|
|
570
|
+
console.log(`[MCP] getAllTools: Custom tools:`, customToolsFiltered.map(t => t.name).join(', '));
|
|
571
|
+
}
|
|
572
|
+
for (const customTool of customToolsFiltered) {
|
|
573
|
+
const toolSchema = {
|
|
574
|
+
name: customTool.name,
|
|
575
|
+
description: customTool.description,
|
|
576
|
+
inputSchema: {
|
|
577
|
+
type: 'object',
|
|
578
|
+
properties: customTool.parameters.reduce((acc, param) => {
|
|
579
|
+
acc[param.name] = {
|
|
580
|
+
type: param.type,
|
|
581
|
+
description: param.description,
|
|
582
|
+
};
|
|
583
|
+
return acc;
|
|
584
|
+
}, {}),
|
|
585
|
+
required: customTool.parameters.filter((p) => p.required).map((p) => p.name),
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
console.log(`[MCP] getAllTools: Adding custom tool: ${toolSchema.name}`);
|
|
589
|
+
tools.push(toolSchema);
|
|
590
|
+
}
|
|
591
|
+
// 添加内置的 explore_table 工具(基于数据库中的表定义动态生成)
|
|
592
|
+
// 只有当配置中 enableExploreTable 为 true 时才添加(默认为 true)
|
|
593
|
+
// 使用 latestConfig 而不是 this.config,确保使用最新加载的配置
|
|
594
|
+
if (latestConfig.enableExploreTable !== false) {
|
|
595
|
+
try {
|
|
596
|
+
const exploreTableDescription = await this.generateExploreTableDescription();
|
|
597
|
+
const exploreTableTool = {
|
|
598
|
+
name: 'explore_table',
|
|
599
|
+
description: exploreTableDescription,
|
|
600
|
+
inputSchema: {
|
|
601
|
+
type: 'object',
|
|
602
|
+
properties: {
|
|
603
|
+
table: {
|
|
604
|
+
type: 'string',
|
|
605
|
+
description: '表名',
|
|
606
|
+
},
|
|
607
|
+
operation: {
|
|
608
|
+
type: 'string',
|
|
609
|
+
description: '操作类型: find(查询), get(获取所有), insert(插入), update(更新), delete(删除)',
|
|
610
|
+
},
|
|
611
|
+
query: {
|
|
612
|
+
type: 'object',
|
|
613
|
+
description: '查询对象(用于 find/update/delete),支持操作符和条件',
|
|
614
|
+
},
|
|
615
|
+
data: {
|
|
616
|
+
type: 'object',
|
|
617
|
+
description: '数据对象或数组(用于 insert/update)',
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
required: ['table'],
|
|
621
|
+
},
|
|
622
|
+
};
|
|
623
|
+
console.log(`[MCP] getAllTools: Adding built-in explore_table tool`);
|
|
624
|
+
tools.push(exploreTableTool);
|
|
625
|
+
}
|
|
626
|
+
catch (error) {
|
|
627
|
+
console.error(`[MCP] getAllTools: Failed to add explore_table tool:`, error);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
console.log(`[MCP] getAllTools: explore_table tool is disabled by configuration`);
|
|
632
|
+
}
|
|
633
|
+
console.log(`[MCP] getAllTools: Total ${tools.length} tools (${mcpToolsCount} from MCP components, ${customToolsFiltered.length} custom, 1 built-in explore_table)`);
|
|
634
|
+
return tools;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* 获取所有提示
|
|
638
|
+
*/
|
|
639
|
+
async getAllPrompts() {
|
|
640
|
+
const allPrompts = [];
|
|
641
|
+
console.log(`[MCP] getAllPrompts: Starting, ${this.components.size} components loaded`);
|
|
642
|
+
// 从组件获取提示
|
|
643
|
+
for (const [name, component] of this.components) {
|
|
644
|
+
// 通过 component name 在配置中查找原始配置
|
|
645
|
+
const originalComponent = this.config.components.find(c => c.name === name);
|
|
646
|
+
// 跳过禁用的组件(双重检查,防止配置更新后组件仍被加载)
|
|
647
|
+
if (originalComponent?.disabled) {
|
|
648
|
+
console.log(`[MCP] getAllPrompts: Skipping disabled component ${name}`);
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
try {
|
|
652
|
+
console.log(`[MCP] getAllPrompts: Fetching prompts from component ${name}`);
|
|
653
|
+
// 添加超时保护(10秒)
|
|
654
|
+
const componentPrompts = await Promise.race([
|
|
655
|
+
component.client.listPrompts(),
|
|
656
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout fetching prompts from component ${name}`)), 10000))
|
|
657
|
+
]);
|
|
658
|
+
console.log(`[MCP] getAllPrompts: Component ${name} returned ${componentPrompts.length} prompts`);
|
|
659
|
+
// 应用命名空间前缀:只有在明确设置了命名空间时才使用,否则不添加前缀
|
|
660
|
+
const namespace = originalComponent?.namespace && originalComponent.namespace.trim() !== ''
|
|
661
|
+
? originalComponent.namespace.trim()
|
|
662
|
+
: null;
|
|
663
|
+
for (const prompt of componentPrompts) {
|
|
664
|
+
allPrompts.push({
|
|
665
|
+
name: namespace ? `${namespace}.${prompt.name}` : prompt.name,
|
|
666
|
+
description: prompt.description || '',
|
|
667
|
+
arguments: prompt.arguments || [],
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
const errorMessage = error.message || String(error);
|
|
673
|
+
console.error(`[MCP] Failed to get prompts from component ${name}:`, errorMessage);
|
|
674
|
+
// 继续处理其他组件,不中断
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
// 从数据库获取用户自定义的 prompts
|
|
678
|
+
try {
|
|
679
|
+
const userPrompts = await db
|
|
680
|
+
.select()
|
|
681
|
+
.from(prompts)
|
|
682
|
+
.where(eq(prompts.userId, this.userId));
|
|
683
|
+
for (const row of userPrompts) {
|
|
684
|
+
allPrompts.push({
|
|
685
|
+
name: row.name,
|
|
686
|
+
description: row.description || undefined,
|
|
687
|
+
arguments: JSON.parse(row.arguments),
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
catch (error) {
|
|
692
|
+
console.error(`Failed to load user prompts:`, error);
|
|
693
|
+
}
|
|
694
|
+
console.log(`[MCP] getAllPrompts: Total ${allPrompts.length} prompts`);
|
|
695
|
+
return allPrompts;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* 替换消息模板中的变量(支持条件逻辑)
|
|
699
|
+
*/
|
|
700
|
+
replaceVariables(text, args) {
|
|
701
|
+
return processPromptTemplate(text, args);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* 获取提示内容
|
|
705
|
+
* @param promptName 提示名称
|
|
706
|
+
* @param args 可选参数,用于填充 prompt 模板。如果 prompt 不需要参数,可以不传或传空对象 {}
|
|
707
|
+
*/
|
|
708
|
+
async getPrompt(promptName, args) {
|
|
709
|
+
// 先检查是否是用户自定义的 prompt(从数据库加载)
|
|
710
|
+
try {
|
|
711
|
+
const userPrompts = await db
|
|
712
|
+
.select()
|
|
713
|
+
.from(prompts)
|
|
714
|
+
.where(and(eq(prompts.userId, this.userId), eq(prompts.name, promptName)))
|
|
715
|
+
.limit(1);
|
|
716
|
+
if (userPrompts.length > 0) {
|
|
717
|
+
const promptRow = userPrompts[0];
|
|
718
|
+
const promptMessages = JSON.parse(promptRow.messages);
|
|
719
|
+
const promptArgs = args || {};
|
|
720
|
+
console.log(`[MCP] getPrompt: Loading user prompt "${promptName}" with ${promptMessages.length} messages`);
|
|
721
|
+
// 替换消息中的变量,确保格式符合 MCP 协议规范
|
|
722
|
+
const processedMessages = promptMessages.map((msg) => {
|
|
723
|
+
// 确保消息格式符合 MCP 协议:role 和 content 是必需的
|
|
724
|
+
// content 必须包含 type 和 text 字段
|
|
725
|
+
return {
|
|
726
|
+
role: msg.role || 'user',
|
|
727
|
+
content: {
|
|
728
|
+
type: msg.content?.type || 'text',
|
|
729
|
+
text: this.replaceVariables(msg.content?.text || '', promptArgs),
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
});
|
|
733
|
+
const result = {
|
|
734
|
+
description: promptRow.description || undefined,
|
|
735
|
+
messages: processedMessages,
|
|
736
|
+
};
|
|
737
|
+
console.log(`[MCP] getPrompt: Returning prompt "${promptName}" with ${processedMessages.length} messages`);
|
|
738
|
+
console.log(`[MCP] getPrompt: Sample message format:`, JSON.stringify(processedMessages[0] || {}, null, 2));
|
|
739
|
+
return result;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
console.error(`Failed to load user prompt ${promptName}:`, error);
|
|
744
|
+
}
|
|
745
|
+
// 调用组件提示
|
|
746
|
+
const parts = promptName.split('.');
|
|
747
|
+
if (parts.length >= 2) {
|
|
748
|
+
const namespace = parts[0];
|
|
749
|
+
const actualPromptName = parts.slice(1).join('.');
|
|
750
|
+
// 查找匹配的组件(通过命名空间或组件名称)
|
|
751
|
+
for (const [name, component] of this.components) {
|
|
752
|
+
const originalComponent = this.config.components.find(c => c.name === name);
|
|
753
|
+
// 检查组件是否被禁用
|
|
754
|
+
if (originalComponent?.disabled) {
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
// 匹配命名空间:只有在明确设置了命名空间时才匹配命名空间,否则匹配组件名称
|
|
758
|
+
const componentNamespace = originalComponent?.namespace && originalComponent.namespace.trim() !== ''
|
|
759
|
+
? originalComponent.namespace.trim()
|
|
760
|
+
: null;
|
|
761
|
+
if (componentNamespace === namespace || (!componentNamespace && name === namespace)) {
|
|
762
|
+
try {
|
|
763
|
+
return await component.client.getPrompt(actualPromptName, args);
|
|
764
|
+
}
|
|
765
|
+
catch (error) {
|
|
766
|
+
throw new Error(`Failed to get prompt "${promptName}": ${error.message}`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
// 没有命名空间,尝试在所有组件中查找(只查找没有设置命名空间的组件)
|
|
773
|
+
for (const [name, component] of this.components) {
|
|
774
|
+
const originalComponent = this.config.components.find(c => c.name === name);
|
|
775
|
+
// 检查组件是否被禁用
|
|
776
|
+
if (originalComponent?.disabled) {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
// 只在没有设置命名空间的组件中查找
|
|
780
|
+
const componentNamespace = originalComponent?.namespace && originalComponent.namespace.trim() !== ''
|
|
781
|
+
? originalComponent.namespace.trim()
|
|
782
|
+
: null;
|
|
783
|
+
if (componentNamespace) {
|
|
784
|
+
// 如果组件设置了命名空间,跳过(因为提示名称应该包含命名空间)
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
try {
|
|
788
|
+
const prompts = await component.client.listPrompts();
|
|
789
|
+
const prompt = prompts.find((p) => p.name === promptName);
|
|
790
|
+
if (prompt) {
|
|
791
|
+
return await component.client.getPrompt(promptName, args);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
catch (error) {
|
|
795
|
+
// 继续查找
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
throw new Error(`Prompt ${promptName} not found`);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* 从数据库检查工具是否被禁用
|
|
803
|
+
*/
|
|
804
|
+
async isToolDisabledInDB(toolName) {
|
|
805
|
+
try {
|
|
806
|
+
const dbTool = await db
|
|
807
|
+
.select()
|
|
808
|
+
.from(mcpComponentTools)
|
|
809
|
+
.where(and(eq(mcpComponentTools.configId, this.config.id), eq(mcpComponentTools.toolName, toolName)))
|
|
810
|
+
.limit(1);
|
|
811
|
+
if (dbTool.length > 0) {
|
|
812
|
+
return dbTool[0].disabled;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
catch (error) {
|
|
816
|
+
console.error(`[MCP] Failed to check tool disabled status:`, error);
|
|
817
|
+
}
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* 调用工具(实时转发到 MCP 组件)
|
|
822
|
+
*/
|
|
823
|
+
async callTool(toolName, args, allowDisabled = false) {
|
|
824
|
+
// 检查是否是内置的 explore_table 工具
|
|
825
|
+
if (toolName === 'explore_table') {
|
|
826
|
+
// 从数据库重新加载最新配置,检查是否启用了 explore_table 工具
|
|
827
|
+
let enableExploreTable = true; // 默认为 true
|
|
828
|
+
try {
|
|
829
|
+
const configResult = await db
|
|
830
|
+
.select()
|
|
831
|
+
.from(mcpConfigs)
|
|
832
|
+
.where(eq(mcpConfigs.id, this.config.id))
|
|
833
|
+
.limit(1);
|
|
834
|
+
if (configResult.length > 0) {
|
|
835
|
+
const row = configResult[0];
|
|
836
|
+
enableExploreTable = row.enableExploreTable !== undefined ? Boolean(row.enableExploreTable) : true;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
catch (error) {
|
|
840
|
+
console.error(`[MCP] callTool: Failed to reload config from database:`, error);
|
|
841
|
+
// 如果加载失败,使用缓存的配置
|
|
842
|
+
enableExploreTable = this.config.enableExploreTable !== false;
|
|
843
|
+
}
|
|
844
|
+
if (enableExploreTable === false) {
|
|
845
|
+
throw new Error('Tool "explore_table" is disabled');
|
|
846
|
+
}
|
|
847
|
+
// 检查表是否允许探索
|
|
848
|
+
if (args.table) {
|
|
849
|
+
const tableDef = await db
|
|
850
|
+
.select()
|
|
851
|
+
.from(tableDefinitions)
|
|
852
|
+
.where(and(eq(tableDefinitions.userId, this.userId), eq(tableDefinitions.name, args.table)))
|
|
853
|
+
.limit(1);
|
|
854
|
+
if (tableDef.length === 0) {
|
|
855
|
+
throw new Error(`Table "${args.table}" not found`);
|
|
856
|
+
}
|
|
857
|
+
const allowExploreValue = tableDef[0].allowExplore;
|
|
858
|
+
const allowExplore = allowExploreValue !== null && allowExploreValue !== undefined ? Boolean(allowExploreValue) : true;
|
|
859
|
+
if (!allowExplore) {
|
|
860
|
+
throw new Error(`Table "${args.table}" is not allowed to be explored`);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
// 创建临时的自定义工具对象来执行脚本
|
|
864
|
+
const exploreTableTool = {
|
|
865
|
+
id: 'builtin-explore-table',
|
|
866
|
+
name: 'explore_table',
|
|
867
|
+
description: await this.generateExploreTableDescription(),
|
|
868
|
+
serverScript: this.generateExploreTableScript(),
|
|
869
|
+
parameters: [
|
|
870
|
+
{
|
|
871
|
+
name: 'table',
|
|
872
|
+
type: 'string',
|
|
873
|
+
description: '表名',
|
|
874
|
+
required: true,
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
name: 'operation',
|
|
878
|
+
type: 'string',
|
|
879
|
+
description: '操作类型: find(查询), get(获取所有), insert(插入), update(更新), delete(删除)',
|
|
880
|
+
required: false,
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
name: 'query',
|
|
884
|
+
type: 'object',
|
|
885
|
+
description: '查询对象(用于 find/update/delete),支持操作符和条件',
|
|
886
|
+
required: false,
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
name: 'data',
|
|
890
|
+
type: 'object',
|
|
891
|
+
description: '数据对象或数组(用于 insert/update)',
|
|
892
|
+
required: false,
|
|
893
|
+
},
|
|
894
|
+
],
|
|
895
|
+
};
|
|
896
|
+
const result = await this.scriptExecutor.executeTool(exploreTableTool, args);
|
|
897
|
+
// 上报 explore_table 工具调用统计
|
|
898
|
+
report('tool_called', {
|
|
899
|
+
tool_name: 'explore_table',
|
|
900
|
+
component_type: 'builtin',
|
|
901
|
+
is_custom_tool: true,
|
|
902
|
+
operation: args.operation || 'unknown',
|
|
903
|
+
has_table: !!args.table,
|
|
904
|
+
}, this.userId);
|
|
905
|
+
return result;
|
|
906
|
+
}
|
|
907
|
+
// 检查是否是自定义工具(排除 explore_table,因为它已经作为内置工具处理)
|
|
908
|
+
const customTool = this.config.customTools.find((t) => t.name === toolName && t.name !== 'explore_table');
|
|
909
|
+
if (customTool) {
|
|
910
|
+
// 自定义工具统一为单一脚本(运行在服务端),执行即可(缺失脚本在执行器内抛错)
|
|
911
|
+
const result = await this.scriptExecutor.executeTool(customTool, args);
|
|
912
|
+
// 上报自定义工具调用统计
|
|
913
|
+
report('tool_called', {
|
|
914
|
+
tool_name: toolName,
|
|
915
|
+
component_type: 'custom',
|
|
916
|
+
is_custom_tool: true,
|
|
917
|
+
has_parameters: (customTool.parameters?.length || 0) > 0,
|
|
918
|
+
}, this.userId);
|
|
919
|
+
return result;
|
|
920
|
+
}
|
|
921
|
+
// 检查工具是否在数据库中被禁用
|
|
922
|
+
const isDisabled = await this.isToolDisabledInDB(toolName);
|
|
923
|
+
if (isDisabled) {
|
|
924
|
+
throw new Error(`Tool "${toolName}" is disabled`);
|
|
925
|
+
}
|
|
926
|
+
// 从数据库查找工具信息,确定组件名称
|
|
927
|
+
// 首先尝试直接匹配 toolName(可能包含命名空间前缀)
|
|
928
|
+
console.log(`[MCP] callTool: Looking up tool "${toolName}" in database`);
|
|
929
|
+
let dbTool = await db
|
|
930
|
+
.select()
|
|
931
|
+
.from(mcpComponentTools)
|
|
932
|
+
.where(and(eq(mcpComponentTools.configId, this.config.id), eq(mcpComponentTools.toolName, toolName)))
|
|
933
|
+
.limit(1);
|
|
934
|
+
if (dbTool.length > 0) {
|
|
935
|
+
console.log(`[MCP] callTool: Found tool directly by toolName: ${dbTool[0].toolName} (original: ${dbTool[0].originalToolName}, component: ${dbTool[0].componentName})`);
|
|
936
|
+
}
|
|
937
|
+
// 如果直接匹配失败,尝试通过 originalToolName 查找(处理命名空间不一致的情况)
|
|
938
|
+
if (dbTool.length === 0) {
|
|
939
|
+
console.log(`[MCP] callTool: Direct lookup failed, trying to find by originalToolName "${toolName}"`);
|
|
940
|
+
// 首先尝试:如果 toolName 看起来像带命名空间的(包含下横线),
|
|
941
|
+
// 尝试提取命名空间和原始工具名称,然后查找
|
|
942
|
+
if (toolName.includes('_')) {
|
|
943
|
+
const parts = toolName.split('_');
|
|
944
|
+
// 尝试不同的分割方式:可能是 "namespace_tool" 或 "namespace_tool_name"
|
|
945
|
+
for (let i = 1; i < parts.length; i++) {
|
|
946
|
+
const possibleNamespace = parts.slice(0, i).join('_');
|
|
947
|
+
const possibleOriginalName = parts.slice(i).join('_');
|
|
948
|
+
// 查找匹配命名空间和原始工具名称的工具
|
|
949
|
+
const namespaceMatchedTools = await db
|
|
950
|
+
.select()
|
|
951
|
+
.from(mcpComponentTools)
|
|
952
|
+
.where(and(eq(mcpComponentTools.configId, this.config.id), eq(mcpComponentTools.originalToolName, possibleOriginalName)));
|
|
953
|
+
for (const tool of namespaceMatchedTools) {
|
|
954
|
+
const component = this.config.components.find(c => c.name === tool.componentName);
|
|
955
|
+
if (component) {
|
|
956
|
+
const namespace = component.namespace && component.namespace.trim() !== ''
|
|
957
|
+
? component.namespace.trim()
|
|
958
|
+
: component.name;
|
|
959
|
+
if (namespace === possibleNamespace) {
|
|
960
|
+
dbTool = [tool];
|
|
961
|
+
console.log(`[MCP] callTool: Found tool by namespace "${possibleNamespace}" and originalToolName "${possibleOriginalName}": ${tool.toolName}`);
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (dbTool.length > 0)
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
// 如果还是没找到,尝试直接通过 originalToolName 查找
|
|
971
|
+
if (dbTool.length === 0) {
|
|
972
|
+
const allTools = await db
|
|
973
|
+
.select()
|
|
974
|
+
.from(mcpComponentTools)
|
|
975
|
+
.where(and(eq(mcpComponentTools.configId, this.config.id), eq(mcpComponentTools.originalToolName, toolName)));
|
|
976
|
+
console.log(`[MCP] callTool: Found ${allTools.length} tools with originalToolName "${toolName}"`);
|
|
977
|
+
if (allTools.length > 0) {
|
|
978
|
+
// 如果只有一个匹配,直接使用
|
|
979
|
+
if (allTools.length === 1) {
|
|
980
|
+
dbTool = allTools;
|
|
981
|
+
console.log(`[MCP] callTool: Using single match: ${allTools[0].toolName} (original: ${allTools[0].originalToolName}, component: ${allTools[0].componentName})`);
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
// 如果找到多个匹配,优先选择与组件命名空间匹配的
|
|
985
|
+
// 否则使用第一个匹配项
|
|
986
|
+
let matchedTool = allTools[0];
|
|
987
|
+
// 尝试找到命名空间匹配的工具
|
|
988
|
+
for (const tool of allTools) {
|
|
989
|
+
const component = this.config.components.find(c => c.name === tool.componentName);
|
|
990
|
+
if (component) {
|
|
991
|
+
const namespace = component.namespace && component.namespace.trim() !== ''
|
|
992
|
+
? component.namespace.trim()
|
|
993
|
+
: component.name;
|
|
994
|
+
const expectedToolName = `${namespace}_${tool.originalToolName}`;
|
|
995
|
+
console.log(`[MCP] callTool: Checking tool "${tool.toolName}" (component: ${tool.componentName}, namespace: ${namespace}, expected: ${expectedToolName})`);
|
|
996
|
+
if (expectedToolName === toolName) {
|
|
997
|
+
matchedTool = tool;
|
|
998
|
+
console.log(`[MCP] callTool: Found namespace-matched tool: ${matchedTool.toolName}`);
|
|
999
|
+
break;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
dbTool = [matchedTool];
|
|
1004
|
+
console.log(`[MCP] callTool: Using tool "${matchedTool.toolName}" (original: ${matchedTool.originalToolName}, component: ${matchedTool.componentName})`);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (dbTool.length === 0) {
|
|
1010
|
+
throw new Error(`Tool "${toolName}" not found in database`);
|
|
1011
|
+
}
|
|
1012
|
+
const toolInfo = dbTool[0];
|
|
1013
|
+
const componentName = toolInfo.componentName;
|
|
1014
|
+
const originalToolName = toolInfo.originalToolName;
|
|
1015
|
+
// 前置校验:检查组件是否被禁用
|
|
1016
|
+
const componentConfig = this.config.components.find(c => c.name === componentName);
|
|
1017
|
+
if (!componentConfig) {
|
|
1018
|
+
throw new Error(`Component "${componentName}" not found in config`);
|
|
1019
|
+
}
|
|
1020
|
+
// 如果 allowDisabled 为 false,检查组件是否被禁用
|
|
1021
|
+
if (!allowDisabled && componentConfig.disabled) {
|
|
1022
|
+
throw new Error(`Component "${componentName}" is disabled`);
|
|
1023
|
+
}
|
|
1024
|
+
// 前置校验:检查工具是否在组件配置中被禁用
|
|
1025
|
+
// 如果 allowDisabled 为 false,检查工具是否被禁用
|
|
1026
|
+
if (!allowDisabled) {
|
|
1027
|
+
const disabledTools = Array.isArray(componentConfig.config?.disabledTools)
|
|
1028
|
+
? componentConfig.config.disabledTools
|
|
1029
|
+
: [];
|
|
1030
|
+
if (disabledTools.includes(originalToolName)) {
|
|
1031
|
+
throw new Error(`Tool "${toolName}" is disabled in component configuration`);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
// 按需加载组件(传递 allowDisabled 参数)
|
|
1035
|
+
const component = await this.ensureComponentLoaded(componentName, allowDisabled);
|
|
1036
|
+
// 调用组件的工具(实时转发)
|
|
1037
|
+
try {
|
|
1038
|
+
const result = await component.client.callTool(originalToolName, args);
|
|
1039
|
+
// 上报工具调用统计
|
|
1040
|
+
report('tool_called', {
|
|
1041
|
+
tool_name: toolName,
|
|
1042
|
+
original_tool_name: originalToolName,
|
|
1043
|
+
component_name: componentName,
|
|
1044
|
+
component_type: componentConfig.type,
|
|
1045
|
+
has_namespace: !!componentConfig.namespace,
|
|
1046
|
+
is_custom_tool: toolName === 'explore_table' || this.config.customTools.some(t => t.name === toolName),
|
|
1047
|
+
}, this.userId);
|
|
1048
|
+
return result;
|
|
1049
|
+
}
|
|
1050
|
+
catch (error) {
|
|
1051
|
+
// 上报工具调用失败
|
|
1052
|
+
report('tool_call_failed', {
|
|
1053
|
+
tool_name: toolName,
|
|
1054
|
+
component_name: componentName,
|
|
1055
|
+
error_type: error.constructor?.name || 'Unknown',
|
|
1056
|
+
}, this.userId);
|
|
1057
|
+
throw new Error(`Failed to call tool "${toolName}": ${error.message}`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* 处理 MCP 协议请求
|
|
1062
|
+
*/
|
|
1063
|
+
async handleRequest(request) {
|
|
1064
|
+
const { method, params } = request;
|
|
1065
|
+
try {
|
|
1066
|
+
switch (method) {
|
|
1067
|
+
case 'initialize': {
|
|
1068
|
+
// 初始化请求
|
|
1069
|
+
// 注意:initialize 响应中不返回工具列表,工具列表通过 tools/list 请求获取
|
|
1070
|
+
// 调用 getAllTools 以确保配置已重新加载
|
|
1071
|
+
const tools = await this.getAllTools();
|
|
1072
|
+
console.log(`[MCP] Initialize: ${tools.length} tools available (will be returned via tools/list)`);
|
|
1073
|
+
console.log(`[MCP] Initialize: Config has ${this.config.customTools.length} custom tools`);
|
|
1074
|
+
if (tools.length > 0) {
|
|
1075
|
+
console.log(`[MCP] Initialize: Tool names:`, tools.map(t => t.name).join(', '));
|
|
1076
|
+
}
|
|
1077
|
+
else {
|
|
1078
|
+
console.warn(`[MCP] Initialize: WARNING - No tools available! This may indicate a configuration issue.`);
|
|
1079
|
+
console.warn(`[MCP] Initialize: Config ID: ${this.config.id}, Components: ${this.config.components.length}`);
|
|
1080
|
+
}
|
|
1081
|
+
return {
|
|
1082
|
+
result: {
|
|
1083
|
+
protocolVersion: '2024-11-05',
|
|
1084
|
+
capabilities: {
|
|
1085
|
+
tools: {
|
|
1086
|
+
listChanged: false, // 声明支持工具列表功能
|
|
1087
|
+
},
|
|
1088
|
+
prompts: {},
|
|
1089
|
+
},
|
|
1090
|
+
serverInfo: {
|
|
1091
|
+
name: 'flex-mcp',
|
|
1092
|
+
version: '1.0.0',
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
case 'notifications/initialized':
|
|
1098
|
+
// 初始化完成通知,无需返回结果
|
|
1099
|
+
return {};
|
|
1100
|
+
case 'ping':
|
|
1101
|
+
return { result: {} };
|
|
1102
|
+
// 兼容旧版本的协议方法
|
|
1103
|
+
case 'listOfferings':
|
|
1104
|
+
case 'ListOfferings': {
|
|
1105
|
+
// 返回工具列表(兼容旧协议)
|
|
1106
|
+
const allToolsCompat = await this.getAllTools();
|
|
1107
|
+
return {
|
|
1108
|
+
result: {
|
|
1109
|
+
tools: allToolsCompat,
|
|
1110
|
+
},
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
case 'tools/list': {
|
|
1114
|
+
console.log(`[MCP] tools/list: Request received`);
|
|
1115
|
+
try {
|
|
1116
|
+
const allTools = await this.getAllTools();
|
|
1117
|
+
console.log(`[MCP] tools/list: Returning ${allTools.length} tools`);
|
|
1118
|
+
if (allTools.length > 0) {
|
|
1119
|
+
console.log(`[MCP] tools/list: Tool names:`, allTools.map(t => t.name).join(', '));
|
|
1120
|
+
// 打印第一个工具的详细信息用于调试
|
|
1121
|
+
console.log(`[MCP] tools/list: First tool details:`, JSON.stringify(allTools[0], null, 2));
|
|
1122
|
+
}
|
|
1123
|
+
else {
|
|
1124
|
+
console.warn(`[MCP] tools/list: No tools found!`);
|
|
1125
|
+
}
|
|
1126
|
+
return {
|
|
1127
|
+
result: { tools: allTools },
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
catch (error) {
|
|
1131
|
+
console.error(`[MCP] tools/list: Error getting tools:`, error);
|
|
1132
|
+
return {
|
|
1133
|
+
error: {
|
|
1134
|
+
code: -32000,
|
|
1135
|
+
message: `Failed to get tools: ${error.message}`,
|
|
1136
|
+
},
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
case 'tools/call': {
|
|
1141
|
+
const { name, arguments: args } = params;
|
|
1142
|
+
if (!name) {
|
|
1143
|
+
return {
|
|
1144
|
+
error: {
|
|
1145
|
+
code: -32602,
|
|
1146
|
+
message: 'Invalid params: tool name is required',
|
|
1147
|
+
},
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
const result = await this.callTool(name, args || {});
|
|
1151
|
+
return {
|
|
1152
|
+
result: {
|
|
1153
|
+
content: [
|
|
1154
|
+
{
|
|
1155
|
+
type: 'text',
|
|
1156
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
|
|
1157
|
+
},
|
|
1158
|
+
],
|
|
1159
|
+
},
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
case 'resources/list': {
|
|
1163
|
+
return {
|
|
1164
|
+
result: { resources: [] },
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
case 'resources/read': {
|
|
1168
|
+
return {
|
|
1169
|
+
error: {
|
|
1170
|
+
code: -32601,
|
|
1171
|
+
message: 'Resources are not supported',
|
|
1172
|
+
},
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
case 'prompts/list': {
|
|
1176
|
+
const allPrompts = await this.getAllPrompts();
|
|
1177
|
+
console.log(`[MCP] prompts/list: Returning ${allPrompts.length} prompts`);
|
|
1178
|
+
return {
|
|
1179
|
+
result: { prompts: allPrompts },
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
case 'prompts/get': {
|
|
1183
|
+
const { name: promptName, arguments: promptArgs } = params;
|
|
1184
|
+
console.log(`[MCP] prompts/get: Request for prompt "${promptName}" with args:`, JSON.stringify(promptArgs || {}));
|
|
1185
|
+
if (!promptName) {
|
|
1186
|
+
return {
|
|
1187
|
+
error: {
|
|
1188
|
+
code: -32602,
|
|
1189
|
+
message: 'Invalid params: prompt name is required',
|
|
1190
|
+
},
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
try {
|
|
1194
|
+
// 参数是可选的:如果 prompt 不需要参数,可以不传 arguments 或传空对象 {}
|
|
1195
|
+
const promptResult = await this.getPrompt(promptName, promptArgs || {});
|
|
1196
|
+
console.log(`[MCP] prompts/get: Successfully retrieved prompt "${promptName}", messages count: ${promptResult.messages?.length || 0}`);
|
|
1197
|
+
return {
|
|
1198
|
+
result: promptResult,
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
catch (error) {
|
|
1202
|
+
console.error(`[MCP] prompts/get: Error retrieving prompt "${promptName}":`, error.message || error);
|
|
1203
|
+
return {
|
|
1204
|
+
error: {
|
|
1205
|
+
code: -32602,
|
|
1206
|
+
message: error.message || `Failed to get prompt: ${promptName}`,
|
|
1207
|
+
},
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
default:
|
|
1212
|
+
return {
|
|
1213
|
+
error: {
|
|
1214
|
+
code: -32601,
|
|
1215
|
+
message: `Method not found: ${method}`,
|
|
1216
|
+
},
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
catch (error) {
|
|
1221
|
+
return {
|
|
1222
|
+
error: {
|
|
1223
|
+
code: -32000,
|
|
1224
|
+
message: error.message || 'Internal error',
|
|
1225
|
+
},
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* 关闭聚合器
|
|
1231
|
+
* 确保所有资源正确释放
|
|
1232
|
+
*/
|
|
1233
|
+
async close() {
|
|
1234
|
+
// 关闭所有组件连接
|
|
1235
|
+
const closePromises = [];
|
|
1236
|
+
for (const [name, component] of this.components) {
|
|
1237
|
+
closePromises.push((async () => {
|
|
1238
|
+
try {
|
|
1239
|
+
await component.client.close();
|
|
1240
|
+
}
|
|
1241
|
+
catch (error) {
|
|
1242
|
+
console.error(`Failed to close component ${name}:`, error.message || error);
|
|
1243
|
+
}
|
|
1244
|
+
})());
|
|
1245
|
+
}
|
|
1246
|
+
// 并行关闭所有组件,但设置总体超时(10秒)
|
|
1247
|
+
try {
|
|
1248
|
+
await Promise.race([
|
|
1249
|
+
Promise.all(closePromises),
|
|
1250
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Aggregator close timeout')), 10000))
|
|
1251
|
+
]);
|
|
1252
|
+
}
|
|
1253
|
+
catch (error) {
|
|
1254
|
+
if (error.message === 'Aggregator close timeout') {
|
|
1255
|
+
console.warn(`[MCP] Some components did not close within timeout, forcing cleanup`);
|
|
1256
|
+
}
|
|
1257
|
+
else {
|
|
1258
|
+
console.error(`[MCP] Error closing aggregator:`, error.message || error);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
this.components.clear();
|
|
1262
|
+
this.failedComponents.clear();
|
|
1263
|
+
await this.scriptExecutor.cleanup();
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
//# sourceMappingURL=aggregator.js.map
|