@cmd233/mcp-database-server 1.1.6 → 1.2.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/dist/src/db/adapter.js +1 -1
- package/dist/src/db/index.js +44 -7
- package/dist/src/db/mysql-adapter.js +21 -21
- package/dist/src/db/postgresql-adapter.js +14 -12
- package/dist/src/db/sqlite-adapter.js +6 -6
- package/dist/src/db/sqlserver-adapter.js +40 -24
- package/dist/src/handlers/resourceHandlers.js +3 -3
- package/dist/src/handlers/toolHandlers.js +423 -28
- package/dist/src/index.js +18 -18
- package/dist/src/tools/insightTools.js +20 -8
- package/dist/src/tools/queryTools.js +21 -10
- package/dist/src/tools/schemaTools.js +225 -43
- package/dist/src/utils/cryptoUtils.js +119 -0
- package/package.json +1 -1
|
@@ -3,12 +3,20 @@ import { formatSuccessResponse } from '../utils/formatUtils.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* 添加业务洞察到备忘录
|
|
5
5
|
* @param insight 业务洞察文本
|
|
6
|
-
* @
|
|
6
|
+
* @param confirm 安全确认标志(默认 false,防止误操作)
|
|
7
|
+
* @returns 操作结果,包含新增记录的 ID
|
|
7
8
|
*/
|
|
8
|
-
export async function appendInsight(insight) {
|
|
9
|
+
export async function appendInsight(insight, confirm = false) {
|
|
9
10
|
try {
|
|
10
11
|
if (!insight) {
|
|
11
|
-
throw new Error("
|
|
12
|
+
throw new Error("洞察内容不能为空");
|
|
13
|
+
}
|
|
14
|
+
// 确认检查:防止误操作
|
|
15
|
+
if (!confirm) {
|
|
16
|
+
return formatSuccessResponse({
|
|
17
|
+
success: false,
|
|
18
|
+
message: "需要安全确认。设置 confirm=true 以继续添加洞察。"
|
|
19
|
+
});
|
|
12
20
|
}
|
|
13
21
|
// 如果 insights 表不存在则创建
|
|
14
22
|
await dbExec(`
|
|
@@ -18,12 +26,16 @@ export async function appendInsight(insight) {
|
|
|
18
26
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
19
27
|
)
|
|
20
28
|
`);
|
|
21
|
-
//
|
|
22
|
-
await dbRun("INSERT INTO mcp_insights (insight) VALUES (?)", [insight]);
|
|
23
|
-
return formatSuccessResponse({
|
|
29
|
+
// 插入洞察记录并获取返回的 ID
|
|
30
|
+
const result = await dbRun("INSERT INTO mcp_insights (insight) VALUES (?)", [insight]);
|
|
31
|
+
return formatSuccessResponse({
|
|
32
|
+
success: true,
|
|
33
|
+
message: "洞察已添加",
|
|
34
|
+
id: result.lastID
|
|
35
|
+
});
|
|
24
36
|
}
|
|
25
37
|
catch (error) {
|
|
26
|
-
throw new Error(
|
|
38
|
+
throw new Error(`添加洞察失败: ${error.message}`);
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
/**
|
|
@@ -49,6 +61,6 @@ export async function listInsights() {
|
|
|
49
61
|
return formatSuccessResponse(insights);
|
|
50
62
|
}
|
|
51
63
|
catch (error) {
|
|
52
|
-
throw new Error(
|
|
64
|
+
throw new Error(`列出洞察失败: ${error.message}`);
|
|
53
65
|
}
|
|
54
66
|
}
|
|
@@ -8,34 +8,45 @@ import { formatSuccessResponse, convertToCSV } from '../utils/formatUtils.js';
|
|
|
8
8
|
export async function readQuery(query) {
|
|
9
9
|
try {
|
|
10
10
|
if (!query.trim().toLowerCase().startsWith("select")) {
|
|
11
|
-
throw new Error("
|
|
11
|
+
throw new Error("read_query 只允许执行 SELECT 查询");
|
|
12
12
|
}
|
|
13
13
|
const result = await dbAll(query);
|
|
14
14
|
return formatSuccessResponse(result);
|
|
15
15
|
}
|
|
16
16
|
catch (error) {
|
|
17
|
-
throw new Error(`SQL
|
|
17
|
+
throw new Error(`SQL 错误: ${error.message}`);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* 执行数据修改 SQL 查询
|
|
22
22
|
* @param query 要执行的 SQL 查询
|
|
23
|
+
* @param confirm 安全确认标志(默认 false,防止误操作)
|
|
23
24
|
* @returns 受影响行的信息
|
|
24
25
|
*/
|
|
25
|
-
export async function writeQuery(query) {
|
|
26
|
+
export async function writeQuery(query, confirm = false) {
|
|
26
27
|
try {
|
|
27
28
|
const lowerQuery = query.trim().toLowerCase();
|
|
28
29
|
if (lowerQuery.startsWith("select")) {
|
|
29
|
-
throw new Error("
|
|
30
|
+
throw new Error("SELECT 操作请使用 read_query");
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
// 支持 INSERT、UPDATE、DELETE 和 TRUNCATE 操作
|
|
33
|
+
const supportedOperations = ["insert", "update", "delete", "truncate"];
|
|
34
|
+
const operation = supportedOperations.find(op => lowerQuery.startsWith(op));
|
|
35
|
+
if (!operation) {
|
|
36
|
+
throw new Error("write_query 只允许执行 INSERT、UPDATE、DELETE 或 TRUNCATE 操作");
|
|
37
|
+
}
|
|
38
|
+
// 确认检查:防止误操作
|
|
39
|
+
if (!confirm) {
|
|
40
|
+
return formatSuccessResponse({
|
|
41
|
+
success: false,
|
|
42
|
+
message: `需要安全确认。设置 confirm=true 以继续执行 ${operation.toUpperCase()} 操作。`
|
|
43
|
+
});
|
|
33
44
|
}
|
|
34
45
|
const result = await dbRun(query);
|
|
35
46
|
return formatSuccessResponse({ affected_rows: result.changes });
|
|
36
47
|
}
|
|
37
48
|
catch (error) {
|
|
38
|
-
throw new Error(`SQL
|
|
49
|
+
throw new Error(`SQL 错误: ${error.message}`);
|
|
39
50
|
}
|
|
40
51
|
}
|
|
41
52
|
/**
|
|
@@ -47,7 +58,7 @@ export async function writeQuery(query) {
|
|
|
47
58
|
export async function exportQuery(query, format) {
|
|
48
59
|
try {
|
|
49
60
|
if (!query.trim().toLowerCase().startsWith("select")) {
|
|
50
|
-
throw new Error("
|
|
61
|
+
throw new Error("export_query 只允许执行 SELECT 查询");
|
|
51
62
|
}
|
|
52
63
|
const result = await dbAll(query);
|
|
53
64
|
if (format === "csv") {
|
|
@@ -64,10 +75,10 @@ export async function exportQuery(query, format) {
|
|
|
64
75
|
return formatSuccessResponse(result);
|
|
65
76
|
}
|
|
66
77
|
else {
|
|
67
|
-
throw new Error("
|
|
78
|
+
throw new Error("不支持的导出格式。请使用 'csv' 或 'json'");
|
|
68
79
|
}
|
|
69
80
|
}
|
|
70
81
|
catch (error) {
|
|
71
|
-
throw new Error(
|
|
82
|
+
throw new Error(`导出错误: ${error.message}`);
|
|
72
83
|
}
|
|
73
84
|
}
|
|
@@ -1,37 +1,125 @@
|
|
|
1
|
-
import { dbAll, dbExec, getListTablesQuery, getDescribeTableQuery } from '../db/index.js';
|
|
1
|
+
import { dbAll, dbExec, getListTablesQuery, getDescribeTableQuery, getListViewsQuery, getViewDefinitionQuery, supportsViews } from '../db/index.js';
|
|
2
2
|
import { formatSuccessResponse } from '../utils/formatUtils.js';
|
|
3
|
+
/**
|
|
4
|
+
* 检查数据库对象是否存在
|
|
5
|
+
* @param objectName 对象名
|
|
6
|
+
* @param objectType 对象类型 ('table' | 'view')
|
|
7
|
+
* @returns 如果存在返回 true,否则返回 false
|
|
8
|
+
*/
|
|
9
|
+
async function checkObjectExists(objectName, objectType) {
|
|
10
|
+
if (objectType === 'table') {
|
|
11
|
+
const query = getListTablesQuery();
|
|
12
|
+
const objects = await dbAll(query);
|
|
13
|
+
return objects.some(obj => obj.name === objectName);
|
|
14
|
+
}
|
|
15
|
+
else if (supportsViews()) {
|
|
16
|
+
const query = getListViewsQuery();
|
|
17
|
+
const objects = await dbAll(query);
|
|
18
|
+
return objects.some(obj => obj.name === objectName);
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 格式化列结构信息
|
|
24
|
+
* @param columns 原始列数据数组
|
|
25
|
+
* @returns 格式化后的列数组
|
|
26
|
+
*/
|
|
27
|
+
function formatColumns(columns) {
|
|
28
|
+
return columns.map((col) => ({
|
|
29
|
+
name: col.name,
|
|
30
|
+
type: col.type,
|
|
31
|
+
notnull: !!col.notnull,
|
|
32
|
+
default_value: col.dflt_value,
|
|
33
|
+
primary_key: !!col.pk,
|
|
34
|
+
comment: col.comment || null
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 从 SQL 语句中提取表名
|
|
39
|
+
* @param query SQL 语句
|
|
40
|
+
* @param operation SQL 操作类型(CREATE TABLE、ALTER TABLE 等)
|
|
41
|
+
* @returns 提取的表名或 null
|
|
42
|
+
*/
|
|
43
|
+
function extractTableName(query, operation) {
|
|
44
|
+
try {
|
|
45
|
+
const normalizedQuery = query.trim().replace(/\s+/g, ' ');
|
|
46
|
+
const operationPrefix = operation.toLowerCase();
|
|
47
|
+
if (!normalizedQuery.toLowerCase().startsWith(operationPrefix)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// 移除操作前缀后的剩余部分
|
|
51
|
+
const afterOperation = normalizedQuery.substring(operationPrefix.length).trim();
|
|
52
|
+
// 处理 IF NOT EXISTS 或 IF EXISTS 等子句
|
|
53
|
+
const patterns = [
|
|
54
|
+
/^if\s+not\s+exists\s+([^\s(]+)/i, // CREATE TABLE IF NOT EXISTS tablename
|
|
55
|
+
/^if\s+exists\s+([^\s(]+)/i, // DROP TABLE IF EXISTS tablename
|
|
56
|
+
/^([^\s(]+)/ // CREATE TABLE tablename
|
|
57
|
+
];
|
|
58
|
+
for (const pattern of patterns) {
|
|
59
|
+
const match = afterOperation.match(pattern);
|
|
60
|
+
if (match && match[1]) {
|
|
61
|
+
// 移除引号(如果有)
|
|
62
|
+
return match[1].replace(/^[`"[]|[`"\]]$/g, '');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
3
71
|
/**
|
|
4
72
|
* 在数据库中创建新表
|
|
5
73
|
* @param query CREATE TABLE SQL 语句
|
|
74
|
+
* @param confirm 安全确认标志(默认 false,防止误操作)
|
|
6
75
|
* @returns 操作结果
|
|
7
76
|
*/
|
|
8
|
-
export async function createTable(query) {
|
|
77
|
+
export async function createTable(query, confirm = false) {
|
|
9
78
|
try {
|
|
10
79
|
if (!query.trim().toLowerCase().startsWith("create table")) {
|
|
11
|
-
throw new Error("
|
|
80
|
+
throw new Error("只允许执行 CREATE TABLE 语句");
|
|
81
|
+
}
|
|
82
|
+
// 确认检查:防止误操作
|
|
83
|
+
if (!confirm) {
|
|
84
|
+
const tableName = extractTableName(query, "CREATE TABLE");
|
|
85
|
+
const tableInfo = tableName ? ` '${tableName}'` : '';
|
|
86
|
+
return formatSuccessResponse({
|
|
87
|
+
success: false,
|
|
88
|
+
message: `需要安全确认。设置 confirm=true 以继续创建表${tableInfo}。`
|
|
89
|
+
});
|
|
12
90
|
}
|
|
13
91
|
await dbExec(query);
|
|
14
|
-
return formatSuccessResponse({ success: true, message: "
|
|
92
|
+
return formatSuccessResponse({ success: true, message: "表创建成功" });
|
|
15
93
|
}
|
|
16
94
|
catch (error) {
|
|
17
|
-
throw new Error(`SQL
|
|
95
|
+
throw new Error(`SQL 错误: ${error.message}`);
|
|
18
96
|
}
|
|
19
97
|
}
|
|
20
98
|
/**
|
|
21
99
|
* 修改现有表的结构
|
|
22
100
|
* @param query ALTER TABLE SQL 语句
|
|
101
|
+
* @param confirm 安全确认标志(默认 false,防止误操作)
|
|
23
102
|
* @returns 操作结果
|
|
24
103
|
*/
|
|
25
|
-
export async function alterTable(query) {
|
|
104
|
+
export async function alterTable(query, confirm = false) {
|
|
26
105
|
try {
|
|
27
106
|
if (!query.trim().toLowerCase().startsWith("alter table")) {
|
|
28
|
-
throw new Error("
|
|
107
|
+
throw new Error("只允许执行 ALTER TABLE 语句");
|
|
108
|
+
}
|
|
109
|
+
// 确认检查:防止误操作
|
|
110
|
+
if (!confirm) {
|
|
111
|
+
const tableName = extractTableName(query, "ALTER TABLE");
|
|
112
|
+
const tableInfo = tableName ? ` '${tableName}'` : '';
|
|
113
|
+
return formatSuccessResponse({
|
|
114
|
+
success: false,
|
|
115
|
+
message: `需要安全确认。设置 confirm=true 以继续修改表结构${tableInfo}。`
|
|
116
|
+
});
|
|
29
117
|
}
|
|
30
118
|
await dbExec(query);
|
|
31
|
-
return formatSuccessResponse({ success: true, message: "
|
|
119
|
+
return formatSuccessResponse({ success: true, message: "表结构修改成功" });
|
|
32
120
|
}
|
|
33
121
|
catch (error) {
|
|
34
|
-
throw new Error(`SQL
|
|
122
|
+
throw new Error(`SQL 错误: ${error.message}`);
|
|
35
123
|
}
|
|
36
124
|
}
|
|
37
125
|
/**
|
|
@@ -43,77 +131,171 @@ export async function alterTable(query) {
|
|
|
43
131
|
export async function dropTable(tableName, confirm) {
|
|
44
132
|
try {
|
|
45
133
|
if (!tableName) {
|
|
46
|
-
throw new Error("
|
|
134
|
+
throw new Error("表名不能为空");
|
|
47
135
|
}
|
|
48
136
|
if (!confirm) {
|
|
49
137
|
return formatSuccessResponse({
|
|
50
138
|
success: false,
|
|
51
|
-
message: "
|
|
139
|
+
message: "需要安全确认。设置 confirm=true 以继续删除表。"
|
|
52
140
|
});
|
|
53
141
|
}
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const tableNames = tables.map(t => t.name);
|
|
58
|
-
if (!tableNames.includes(tableName)) {
|
|
59
|
-
throw new Error(`Table '${tableName}' does not exist`);
|
|
142
|
+
// 检查表是否存在
|
|
143
|
+
if (!(await checkObjectExists(tableName, 'table'))) {
|
|
144
|
+
throw new Error(`表 '${tableName}' 不存在`);
|
|
60
145
|
}
|
|
61
|
-
//
|
|
146
|
+
// 删除表
|
|
62
147
|
await dbExec(`DROP TABLE "${tableName}"`);
|
|
63
148
|
return formatSuccessResponse({
|
|
64
149
|
success: true,
|
|
65
|
-
message:
|
|
150
|
+
message: `表 '${tableName}' 删除成功`
|
|
66
151
|
});
|
|
67
152
|
}
|
|
68
153
|
catch (error) {
|
|
69
|
-
throw new Error(
|
|
154
|
+
throw new Error(`删除表失败: ${error.message}`);
|
|
70
155
|
}
|
|
71
156
|
}
|
|
72
157
|
/**
|
|
73
158
|
* 列出数据库中的所有表
|
|
159
|
+
* @param includeViews 是否包含视图(默认 false)
|
|
74
160
|
* @returns 表名数组
|
|
75
161
|
*/
|
|
76
|
-
export async function listTables() {
|
|
162
|
+
export async function listTables(includeViews = false) {
|
|
77
163
|
try {
|
|
78
|
-
//
|
|
164
|
+
// 使用适配器特定的查询来列出表
|
|
79
165
|
const query = getListTablesQuery();
|
|
80
166
|
const tables = await dbAll(query);
|
|
81
|
-
|
|
167
|
+
const result = tables.map((t) => ({ name: t.name, type: 'table' }));
|
|
168
|
+
// 如果需要包含视图且数据库支持视图
|
|
169
|
+
if (includeViews && supportsViews()) {
|
|
170
|
+
const viewsQuery = getListViewsQuery();
|
|
171
|
+
const views = await dbAll(viewsQuery);
|
|
172
|
+
result.push(...views.map((v) => ({ name: v.name, type: 'view' })));
|
|
173
|
+
}
|
|
174
|
+
return formatSuccessResponse(result);
|
|
82
175
|
}
|
|
83
176
|
catch (error) {
|
|
84
|
-
throw new Error(
|
|
177
|
+
throw new Error(`列出表失败: ${error.message}`);
|
|
85
178
|
}
|
|
86
179
|
}
|
|
87
180
|
/**
|
|
88
181
|
* 获取指定表的结构信息
|
|
89
|
-
*
|
|
90
|
-
* @
|
|
182
|
+
* 支持实体表和视图
|
|
183
|
+
* @param tableName 要描述的表名或视图名
|
|
184
|
+
* @returns 表/视图的列定义
|
|
91
185
|
*/
|
|
92
186
|
export async function describeTable(tableName) {
|
|
93
187
|
try {
|
|
94
188
|
if (!tableName) {
|
|
95
|
-
throw new Error("
|
|
189
|
+
throw new Error("表名不能为空");
|
|
96
190
|
}
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (!tableNames.includes(tableName)) {
|
|
102
|
-
throw new Error(`Table '${tableName}' does not exist`);
|
|
191
|
+
// 检查是表还是视图
|
|
192
|
+
let objectType = 'table';
|
|
193
|
+
if (await checkObjectExists(tableName, 'table')) {
|
|
194
|
+
objectType = 'table';
|
|
103
195
|
}
|
|
104
|
-
|
|
196
|
+
else if (supportsViews() && await checkObjectExists(tableName, 'view')) {
|
|
197
|
+
objectType = 'view';
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
throw new Error(supportsViews() ? `表或视图 '${tableName}' 不存在` : `表 '${tableName}' 不存在`);
|
|
201
|
+
}
|
|
202
|
+
// 使用适配器特定的查询来描述表/视图结构
|
|
105
203
|
const descQuery = getDescribeTableQuery(tableName);
|
|
106
204
|
const columns = await dbAll(descQuery);
|
|
107
|
-
return formatSuccessResponse(
|
|
108
|
-
name:
|
|
109
|
-
type:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
})
|
|
205
|
+
return formatSuccessResponse({
|
|
206
|
+
name: tableName,
|
|
207
|
+
type: objectType,
|
|
208
|
+
columns: formatColumns(columns)
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
throw new Error(`描述表结构失败: ${error.message}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 列出数据库中的所有视图
|
|
217
|
+
* 仅支持 SQL Server
|
|
218
|
+
* @returns 视图名数组
|
|
219
|
+
*/
|
|
220
|
+
export async function listViews() {
|
|
221
|
+
try {
|
|
222
|
+
if (!supportsViews()) {
|
|
223
|
+
throw new Error("视图功能仅支持 SQL Server 数据库");
|
|
224
|
+
}
|
|
225
|
+
const query = getListViewsQuery();
|
|
226
|
+
const views = await dbAll(query);
|
|
227
|
+
return formatSuccessResponse(views.map((v) => v.name));
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
throw new Error(`列出视图失败: ${error.message}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 获取指定视图的结构信息
|
|
235
|
+
* 仅支持 SQL Server
|
|
236
|
+
* @param viewName 视图名
|
|
237
|
+
* @returns 视图的列定义
|
|
238
|
+
*/
|
|
239
|
+
export async function describeView(viewName) {
|
|
240
|
+
try {
|
|
241
|
+
if (!viewName) {
|
|
242
|
+
throw new Error("视图名不能为空");
|
|
243
|
+
}
|
|
244
|
+
if (!supportsViews()) {
|
|
245
|
+
throw new Error("视图功能仅支持 SQL Server 数据库");
|
|
246
|
+
}
|
|
247
|
+
// 检查视图是否存在
|
|
248
|
+
if (!(await checkObjectExists(viewName, 'view'))) {
|
|
249
|
+
throw new Error(`视图 '${viewName}' 不存在`);
|
|
250
|
+
}
|
|
251
|
+
// 使用相同的 describe 查询获取视图列结构
|
|
252
|
+
const descQuery = getDescribeTableQuery(viewName);
|
|
253
|
+
const columns = await dbAll(descQuery);
|
|
254
|
+
return formatSuccessResponse({
|
|
255
|
+
name: viewName,
|
|
256
|
+
type: 'view',
|
|
257
|
+
columns: formatColumns(columns)
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
throw new Error(`描述视图结构失败: ${error.message}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* 获取视图的定义 SQL
|
|
266
|
+
* 仅支持 SQL Server
|
|
267
|
+
* 注意: 使用 WITH ENCRYPTION 创建的视图无法获取定义
|
|
268
|
+
* @param viewName 视图名
|
|
269
|
+
* @returns 视图定义 SQL
|
|
270
|
+
*/
|
|
271
|
+
export async function getViewDefinition(viewName) {
|
|
272
|
+
try {
|
|
273
|
+
if (!viewName) {
|
|
274
|
+
throw new Error("视图名不能为空");
|
|
275
|
+
}
|
|
276
|
+
if (!supportsViews()) {
|
|
277
|
+
throw new Error("视图功能仅支持 SQL Server 数据库");
|
|
278
|
+
}
|
|
279
|
+
// 检查视图是否存在
|
|
280
|
+
if (!(await checkObjectExists(viewName, 'view'))) {
|
|
281
|
+
throw new Error(`视图 '${viewName}' 不存在`);
|
|
282
|
+
}
|
|
283
|
+
// 获取视图定义
|
|
284
|
+
const defQuery = getViewDefinitionQuery(viewName);
|
|
285
|
+
const result = await dbAll(defQuery);
|
|
286
|
+
if (result.length === 0 || !result[0].definition) {
|
|
287
|
+
return formatSuccessResponse({
|
|
288
|
+
name: viewName,
|
|
289
|
+
definition: null,
|
|
290
|
+
message: "视图定义不可用(可能使用 WITH ENCRYPTION 创建)"
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
return formatSuccessResponse({
|
|
294
|
+
name: viewName,
|
|
295
|
+
definition: result[0].definition
|
|
296
|
+
});
|
|
115
297
|
}
|
|
116
298
|
catch (error) {
|
|
117
|
-
throw new Error(
|
|
299
|
+
throw new Error(`获取视图定义失败: ${error.message}`);
|
|
118
300
|
}
|
|
119
301
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 加密和确认码工具模块
|
|
3
|
+
* 用于两步确认机制,防止 AI 模型自动执行危险操作
|
|
4
|
+
*/
|
|
5
|
+
// 硬编码密钥,用于生成确认码
|
|
6
|
+
const CONFIRM_SECRET_KEY = 'mcp-db-server-confirm-key-v1';
|
|
7
|
+
/**
|
|
8
|
+
* 获取当前分钟时间戳
|
|
9
|
+
* @returns 当前分钟的时间戳字符串
|
|
10
|
+
*/
|
|
11
|
+
function getMinuteTimestamp() {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
// 转换为分钟级时间戳(去掉秒和毫秒)
|
|
14
|
+
const minuteTimestamp = Math.floor(now / 60000);
|
|
15
|
+
return minuteTimestamp.toString();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 生成简单哈希
|
|
19
|
+
* 使用简单的字符串哈希算法生成固定长度的哈希值
|
|
20
|
+
* @param content 要哈希的内容
|
|
21
|
+
* @returns 8位十六进制哈希字符串
|
|
22
|
+
*/
|
|
23
|
+
function generateSimpleHash(content) {
|
|
24
|
+
let hash = 0;
|
|
25
|
+
// 使用简单的字符串哈希算法
|
|
26
|
+
for (let i = 0; i < content.length; i++) {
|
|
27
|
+
const char = content.charCodeAt(i);
|
|
28
|
+
hash = ((hash << 5) - hash) + char;
|
|
29
|
+
hash = hash & hash; // 转换为 32 位整数
|
|
30
|
+
}
|
|
31
|
+
// 转换为无符号整数并转为十六进制,取前 8 位
|
|
32
|
+
const hashStr = Math.abs(hash).toString(16).padStart(8, '0');
|
|
33
|
+
return hashStr.substring(0, 8);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 标准化操作内容
|
|
37
|
+
* 移除多余的空格,统一大小写,确保相同操作生成相同的确认码
|
|
38
|
+
* @param query 原始 SQL 查询或操作内容
|
|
39
|
+
* @returns 标准化后的操作内容
|
|
40
|
+
*/
|
|
41
|
+
export function normalizeOperationContent(query) {
|
|
42
|
+
// 移除前后空格,将多个连续空格替换为单个空格
|
|
43
|
+
return query.trim().replace(/\s+/g, ' ');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 生成确认码
|
|
47
|
+
* 基于操作内容和当前分钟时间戳生成 8 位十六进制确认码
|
|
48
|
+
* @param operationContent 操作内容(SQL 查询或操作描述)
|
|
49
|
+
* @returns 8 位十六进制确认码
|
|
50
|
+
*/
|
|
51
|
+
export function generateConfirmCode(operationContent) {
|
|
52
|
+
const normalized = normalizeOperationContent(operationContent);
|
|
53
|
+
const minuteTimestamp = getMinuteTimestamp();
|
|
54
|
+
const hashInput = normalized + minuteTimestamp + CONFIRM_SECRET_KEY;
|
|
55
|
+
return generateSimpleHash(hashInput);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 验证确认码
|
|
59
|
+
* 验证确认码是否有效,支持当前分钟和上一分钟的确认码(2 分钟窗口)
|
|
60
|
+
* @param operationContent 操作内容(SQL 查询或操作描述)
|
|
61
|
+
* @param confirmCode 要验证的确认码
|
|
62
|
+
* @returns 确认码是否有效
|
|
63
|
+
*/
|
|
64
|
+
export function verifyConfirmCode(operationContent, confirmCode) {
|
|
65
|
+
if (!confirmCode || confirmCode.length !== 8) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const normalized = normalizeOperationContent(operationContent);
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const currentMinute = Math.floor(now / 60000);
|
|
71
|
+
const previousMinute = currentMinute - 1;
|
|
72
|
+
// 验证当前分钟的确认码
|
|
73
|
+
const currentHashInput = normalized + currentMinute.toString() + CONFIRM_SECRET_KEY;
|
|
74
|
+
const currentCode = generateSimpleHash(currentHashInput);
|
|
75
|
+
if (currentCode === confirmCode) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
// 验证上一分钟的确认码
|
|
79
|
+
const previousHashInput = normalized + previousMinute.toString() + CONFIRM_SECRET_KEY;
|
|
80
|
+
const previousCode = generateSimpleHash(previousHashInput);
|
|
81
|
+
return previousCode === confirmCode;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 格式化预览响应
|
|
85
|
+
* 生成预览模式的响应格式,包含确认码和操作说明
|
|
86
|
+
* @param toolName 工具名称
|
|
87
|
+
* @param operationContent 操作内容
|
|
88
|
+
* @param additionalInfo 额外信息(如警告消息)
|
|
89
|
+
* @returns 预览响应对象
|
|
90
|
+
*/
|
|
91
|
+
export function formatPreviewResponse(toolName, operationContent, additionalInfo) {
|
|
92
|
+
const confirmCode = generateConfirmCode(operationContent);
|
|
93
|
+
const response = {
|
|
94
|
+
preview: true,
|
|
95
|
+
tool_name: toolName,
|
|
96
|
+
confirm_code: confirmCode,
|
|
97
|
+
message: "⚠️ 预览模式:此操作需要确认",
|
|
98
|
+
operation_content: normalizeOperationContent(operationContent),
|
|
99
|
+
instructions: [
|
|
100
|
+
"请仔细检查上述操作内容",
|
|
101
|
+
"如确认执行,请重新调用此工具并提供确认码",
|
|
102
|
+
`参数示例: { "preview": false, "confirm_code": "${confirmCode}" }`
|
|
103
|
+
]
|
|
104
|
+
};
|
|
105
|
+
// 添加额外信息
|
|
106
|
+
if (additionalInfo?.warning) {
|
|
107
|
+
response.warning = additionalInfo.warning;
|
|
108
|
+
}
|
|
109
|
+
if (additionalInfo?.operation) {
|
|
110
|
+
response.operation = additionalInfo.operation;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
content: [{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: JSON.stringify(response, null, 2)
|
|
116
|
+
}],
|
|
117
|
+
isError: false
|
|
118
|
+
};
|
|
119
|
+
}
|
package/package.json
CHANGED