vmoo-mcp-database-server 1.1.0 → 1.2.1
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/README.md +214 -185
- package/bin/vmoo-mcp-deliver.js +45 -45
- package/bin/vmoo-mcp-dev.js +45 -45
- package/bin/vmoo-mcp-prod.js +45 -45
- package/bin/vmoo-mcp-server.js +81 -81
- package/index.js +38 -38
- package/package.json +61 -61
- package/shared/database-utils.js +102 -102
- package/shared/privacy-utils.js +207 -207
- package/shared/security-utils.js +219 -219
- package/shared/time-utils.js +62 -62
- package/vmoo-database-deliver/config.json +25 -35
- package/vmoo-database-deliver/package.json +26 -28
- package/vmoo-database-deliver/server.js +297 -297
- package/vmoo-database-dev/config.json +25 -25
- package/vmoo-database-dev/package.json +32 -32
- package/vmoo-database-dev/server.js +294 -294
- package/vmoo-database-prod/config.json +41 -41
- package/vmoo-database-prod/package.json +33 -33
- package/vmoo-database-prod/server.js +313 -313
|
@@ -1,313 +1,313 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
-
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
-
import mysql from 'mysql2/promise';
|
|
7
|
-
import fs from 'fs';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
|
-
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
|
|
14
|
-
// 导入共享模块
|
|
15
|
-
const { createConnectionPool, executeQuery, normalizeTableName, getTableStructure, getTableCount, getTableSampleData, listTables } = await import('../shared/database-utils.js');
|
|
16
|
-
const { SECURITY_LEVELS, performSecurityCheck, applyQueryLimits } = await import('../shared/security-utils.js');
|
|
17
|
-
const { processTimeFields } = await import('../shared/time-utils.js');
|
|
18
|
-
const { anonymizeQueryResults } = await import('../shared/privacy-utils.js');
|
|
19
|
-
|
|
20
|
-
// 读取配置文件
|
|
21
|
-
const config = JSON.parse(fs.readFileSync(path.join(__dirname, 'config.json'), 'utf8'));
|
|
22
|
-
|
|
23
|
-
// 创建数据库连接池
|
|
24
|
-
const pool = createConnectionPool(config.database);
|
|
25
|
-
|
|
26
|
-
// 创建MCP服务器
|
|
27
|
-
const server = new Server(
|
|
28
|
-
{
|
|
29
|
-
name: config.server.name,
|
|
30
|
-
version: config.server.version,
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
capabilities: {
|
|
34
|
-
tools: {},
|
|
35
|
-
},
|
|
36
|
-
}
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
// 注册工具
|
|
40
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
41
|
-
return {
|
|
42
|
-
tools: [
|
|
43
|
-
{
|
|
44
|
-
name: 'query_database',
|
|
45
|
-
description: '执行SQL查询语句(生产环境:仅支持查询,有频率限制和数据匿名化)',
|
|
46
|
-
inputSchema: {
|
|
47
|
-
type: 'object',
|
|
48
|
-
properties: {
|
|
49
|
-
query: {
|
|
50
|
-
type: 'string',
|
|
51
|
-
description: 'SQL查询语句(仅支持SELECT、SHOW、DESCRIBE、EXPLAIN)'
|
|
52
|
-
},
|
|
53
|
-
limit: {
|
|
54
|
-
type: 'number',
|
|
55
|
-
description: '限制返回结果数量,默认50,最大50',
|
|
56
|
-
default: 50
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
required: ['query']
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: 'query_with_time_format',
|
|
64
|
-
description: '执行SQL查询并自动格式化时间字段(生产环境专用,包含数据匿名化)',
|
|
65
|
-
inputSchema: {
|
|
66
|
-
type: 'object',
|
|
67
|
-
properties: {
|
|
68
|
-
query: {
|
|
69
|
-
type: 'string',
|
|
70
|
-
description: 'SQL查询语句,时间字段会自动转换为北京时间,个人信息会被匿名化'
|
|
71
|
-
},
|
|
72
|
-
limit: {
|
|
73
|
-
type: 'number',
|
|
74
|
-
description: '限制返回结果数量,默认50,最大50',
|
|
75
|
-
default: 50
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
required: ['query']
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: 'describe_table',
|
|
83
|
-
description: '获取表结构信息',
|
|
84
|
-
inputSchema: {
|
|
85
|
-
type: 'object',
|
|
86
|
-
properties: {
|
|
87
|
-
table_name: {
|
|
88
|
-
type: 'string',
|
|
89
|
-
description: '表名(可以包含或不包含fanwe_前缀)'
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
required: ['table_name']
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
name: 'list_tables',
|
|
97
|
-
description: '列出数据库中所有表',
|
|
98
|
-
inputSchema: {
|
|
99
|
-
type: 'object',
|
|
100
|
-
properties: {
|
|
101
|
-
pattern: {
|
|
102
|
-
type: 'string',
|
|
103
|
-
description: '表名匹配模式(可选)'
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: 'table_sample_data',
|
|
110
|
-
description: '获取表的示例数据(数据已匿名化)',
|
|
111
|
-
inputSchema: {
|
|
112
|
-
type: 'object',
|
|
113
|
-
properties: {
|
|
114
|
-
table_name: {
|
|
115
|
-
type: 'string',
|
|
116
|
-
description: '表名'
|
|
117
|
-
},
|
|
118
|
-
limit: {
|
|
119
|
-
type: 'number',
|
|
120
|
-
description: '返回记录数量,默认10,最大10',
|
|
121
|
-
default: 10
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
required: ['table_name']
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
name: 'table_count',
|
|
129
|
-
description: '获取表的记录总数',
|
|
130
|
-
inputSchema: {
|
|
131
|
-
type: 'object',
|
|
132
|
-
properties: {
|
|
133
|
-
table_name: {
|
|
134
|
-
type: 'string',
|
|
135
|
-
description: '表名'
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
required: ['table_name']
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
]
|
|
142
|
-
};
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// 处理工具调用
|
|
146
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
147
|
-
const { name, arguments: args } = request.params;
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
switch (name) {
|
|
151
|
-
case 'query_database':
|
|
152
|
-
return await handleQueryDatabase(args);
|
|
153
|
-
case 'query_with_time_format':
|
|
154
|
-
return await handleQueryWithTimeFormat(args);
|
|
155
|
-
case 'describe_table':
|
|
156
|
-
return await handleDescribeTable(args);
|
|
157
|
-
case 'list_tables':
|
|
158
|
-
return await handleListTables(args);
|
|
159
|
-
case 'table_sample_data':
|
|
160
|
-
return await handleTableSampleData(args);
|
|
161
|
-
case 'table_count':
|
|
162
|
-
return await handleTableCount(args);
|
|
163
|
-
default:
|
|
164
|
-
throw new Error(`未知工具: ${name}`);
|
|
165
|
-
}
|
|
166
|
-
} catch (error) {
|
|
167
|
-
return {
|
|
168
|
-
content: [
|
|
169
|
-
{
|
|
170
|
-
type: 'text',
|
|
171
|
-
text: `错误: ${error.message}`
|
|
172
|
-
}
|
|
173
|
-
]
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// 查询数据库(生产环境)
|
|
179
|
-
async function handleQueryDatabase(args) {
|
|
180
|
-
const { query, limit = 50 } = args;
|
|
181
|
-
|
|
182
|
-
// 限制最大记录数
|
|
183
|
-
const actualLimit = Math.min(limit, 50);
|
|
184
|
-
|
|
185
|
-
// 安全检查(包含频率限制和复杂度检测)
|
|
186
|
-
const securityCheck = performSecurityCheck(query, SECURITY_LEVELS.PRODUCTION);
|
|
187
|
-
if (!securityCheck.allowed) {
|
|
188
|
-
throw new Error(securityCheck.reason);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// 应用查询限制
|
|
192
|
-
const finalQuery = applyQueryLimits(query, actualLimit);
|
|
193
|
-
|
|
194
|
-
const rows = await executeQuery(pool, finalQuery);
|
|
195
|
-
|
|
196
|
-
// 数据匿名化
|
|
197
|
-
const anonymizedRows = anonymizeQueryResults(rows);
|
|
198
|
-
|
|
199
|
-
return {
|
|
200
|
-
content: [
|
|
201
|
-
{
|
|
202
|
-
type: 'text',
|
|
203
|
-
text: `🔴 生产环境查询结果 (${Array.isArray(anonymizedRows) ? anonymizedRows.length : 0} 条记录):\n🛡️ 个人信息已匿名化处理\n\n${JSON.stringify(anonymizedRows, null, 2)}`
|
|
204
|
-
}
|
|
205
|
-
]
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// 带时间格式化的查询(生产环境)
|
|
210
|
-
async function handleQueryWithTimeFormat(args) {
|
|
211
|
-
const { query, limit = 50 } = args;
|
|
212
|
-
|
|
213
|
-
// 限制最大记录数
|
|
214
|
-
const actualLimit = Math.min(limit, 50);
|
|
215
|
-
|
|
216
|
-
// 安全检查(包含频率限制和复杂度检测)
|
|
217
|
-
const securityCheck = performSecurityCheck(query, SECURITY_LEVELS.PRODUCTION);
|
|
218
|
-
if (!securityCheck.allowed) {
|
|
219
|
-
throw new Error(securityCheck.reason);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// 应用查询限制
|
|
223
|
-
const finalQuery = applyQueryLimits(query, actualLimit);
|
|
224
|
-
|
|
225
|
-
const rows = await executeQuery(pool, finalQuery);
|
|
226
|
-
const processedRows = processTimeFields(rows);
|
|
227
|
-
|
|
228
|
-
// 数据匿名化
|
|
229
|
-
const anonymizedRows = anonymizeQueryResults(processedRows);
|
|
230
|
-
|
|
231
|
-
return {
|
|
232
|
-
content: [
|
|
233
|
-
{
|
|
234
|
-
type: 'text',
|
|
235
|
-
text: `🔴 生产环境查询结果 (${Array.isArray(anonymizedRows) ? anonymizedRows.length : 0} 条记录):\n📅 时间字段已自动转换为北京时间\n🛡️ 个人信息已匿名化处理\n\n${JSON.stringify(anonymizedRows, null, 2)}`
|
|
236
|
-
}
|
|
237
|
-
]
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// 其他处理函数...
|
|
242
|
-
async function handleDescribeTable(args) {
|
|
243
|
-
const { table_name } = args;
|
|
244
|
-
const rows = await getTableStructure(pool, table_name);
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
content: [
|
|
248
|
-
{
|
|
249
|
-
type: 'text',
|
|
250
|
-
text: `表 ${normalizeTableName(table_name)} 的结构:\n\n${JSON.stringify(rows, null, 2)}`
|
|
251
|
-
}
|
|
252
|
-
]
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
async function handleListTables(args) {
|
|
257
|
-
const { pattern } = args;
|
|
258
|
-
const rows = await listTables(pool, pattern);
|
|
259
|
-
|
|
260
|
-
return {
|
|
261
|
-
content: [
|
|
262
|
-
{
|
|
263
|
-
type: 'text',
|
|
264
|
-
text: `数据库表列表:\n\n${JSON.stringify(rows, null, 2)}`
|
|
265
|
-
}
|
|
266
|
-
]
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
async function handleTableSampleData(args) {
|
|
271
|
-
const { table_name, limit = 10 } = args;
|
|
272
|
-
const actualLimit = Math.min(limit, 10); // 生产环境限制更严格
|
|
273
|
-
|
|
274
|
-
const rows = await getTableSampleData(pool, table_name, actualLimit);
|
|
275
|
-
const processedRows = processTimeFields(rows);
|
|
276
|
-
|
|
277
|
-
// 数据匿名化
|
|
278
|
-
const anonymizedRows = anonymizeQueryResults(processedRows);
|
|
279
|
-
|
|
280
|
-
return {
|
|
281
|
-
content: [
|
|
282
|
-
{
|
|
283
|
-
type: 'text',
|
|
284
|
-
text: `表 ${normalizeTableName(table_name)} 的示例数据 (前${actualLimit}条,已匿名化):\n\n${JSON.stringify(anonymizedRows, null, 2)}`
|
|
285
|
-
}
|
|
286
|
-
]
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async function handleTableCount(args) {
|
|
291
|
-
const { table_name } = args;
|
|
292
|
-
const count = await getTableCount(pool, table_name);
|
|
293
|
-
|
|
294
|
-
return {
|
|
295
|
-
content: [
|
|
296
|
-
{
|
|
297
|
-
type: 'text',
|
|
298
|
-
text: `表 ${normalizeTableName(table_name)} 共有 ${count} 条记录`
|
|
299
|
-
}
|
|
300
|
-
]
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// 启动服务器
|
|
305
|
-
async function main() {
|
|
306
|
-
const transport = new StdioServerTransport();
|
|
307
|
-
await server.connect(transport);
|
|
308
|
-
console.error(`🔴 VMOO生产环境MCP服务器已启动 - ${config.server.name} v${config.server.version}`);
|
|
309
|
-
console.error(`🔒 安全级别: ${config.security.level} - ${config.security.description}`);
|
|
310
|
-
console.error(`🛡️ 防护功能: 频率限制、防爬虫、数据匿名化`);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
main().catch(console.error);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import mysql from 'mysql2/promise';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// 导入共享模块
|
|
15
|
+
const { createConnectionPool, executeQuery, normalizeTableName, getTableStructure, getTableCount, getTableSampleData, listTables } = await import('../shared/database-utils.js');
|
|
16
|
+
const { SECURITY_LEVELS, performSecurityCheck, applyQueryLimits } = await import('../shared/security-utils.js');
|
|
17
|
+
const { processTimeFields } = await import('../shared/time-utils.js');
|
|
18
|
+
const { anonymizeQueryResults } = await import('../shared/privacy-utils.js');
|
|
19
|
+
|
|
20
|
+
// 读取配置文件
|
|
21
|
+
const config = JSON.parse(fs.readFileSync(path.join(__dirname, 'config.json'), 'utf8'));
|
|
22
|
+
|
|
23
|
+
// 创建数据库连接池
|
|
24
|
+
const pool = createConnectionPool(config.database);
|
|
25
|
+
|
|
26
|
+
// 创建MCP服务器
|
|
27
|
+
const server = new Server(
|
|
28
|
+
{
|
|
29
|
+
name: config.server.name,
|
|
30
|
+
version: config.server.version,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
capabilities: {
|
|
34
|
+
tools: {},
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// 注册工具
|
|
40
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
41
|
+
return {
|
|
42
|
+
tools: [
|
|
43
|
+
{
|
|
44
|
+
name: 'query_database',
|
|
45
|
+
description: '执行SQL查询语句(生产环境:仅支持查询,有频率限制和数据匿名化)',
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
query: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: 'SQL查询语句(仅支持SELECT、SHOW、DESCRIBE、EXPLAIN)'
|
|
52
|
+
},
|
|
53
|
+
limit: {
|
|
54
|
+
type: 'number',
|
|
55
|
+
description: '限制返回结果数量,默认50,最大50',
|
|
56
|
+
default: 50
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
required: ['query']
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'query_with_time_format',
|
|
64
|
+
description: '执行SQL查询并自动格式化时间字段(生产环境专用,包含数据匿名化)',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
query: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'SQL查询语句,时间字段会自动转换为北京时间,个人信息会被匿名化'
|
|
71
|
+
},
|
|
72
|
+
limit: {
|
|
73
|
+
type: 'number',
|
|
74
|
+
description: '限制返回结果数量,默认50,最大50',
|
|
75
|
+
default: 50
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
required: ['query']
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'describe_table',
|
|
83
|
+
description: '获取表结构信息',
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: 'object',
|
|
86
|
+
properties: {
|
|
87
|
+
table_name: {
|
|
88
|
+
type: 'string',
|
|
89
|
+
description: '表名(可以包含或不包含fanwe_前缀)'
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
required: ['table_name']
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'list_tables',
|
|
97
|
+
description: '列出数据库中所有表',
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
pattern: {
|
|
102
|
+
type: 'string',
|
|
103
|
+
description: '表名匹配模式(可选)'
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'table_sample_data',
|
|
110
|
+
description: '获取表的示例数据(数据已匿名化)',
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
table_name: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: '表名'
|
|
117
|
+
},
|
|
118
|
+
limit: {
|
|
119
|
+
type: 'number',
|
|
120
|
+
description: '返回记录数量,默认10,最大10',
|
|
121
|
+
default: 10
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
required: ['table_name']
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'table_count',
|
|
129
|
+
description: '获取表的记录总数',
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
table_name: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
description: '表名'
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
required: ['table_name']
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 处理工具调用
|
|
146
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
147
|
+
const { name, arguments: args } = request.params;
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
switch (name) {
|
|
151
|
+
case 'query_database':
|
|
152
|
+
return await handleQueryDatabase(args);
|
|
153
|
+
case 'query_with_time_format':
|
|
154
|
+
return await handleQueryWithTimeFormat(args);
|
|
155
|
+
case 'describe_table':
|
|
156
|
+
return await handleDescribeTable(args);
|
|
157
|
+
case 'list_tables':
|
|
158
|
+
return await handleListTables(args);
|
|
159
|
+
case 'table_sample_data':
|
|
160
|
+
return await handleTableSampleData(args);
|
|
161
|
+
case 'table_count':
|
|
162
|
+
return await handleTableCount(args);
|
|
163
|
+
default:
|
|
164
|
+
throw new Error(`未知工具: ${name}`);
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: 'text',
|
|
171
|
+
text: `错误: ${error.message}`
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// 查询数据库(生产环境)
|
|
179
|
+
async function handleQueryDatabase(args) {
|
|
180
|
+
const { query, limit = 50 } = args;
|
|
181
|
+
|
|
182
|
+
// 限制最大记录数
|
|
183
|
+
const actualLimit = Math.min(limit, 50);
|
|
184
|
+
|
|
185
|
+
// 安全检查(包含频率限制和复杂度检测)
|
|
186
|
+
const securityCheck = performSecurityCheck(query, SECURITY_LEVELS.PRODUCTION);
|
|
187
|
+
if (!securityCheck.allowed) {
|
|
188
|
+
throw new Error(securityCheck.reason);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 应用查询限制
|
|
192
|
+
const finalQuery = applyQueryLimits(query, actualLimit);
|
|
193
|
+
|
|
194
|
+
const rows = await executeQuery(pool, finalQuery);
|
|
195
|
+
|
|
196
|
+
// 数据匿名化
|
|
197
|
+
const anonymizedRows = anonymizeQueryResults(rows);
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
content: [
|
|
201
|
+
{
|
|
202
|
+
type: 'text',
|
|
203
|
+
text: `🔴 生产环境查询结果 (${Array.isArray(anonymizedRows) ? anonymizedRows.length : 0} 条记录):\n🛡️ 个人信息已匿名化处理\n\n${JSON.stringify(anonymizedRows, null, 2)}`
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 带时间格式化的查询(生产环境)
|
|
210
|
+
async function handleQueryWithTimeFormat(args) {
|
|
211
|
+
const { query, limit = 50 } = args;
|
|
212
|
+
|
|
213
|
+
// 限制最大记录数
|
|
214
|
+
const actualLimit = Math.min(limit, 50);
|
|
215
|
+
|
|
216
|
+
// 安全检查(包含频率限制和复杂度检测)
|
|
217
|
+
const securityCheck = performSecurityCheck(query, SECURITY_LEVELS.PRODUCTION);
|
|
218
|
+
if (!securityCheck.allowed) {
|
|
219
|
+
throw new Error(securityCheck.reason);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 应用查询限制
|
|
223
|
+
const finalQuery = applyQueryLimits(query, actualLimit);
|
|
224
|
+
|
|
225
|
+
const rows = await executeQuery(pool, finalQuery);
|
|
226
|
+
const processedRows = processTimeFields(rows);
|
|
227
|
+
|
|
228
|
+
// 数据匿名化
|
|
229
|
+
const anonymizedRows = anonymizeQueryResults(processedRows);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
content: [
|
|
233
|
+
{
|
|
234
|
+
type: 'text',
|
|
235
|
+
text: `🔴 生产环境查询结果 (${Array.isArray(anonymizedRows) ? anonymizedRows.length : 0} 条记录):\n📅 时间字段已自动转换为北京时间\n🛡️ 个人信息已匿名化处理\n\n${JSON.stringify(anonymizedRows, null, 2)}`
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 其他处理函数...
|
|
242
|
+
async function handleDescribeTable(args) {
|
|
243
|
+
const { table_name } = args;
|
|
244
|
+
const rows = await getTableStructure(pool, table_name);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
content: [
|
|
248
|
+
{
|
|
249
|
+
type: 'text',
|
|
250
|
+
text: `表 ${normalizeTableName(table_name)} 的结构:\n\n${JSON.stringify(rows, null, 2)}`
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function handleListTables(args) {
|
|
257
|
+
const { pattern } = args;
|
|
258
|
+
const rows = await listTables(pool, pattern);
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
content: [
|
|
262
|
+
{
|
|
263
|
+
type: 'text',
|
|
264
|
+
text: `数据库表列表:\n\n${JSON.stringify(rows, null, 2)}`
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function handleTableSampleData(args) {
|
|
271
|
+
const { table_name, limit = 10 } = args;
|
|
272
|
+
const actualLimit = Math.min(limit, 10); // 生产环境限制更严格
|
|
273
|
+
|
|
274
|
+
const rows = await getTableSampleData(pool, table_name, actualLimit);
|
|
275
|
+
const processedRows = processTimeFields(rows);
|
|
276
|
+
|
|
277
|
+
// 数据匿名化
|
|
278
|
+
const anonymizedRows = anonymizeQueryResults(processedRows);
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
content: [
|
|
282
|
+
{
|
|
283
|
+
type: 'text',
|
|
284
|
+
text: `表 ${normalizeTableName(table_name)} 的示例数据 (前${actualLimit}条,已匿名化):\n\n${JSON.stringify(anonymizedRows, null, 2)}`
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function handleTableCount(args) {
|
|
291
|
+
const { table_name } = args;
|
|
292
|
+
const count = await getTableCount(pool, table_name);
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
content: [
|
|
296
|
+
{
|
|
297
|
+
type: 'text',
|
|
298
|
+
text: `表 ${normalizeTableName(table_name)} 共有 ${count} 条记录`
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// 启动服务器
|
|
305
|
+
async function main() {
|
|
306
|
+
const transport = new StdioServerTransport();
|
|
307
|
+
await server.connect(transport);
|
|
308
|
+
console.error(`🔴 VMOO生产环境MCP服务器已启动 - ${config.server.name} v${config.server.version}`);
|
|
309
|
+
console.error(`🔒 安全级别: ${config.security.level} - ${config.security.description}`);
|
|
310
|
+
console.error(`🛡️ 防护功能: 频率限制、防爬虫、数据匿名化`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
main().catch(console.error);
|