@cmd233/mcp-database-server 1.1.7 → 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/index.js +37 -0
- package/dist/src/db/mysql-adapter.js +6 -6
- package/dist/src/db/postgresql-adapter.js +7 -5
- package/dist/src/db/sqlite-adapter.js +3 -3
- package/dist/src/db/sqlserver-adapter.js +38 -22
- package/dist/src/handlers/toolHandlers.js +422 -27
- package/dist/src/tools/insightTools.js +17 -5
- package/dist/src/tools/queryTools.js +14 -3
- package/dist/src/tools/schemaTools.js +209 -27
- package/dist/src/utils/cryptoUtils.js +119 -0
- package/package.json +1 -1
package/dist/src/db/index.js
CHANGED
|
@@ -93,3 +93,40 @@ export function getDescribeTableQuery(tableName) {
|
|
|
93
93
|
}
|
|
94
94
|
return dbAdapter.getDescribeTableQuery(tableName);
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* 获取列出视图的数据库特定查询
|
|
98
|
+
* 仅 SQL Server 支持,其他数据库返回空结果
|
|
99
|
+
*/
|
|
100
|
+
export function getListViewsQuery() {
|
|
101
|
+
if (!dbAdapter) {
|
|
102
|
+
throw new Error("数据库未初始化");
|
|
103
|
+
}
|
|
104
|
+
if (dbAdapter.getListViewsQuery) {
|
|
105
|
+
return dbAdapter.getListViewsQuery();
|
|
106
|
+
}
|
|
107
|
+
// 不支持视图的数据库返回空结果查询
|
|
108
|
+
return "SELECT 1 as name WHERE 1=0";
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 获取视图定义的数据库特定查询
|
|
112
|
+
* 仅 SQL Server 支持
|
|
113
|
+
* @param viewName 视图名
|
|
114
|
+
*/
|
|
115
|
+
export function getViewDefinitionQuery(viewName) {
|
|
116
|
+
if (!dbAdapter) {
|
|
117
|
+
throw new Error("数据库未初始化");
|
|
118
|
+
}
|
|
119
|
+
if (dbAdapter.getViewDefinitionQuery) {
|
|
120
|
+
return dbAdapter.getViewDefinitionQuery(viewName);
|
|
121
|
+
}
|
|
122
|
+
throw new Error("当前数据库不支持视图功能");
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 检查数据库是否支持视图功能
|
|
126
|
+
*/
|
|
127
|
+
export function supportsViews() {
|
|
128
|
+
if (!dbAdapter) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return dbAdapter.supportsViews ? dbAdapter.supportsViews() : false;
|
|
132
|
+
}
|
|
@@ -55,7 +55,7 @@ export class MysqlAdapter {
|
|
|
55
55
|
throw new Error("AWS IAM 认证需要 AWS 用户名参数");
|
|
56
56
|
}
|
|
57
57
|
try {
|
|
58
|
-
console.info(`[INFO]
|
|
58
|
+
console.info(`[INFO] 正在为区域 ${this.awsRegion} 生成 AWS 认证令牌, 主机: ${this.host}, 用户: ${this.config.user}`);
|
|
59
59
|
const signer = new Signer({
|
|
60
60
|
region: this.awsRegion,
|
|
61
61
|
hostname: this.host,
|
|
@@ -63,7 +63,7 @@ export class MysqlAdapter {
|
|
|
63
63
|
username: this.config.user,
|
|
64
64
|
});
|
|
65
65
|
const token = await signer.getAuthToken();
|
|
66
|
-
console.info(`[INFO] AWS
|
|
66
|
+
console.info(`[INFO] AWS 认证令牌生成成功`);
|
|
67
67
|
return token;
|
|
68
68
|
}
|
|
69
69
|
catch (err) {
|
|
@@ -76,10 +76,10 @@ export class MysqlAdapter {
|
|
|
76
76
|
*/
|
|
77
77
|
async init() {
|
|
78
78
|
try {
|
|
79
|
-
console.info(`[INFO]
|
|
79
|
+
console.info(`[INFO] 正在连接 MySQL: ${this.host}, 数据库: ${this.database}`);
|
|
80
80
|
// 处理 AWS IAM 认证
|
|
81
81
|
if (this.awsIamAuth) {
|
|
82
|
-
console.info(`[INFO]
|
|
82
|
+
console.info(`[INFO] 正在为用户 ${this.config.user} 使用 AWS IAM 认证`);
|
|
83
83
|
try {
|
|
84
84
|
const authToken = await this.generateAwsAuthToken();
|
|
85
85
|
// 使用生成的令牌作为密码创建新配置
|
|
@@ -97,10 +97,10 @@ export class MysqlAdapter {
|
|
|
97
97
|
else {
|
|
98
98
|
this.connection = await mysql.createConnection(this.config);
|
|
99
99
|
}
|
|
100
|
-
console.info(`[INFO] MySQL
|
|
100
|
+
console.info(`[INFO] MySQL 连接成功建立`);
|
|
101
101
|
}
|
|
102
102
|
catch (err) {
|
|
103
|
-
console.error(`[ERROR] MySQL
|
|
103
|
+
console.error(`[ERROR] MySQL 连接错误: ${err.message}`);
|
|
104
104
|
if (this.awsIamAuth) {
|
|
105
105
|
throw new Error(`使用 AWS IAM 认证连接 MySQL 失败: ${err.message}。请验证您的 AWS 凭据、IAM 权限和 RDS 配置。`);
|
|
106
106
|
}
|
|
@@ -24,7 +24,7 @@ export class PostgresqlAdapter {
|
|
|
24
24
|
*/
|
|
25
25
|
async init() {
|
|
26
26
|
try {
|
|
27
|
-
console.error(`[INFO]
|
|
27
|
+
console.error(`[INFO] 正在连接 PostgreSQL: ${this.host}, 数据库: ${this.database}`);
|
|
28
28
|
console.error(`[DEBUG] Connection details:`, {
|
|
29
29
|
host: this.host,
|
|
30
30
|
database: this.database,
|
|
@@ -35,10 +35,10 @@ export class PostgresqlAdapter {
|
|
|
35
35
|
});
|
|
36
36
|
this.client = new pg.Client(this.config);
|
|
37
37
|
await this.client.connect();
|
|
38
|
-
console.error(`[INFO] PostgreSQL
|
|
38
|
+
console.error(`[INFO] PostgreSQL 连接成功建立`);
|
|
39
39
|
}
|
|
40
40
|
catch (err) {
|
|
41
|
-
console.error(`[ERROR] PostgreSQL
|
|
41
|
+
console.error(`[ERROR] PostgreSQL 连接错误: ${err.message}`);
|
|
42
42
|
throw new Error(`连接 PostgreSQL 失败: ${err.message}`);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -54,7 +54,8 @@ export class PostgresqlAdapter {
|
|
|
54
54
|
}
|
|
55
55
|
try {
|
|
56
56
|
// PostgreSQL 使用 $1, $2 等作为参数化查询的占位符
|
|
57
|
-
|
|
57
|
+
let paramIndex = 0;
|
|
58
|
+
const preparedQuery = query.replace(/\?/g, () => `$${++paramIndex}`);
|
|
58
59
|
const result = await this.client.query(preparedQuery, params);
|
|
59
60
|
return result.rows;
|
|
60
61
|
}
|
|
@@ -74,7 +75,8 @@ export class PostgresqlAdapter {
|
|
|
74
75
|
}
|
|
75
76
|
try {
|
|
76
77
|
// 将 ? 替换为编号参数
|
|
77
|
-
|
|
78
|
+
let paramIndex = 0;
|
|
79
|
+
const preparedQuery = query.replace(/\?/g, () => `$${++paramIndex}`);
|
|
78
80
|
let lastID = 0;
|
|
79
81
|
let changes = 0;
|
|
80
82
|
// 对于 INSERT 查询,尝试获取插入的 ID
|
|
@@ -13,14 +13,14 @@ export class SqliteAdapter {
|
|
|
13
13
|
async init() {
|
|
14
14
|
return new Promise((resolve, reject) => {
|
|
15
15
|
// 确保数据库路径可访问
|
|
16
|
-
console.error(`[INFO]
|
|
16
|
+
console.error(`[INFO] 正在打开 SQLite 数据库: ${this.dbPath}`);
|
|
17
17
|
this.db = new sqlite3.Database(this.dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
|
|
18
18
|
if (err) {
|
|
19
|
-
console.error(`[ERROR] SQLite
|
|
19
|
+
console.error(`[ERROR] SQLite 连接错误: ${err.message}`);
|
|
20
20
|
reject(err);
|
|
21
21
|
}
|
|
22
22
|
else {
|
|
23
|
-
console.error("[INFO] SQLite
|
|
23
|
+
console.error("[INFO] SQLite 数据库成功打开");
|
|
24
24
|
resolve();
|
|
25
25
|
}
|
|
26
26
|
});
|
|
@@ -68,27 +68,27 @@ export class SqlServerAdapter {
|
|
|
68
68
|
await this.pool.close();
|
|
69
69
|
}
|
|
70
70
|
catch (closeErr) {
|
|
71
|
-
console.error(`[WARN]
|
|
71
|
+
console.error(`[WARN] 关闭旧连接池时出错: ${closeErr.message}`);
|
|
72
72
|
}
|
|
73
73
|
this.pool = null;
|
|
74
74
|
}
|
|
75
|
-
console.error(`[INFO]
|
|
75
|
+
console.error(`[INFO] 正在连接 SQL Server: ${this.server}, 数据库: ${this.database}`);
|
|
76
76
|
const pool = new sql.ConnectionPool(this.config);
|
|
77
77
|
// 使用单次监听器防止内存泄漏
|
|
78
78
|
pool.once('error', (err) => {
|
|
79
|
-
console.error(`[ERROR] SQL Server
|
|
79
|
+
console.error(`[ERROR] SQL Server 连接池错误: ${err.message}`);
|
|
80
80
|
// 标记连接池为不可用,下次查询时会自动重连
|
|
81
81
|
if (this.pool === pool) {
|
|
82
82
|
this.pool = null;
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
this.pool = await pool.connect();
|
|
86
|
-
console.error(`[INFO] SQL Server
|
|
86
|
+
console.error(`[INFO] SQL Server 连接成功建立`);
|
|
87
87
|
return this.pool;
|
|
88
88
|
}
|
|
89
89
|
catch (err) {
|
|
90
90
|
this.pool = null;
|
|
91
|
-
console.error(`[ERROR] SQL Server
|
|
91
|
+
console.error(`[ERROR] SQL Server 连接错误: ${err.message}`);
|
|
92
92
|
throw new Error(`连接 SQL Server 失败: ${err.message}`);
|
|
93
93
|
}
|
|
94
94
|
finally {
|
|
@@ -139,7 +139,7 @@ export class SqlServerAdapter {
|
|
|
139
139
|
// 只有在获取连接池后(即 poolAcquired = true)发生的连接错误才重试
|
|
140
140
|
// ensureConnection 本身的错误(如认证失败、连接超时)不应重试
|
|
141
141
|
if (isConnectionError && poolAcquired && attempt < retries && this.pool !== null) {
|
|
142
|
-
console.error(`[WARN]
|
|
142
|
+
console.error(`[WARN] 检测到连接错误,正在尝试重新连接 (尝试 ${attempt + 1}/${retries}): ${lastError.message}`);
|
|
143
143
|
this.pool = null;
|
|
144
144
|
poolAcquired = false; // 重置标记
|
|
145
145
|
await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
|
|
@@ -163,7 +163,7 @@ export class SqlServerAdapter {
|
|
|
163
163
|
await this.pool.close();
|
|
164
164
|
}
|
|
165
165
|
catch (err) {
|
|
166
|
-
console.error(`[WARN]
|
|
166
|
+
console.error(`[WARN] 关闭连接池时出错: ${err.message}`);
|
|
167
167
|
}
|
|
168
168
|
this.pool = null;
|
|
169
169
|
}
|
|
@@ -207,17 +207,22 @@ export class SqlServerAdapter {
|
|
|
207
207
|
const preparedQuery = query.replace(/\?/g, () => `@param${paramIndex++}`);
|
|
208
208
|
// 如果是 INSERT,添加标识值的输出参数
|
|
209
209
|
let lastID = 0;
|
|
210
|
+
let changes = 0;
|
|
210
211
|
if (query.trim().toUpperCase().startsWith('INSERT')) {
|
|
211
212
|
request.output('insertedId', sql.Int, 0);
|
|
212
213
|
const updatedQuery = `${preparedQuery}; SELECT @insertedId = SCOPE_IDENTITY();`;
|
|
213
214
|
const result = await request.query(updatedQuery);
|
|
214
215
|
lastID = result.output.insertedId || 0;
|
|
216
|
+
// 使用 rowsAffected 获取受影响行数
|
|
217
|
+
changes = result.rowsAffected?.[0] || (lastID > 0 ? 1 : 0);
|
|
215
218
|
}
|
|
216
219
|
else {
|
|
217
|
-
await request.query(preparedQuery);
|
|
220
|
+
const result = await request.query(preparedQuery);
|
|
221
|
+
// 使用 rowsAffected 获取受影响行数
|
|
222
|
+
changes = result.rowsAffected?.[0] || 0;
|
|
218
223
|
}
|
|
219
224
|
return {
|
|
220
|
-
changes:
|
|
225
|
+
changes: changes,
|
|
221
226
|
lastID: lastID
|
|
222
227
|
};
|
|
223
228
|
});
|
|
@@ -251,8 +256,8 @@ export class SqlServerAdapter {
|
|
|
251
256
|
return "SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME";
|
|
252
257
|
}
|
|
253
258
|
/**
|
|
254
|
-
*
|
|
255
|
-
* @param tableName
|
|
259
|
+
* 获取描述表或视图的数据库特定查询
|
|
260
|
+
* @param tableName 表名或视图名
|
|
256
261
|
*/
|
|
257
262
|
getDescribeTableQuery(tableName) {
|
|
258
263
|
return `
|
|
@@ -272,10 +277,11 @@ export class SqlServerAdapter {
|
|
|
272
277
|
LEFT JOIN
|
|
273
278
|
sys.extended_properties ep
|
|
274
279
|
ON ep.major_id = (
|
|
275
|
-
SELECT
|
|
276
|
-
FROM sys.
|
|
277
|
-
INNER JOIN sys.schemas s ON
|
|
278
|
-
WHERE
|
|
280
|
+
SELECT o.object_id
|
|
281
|
+
FROM sys.objects o
|
|
282
|
+
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
|
283
|
+
WHERE o.name = '${tableName}' AND s.name = c.TABLE_SCHEMA
|
|
284
|
+
AND o.type IN ('U', 'V')
|
|
279
285
|
)
|
|
280
286
|
AND ep.minor_id = c.ORDINAL_POSITION
|
|
281
287
|
AND ep.name = 'MS_Description'
|
|
@@ -286,13 +292,23 @@ export class SqlServerAdapter {
|
|
|
286
292
|
`;
|
|
287
293
|
}
|
|
288
294
|
/**
|
|
289
|
-
*
|
|
295
|
+
* 获取列出视图的数据库特定查询
|
|
290
296
|
*/
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
+
getListViewsQuery() {
|
|
298
|
+
return "SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.VIEWS ORDER BY TABLE_NAME";
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 获取视图定义的数据库特定查询
|
|
302
|
+
* @param viewName 视图名
|
|
303
|
+
* 注意: 使用 WITH ENCRYPTION 创建的视图无法获取定义
|
|
304
|
+
*/
|
|
305
|
+
getViewDefinitionQuery(viewName) {
|
|
306
|
+
return `SELECT VIEW_DEFINITION as definition FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '${viewName}'`;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* 检查数据库是否支持视图功能
|
|
310
|
+
*/
|
|
311
|
+
supportsViews() {
|
|
312
|
+
return true;
|
|
297
313
|
}
|
|
298
314
|
}
|