@harneon-ai/db 0.0.1 → 0.0.3
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 +338 -338
- package/config/datasource.example.yaml +68 -62
- package/dist/config/config-manager.d.ts +110 -0
- package/dist/config/config-manager.js +275 -0
- package/dist/config/parser.d.ts +35 -21
- package/dist/config/parser.js +84 -36
- package/dist/config/types.d.ts +349 -1
- package/dist/config/types.js +19 -1
- package/dist/config/writer.d.ts +35 -1
- package/dist/config/writer.js +83 -0
- package/dist/db/connection-manager.d.ts +52 -18
- package/dist/db/connection-manager.js +93 -35
- package/dist/db/driver-registry.d.ts +51 -0
- package/dist/db/driver-registry.js +89 -0
- package/dist/db/driver.d.ts +63 -0
- package/dist/db/driver.js +15 -0
- package/dist/db/drivers/mysql.d.ts +89 -0
- package/dist/db/drivers/mysql.js +268 -0
- package/dist/db/drivers/postgresql.d.ts +95 -0
- package/dist/db/drivers/postgresql.js +252 -0
- package/dist/db/metadata-queries.js +53 -53
- package/dist/db/types.d.ts +95 -0
- package/dist/db/types.js +14 -0
- package/dist/index.d.ts +9 -8
- package/dist/index.js +33 -28
- package/dist/tools/delete-datasource-config.d.ts +12 -0
- package/dist/tools/delete-datasource-config.js +75 -0
- package/dist/tools/describe-indexes.d.ts +1 -1
- package/dist/tools/describe-indexes.js +7 -5
- package/dist/tools/describe-table.d.ts +1 -0
- package/dist/tools/describe-table.js +9 -6
- package/dist/tools/list-datasources.d.ts +3 -2
- package/dist/tools/list-datasources.js +19 -8
- package/dist/tools/list-tables.d.ts +1 -1
- package/dist/tools/list-tables.js +7 -5
- package/dist/tools/query-metadata.d.ts +2 -7
- package/dist/tools/query-metadata.js +18 -51
- package/dist/tools/save-datasource-config.d.ts +5 -4
- package/dist/tools/save-datasource-config.js +25 -28
- package/dist/tools/test-connection.d.ts +4 -2
- package/dist/tools/test-connection.js +47 -40
- package/dist/tools/update-datasource-config.d.ts +14 -0
- package/dist/tools/update-datasource-config.js +243 -0
- package/package.json +58 -42
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
*/
|
|
15
15
|
/** 列出指定 schema 下的所有用户表和视图 */
|
|
16
16
|
export async function listTables(pool, schema = 'public') {
|
|
17
|
-
const result = await pool.query(`SELECT table_name, table_type
|
|
18
|
-
FROM information_schema.tables
|
|
19
|
-
WHERE table_schema = $1
|
|
20
|
-
AND table_type IN ('BASE TABLE', 'VIEW')
|
|
17
|
+
const result = await pool.query(`SELECT table_name, table_type
|
|
18
|
+
FROM information_schema.tables
|
|
19
|
+
WHERE table_schema = $1
|
|
20
|
+
AND table_type IN ('BASE TABLE', 'VIEW')
|
|
21
21
|
ORDER BY table_name`, [schema]);
|
|
22
22
|
return result.rows;
|
|
23
23
|
}
|
|
@@ -29,20 +29,20 @@ export async function listTables(pool, schema = 'public') {
|
|
|
29
29
|
* - pg_description 通过 objoid + objsubid(列序号) 关联到列注释
|
|
30
30
|
*/
|
|
31
31
|
export async function describeTable(pool, tableName, schema = 'public') {
|
|
32
|
-
const result = await pool.query(`SELECT
|
|
33
|
-
c.column_name,
|
|
34
|
-
c.data_type,
|
|
35
|
-
c.is_nullable,
|
|
36
|
-
c.column_default,
|
|
37
|
-
c.character_maximum_length,
|
|
38
|
-
c.ordinal_position,
|
|
39
|
-
pgd.description AS comment
|
|
40
|
-
FROM information_schema.columns c
|
|
41
|
-
LEFT JOIN pg_catalog.pg_statio_all_tables st
|
|
42
|
-
ON st.schemaname = c.table_schema AND st.relname = c.table_name
|
|
43
|
-
LEFT JOIN pg_catalog.pg_description pgd
|
|
44
|
-
ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
|
|
45
|
-
WHERE c.table_schema = $1 AND c.table_name = $2
|
|
32
|
+
const result = await pool.query(`SELECT
|
|
33
|
+
c.column_name,
|
|
34
|
+
c.data_type,
|
|
35
|
+
c.is_nullable,
|
|
36
|
+
c.column_default,
|
|
37
|
+
c.character_maximum_length,
|
|
38
|
+
c.ordinal_position,
|
|
39
|
+
pgd.description AS comment
|
|
40
|
+
FROM information_schema.columns c
|
|
41
|
+
LEFT JOIN pg_catalog.pg_statio_all_tables st
|
|
42
|
+
ON st.schemaname = c.table_schema AND st.relname = c.table_name
|
|
43
|
+
LEFT JOIN pg_catalog.pg_description pgd
|
|
44
|
+
ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
|
|
45
|
+
WHERE c.table_schema = $1 AND c.table_name = $2
|
|
46
46
|
ORDER BY c.ordinal_position`, [schema, tableName]);
|
|
47
47
|
return result.rows;
|
|
48
48
|
}
|
|
@@ -56,38 +56,38 @@ export async function describeTable(pool, tableName, schema = 'public') {
|
|
|
56
56
|
* - pg_attribute: 索引包含的列名
|
|
57
57
|
*/
|
|
58
58
|
export async function describeIndexes(pool, tableName, schema = 'public') {
|
|
59
|
-
const result = await pool.query(`SELECT
|
|
60
|
-
i.relname AS index_name,
|
|
61
|
-
pg_get_indexdef(i.oid) AS index_definition,
|
|
62
|
-
ix.indisunique AS is_unique,
|
|
63
|
-
am.amname AS index_method,
|
|
64
|
-
array_to_string(ARRAY(
|
|
65
|
-
SELECT a.attname
|
|
66
|
-
FROM pg_catalog.pg_attribute a
|
|
67
|
-
WHERE a.attrelid = i.oid
|
|
68
|
-
ORDER BY a.attnum
|
|
69
|
-
), ', ') AS index_columns
|
|
70
|
-
FROM pg_catalog.pg_index ix
|
|
71
|
-
JOIN pg_catalog.pg_class t ON t.oid = ix.indrelid
|
|
72
|
-
JOIN pg_catalog.pg_class i ON i.oid = ix.indexrelid
|
|
73
|
-
JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace
|
|
74
|
-
JOIN pg_catalog.pg_am am ON am.oid = i.relam
|
|
75
|
-
WHERE n.nspname = $1 AND t.relname = $2
|
|
59
|
+
const result = await pool.query(`SELECT
|
|
60
|
+
i.relname AS index_name,
|
|
61
|
+
pg_get_indexdef(i.oid) AS index_definition,
|
|
62
|
+
ix.indisunique AS is_unique,
|
|
63
|
+
am.amname AS index_method,
|
|
64
|
+
array_to_string(ARRAY(
|
|
65
|
+
SELECT a.attname
|
|
66
|
+
FROM pg_catalog.pg_attribute a
|
|
67
|
+
WHERE a.attrelid = i.oid
|
|
68
|
+
ORDER BY a.attnum
|
|
69
|
+
), ', ') AS index_columns
|
|
70
|
+
FROM pg_catalog.pg_index ix
|
|
71
|
+
JOIN pg_catalog.pg_class t ON t.oid = ix.indrelid
|
|
72
|
+
JOIN pg_catalog.pg_class i ON i.oid = ix.indexrelid
|
|
73
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace
|
|
74
|
+
JOIN pg_catalog.pg_am am ON am.oid = i.relam
|
|
75
|
+
WHERE n.nspname = $1 AND t.relname = $2
|
|
76
76
|
ORDER BY i.relname`, [schema, tableName]);
|
|
77
77
|
return result.rows;
|
|
78
78
|
}
|
|
79
79
|
/** 查询表的约束信息(主键、外键、唯一约束) */
|
|
80
80
|
export async function getTableConstraints(pool, tableName, schema = 'public') {
|
|
81
|
-
const result = await pool.query(`SELECT
|
|
82
|
-
tc.constraint_name,
|
|
83
|
-
tc.constraint_type,
|
|
84
|
-
string_agg(kcu.column_name, ', ' ORDER BY kcu.ordinal_position) AS columns
|
|
85
|
-
FROM information_schema.table_constraints tc
|
|
86
|
-
JOIN information_schema.key_column_usage kcu
|
|
87
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
88
|
-
AND tc.table_schema = kcu.table_schema
|
|
89
|
-
WHERE tc.table_schema = $1 AND tc.table_name = $2
|
|
90
|
-
GROUP BY tc.constraint_name, tc.constraint_type
|
|
81
|
+
const result = await pool.query(`SELECT
|
|
82
|
+
tc.constraint_name,
|
|
83
|
+
tc.constraint_type,
|
|
84
|
+
string_agg(kcu.column_name, ', ' ORDER BY kcu.ordinal_position) AS columns
|
|
85
|
+
FROM information_schema.table_constraints tc
|
|
86
|
+
JOIN information_schema.key_column_usage kcu
|
|
87
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
88
|
+
AND tc.table_schema = kcu.table_schema
|
|
89
|
+
WHERE tc.table_schema = $1 AND tc.table_name = $2
|
|
90
|
+
GROUP BY tc.constraint_name, tc.constraint_type
|
|
91
91
|
ORDER BY tc.constraint_type, tc.constraint_name`, [schema, tableName]);
|
|
92
92
|
return result.rows;
|
|
93
93
|
}
|
|
@@ -100,14 +100,14 @@ export async function getTableConstraints(pool, tableName, schema = 'public') {
|
|
|
100
100
|
* @returns 表统计信息,表不存在时返回 null
|
|
101
101
|
*/
|
|
102
102
|
export async function getTableStats(pool, tableName, schema = 'public') {
|
|
103
|
-
const result = await pool.query(`SELECT
|
|
104
|
-
s.n_live_tup AS estimated_rows,
|
|
105
|
-
pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,
|
|
106
|
-
pg_size_pretty(pg_table_size(c.oid)) AS table_size,
|
|
107
|
-
pg_size_pretty(pg_indexes_size(c.oid)) AS index_size
|
|
108
|
-
FROM pg_catalog.pg_stat_user_tables s
|
|
109
|
-
JOIN pg_catalog.pg_class c ON c.relname = s.relname
|
|
110
|
-
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace AND n.nspname = s.schemaname
|
|
103
|
+
const result = await pool.query(`SELECT
|
|
104
|
+
s.n_live_tup AS estimated_rows,
|
|
105
|
+
pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,
|
|
106
|
+
pg_size_pretty(pg_table_size(c.oid)) AS table_size,
|
|
107
|
+
pg_size_pretty(pg_indexes_size(c.oid)) AS index_size
|
|
108
|
+
FROM pg_catalog.pg_stat_user_tables s
|
|
109
|
+
JOIN pg_catalog.pg_class c ON c.relname = s.relname
|
|
110
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace AND n.nspname = s.schemaname
|
|
111
111
|
WHERE s.schemaname = $1 AND s.relname = $2`, [schema, tableName]);
|
|
112
112
|
return result.rows[0] ?? null;
|
|
113
113
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据库抽象层共享类型定义
|
|
3
|
+
*
|
|
4
|
+
* 本文件定义了驱动抽象体系中的核心类型,使上层代码(工具层、连接管理器)
|
|
5
|
+
* 与具体数据库实现解耦。
|
|
6
|
+
*
|
|
7
|
+
* 类型体系:
|
|
8
|
+
* - DatabaseConnection: 统一的数据库连接抽象,屏蔽 pg.Pool / mysql2.Pool 等差异
|
|
9
|
+
* - QueryResult: 统一的查询结果格式
|
|
10
|
+
* - DriverConnectionConfig: 驱动创建连接时需要的配置参数
|
|
11
|
+
* - ConnectionParams: 从 JDBC URL 解析出的连接参数
|
|
12
|
+
* - 元数据类型(TableInfo、ColumnInfo 等): 各驱动的元数据查询返回统一的结构
|
|
13
|
+
*/
|
|
14
|
+
/** information_schema.tables 查询结果 */
|
|
15
|
+
export interface TableInfo {
|
|
16
|
+
table_name: string;
|
|
17
|
+
table_type: string;
|
|
18
|
+
}
|
|
19
|
+
/** 列信息查询结果(含列注释) */
|
|
20
|
+
export interface ColumnInfo {
|
|
21
|
+
column_name: string;
|
|
22
|
+
data_type: string;
|
|
23
|
+
is_nullable: string;
|
|
24
|
+
column_default: string | null;
|
|
25
|
+
character_maximum_length: number | null;
|
|
26
|
+
comment: string | null;
|
|
27
|
+
ordinal_position: number;
|
|
28
|
+
}
|
|
29
|
+
/** 索引信息查询结果 */
|
|
30
|
+
export interface IndexInfo {
|
|
31
|
+
index_name: string;
|
|
32
|
+
index_columns: string;
|
|
33
|
+
is_unique: boolean;
|
|
34
|
+
index_method: string;
|
|
35
|
+
index_definition: string;
|
|
36
|
+
}
|
|
37
|
+
/** 约束信息查询结果 */
|
|
38
|
+
export interface ConstraintInfo {
|
|
39
|
+
constraint_name: string;
|
|
40
|
+
constraint_type: string;
|
|
41
|
+
columns: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 表统计信息查询结果
|
|
45
|
+
*
|
|
46
|
+
* 注意: estimated_rows 在不同数据库中精度不同:
|
|
47
|
+
* - PostgreSQL: 基于 pg_stat_user_tables.n_live_tup(ANALYZE 后相对准确)
|
|
48
|
+
* - MySQL: 基于 information_schema.TABLES.TABLE_ROWS(InnoDB 为粗略估算)
|
|
49
|
+
*/
|
|
50
|
+
export interface TableStats {
|
|
51
|
+
estimated_rows: number;
|
|
52
|
+
total_size: string;
|
|
53
|
+
table_size: string;
|
|
54
|
+
index_size: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 统一的数据库连接抽象
|
|
58
|
+
*
|
|
59
|
+
* 工具层只需要 query 和 end 两个能力,保持最小抽象。
|
|
60
|
+
* 具体的连接池管理(最大连接数、超时等)由各驱动内部处理。
|
|
61
|
+
*/
|
|
62
|
+
export interface DatabaseConnection {
|
|
63
|
+
query<T>(sql: string, params?: unknown[]): Promise<QueryResult<T>>;
|
|
64
|
+
end(): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
/** 统一的查询结果格式 */
|
|
67
|
+
export interface QueryResult<T = Record<string, unknown>> {
|
|
68
|
+
rows: T[];
|
|
69
|
+
rowCount: number | null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 驱动创建连接时需要的配置参数
|
|
73
|
+
*
|
|
74
|
+
* 对应 YAML 配置中单个数据源的连接信息。
|
|
75
|
+
* host/port/database 由驱动从 JDBC URL 解析得出。
|
|
76
|
+
*/
|
|
77
|
+
export interface DriverConnectionConfig {
|
|
78
|
+
host: string;
|
|
79
|
+
port: number;
|
|
80
|
+
database: string;
|
|
81
|
+
username: string;
|
|
82
|
+
password: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 从 JDBC URL 解析出的连接参数
|
|
86
|
+
*
|
|
87
|
+
* 各驱动实现自己的 parseJdbcUrl(),因为不同数据库的 JDBC URL 格式不同:
|
|
88
|
+
* - PostgreSQL: jdbc:postgresql://host:port/database
|
|
89
|
+
* - MySQL: jdbc:mysql://host:port/database
|
|
90
|
+
*/
|
|
91
|
+
export interface ConnectionParams {
|
|
92
|
+
host: string;
|
|
93
|
+
port: number;
|
|
94
|
+
database: string;
|
|
95
|
+
}
|
package/dist/db/types.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据库抽象层共享类型定义
|
|
3
|
+
*
|
|
4
|
+
* 本文件定义了驱动抽象体系中的核心类型,使上层代码(工具层、连接管理器)
|
|
5
|
+
* 与具体数据库实现解耦。
|
|
6
|
+
*
|
|
7
|
+
* 类型体系:
|
|
8
|
+
* - DatabaseConnection: 统一的数据库连接抽象,屏蔽 pg.Pool / mysql2.Pool 等差异
|
|
9
|
+
* - QueryResult: 统一的查询结果格式
|
|
10
|
+
* - DriverConnectionConfig: 驱动创建连接时需要的配置参数
|
|
11
|
+
* - ConnectionParams: 从 JDBC URL 解析出的连接参数
|
|
12
|
+
* - 元数据类型(TableInfo、ColumnInfo 等): 各驱动的元数据查询返回统一的结构
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -4,20 +4,21 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 架构概述:
|
|
6
6
|
* - 本项目是一个 MCP (Model Context Protocol) Server,通过 stdio 传输与 AI 客户端(如 Claude Desktop/Claude Code)通信
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
7
|
+
* - 提供只读的数据库元数据查询能力(支持 PostgreSQL、MySQL 等),支持 ShardingSphere 风格的分片拓扑解析
|
|
8
|
+
* - 配置采用多文件 YAML 格式,由 ConfigManager 统一管理,支持从旧版单文件自动迁移
|
|
9
9
|
*
|
|
10
10
|
* 安全策略(多层防御):
|
|
11
|
-
* 1. 连接级别:
|
|
12
|
-
* 2. SQL 级别: query_metadata tool
|
|
11
|
+
* 1. 连接级别: 各数据库驱动在连接创建时强制只读模式,数据库层面禁止写操作
|
|
12
|
+
* 2. SQL 级别: query_metadata tool 通过驱动的 validateMetadataQuery() 验证 SQL 安全性
|
|
13
13
|
* 3. Tool 级别: 所有预定义 tool 只执行固定的元数据查询 SQL
|
|
14
14
|
*
|
|
15
15
|
* 模块结构:
|
|
16
|
-
* - src/config/ — 配置类型定义(types.ts)
|
|
17
|
-
* - src/db/ —
|
|
18
|
-
* - src/tools/ —
|
|
16
|
+
* - src/config/ — 配置类型定义(types.ts)、YAML 解析器(parser.ts)、配置管理器(config-manager.ts)
|
|
17
|
+
* - src/db/ — 驱动抽象(driver.ts/types.ts)、驱动注册(driver-registry.ts)、驱动实现(drivers/)、连接管理(connection-manager.ts)
|
|
18
|
+
* - src/tools/ — MCP Tool 的实现,每个 tool 一个文件
|
|
19
19
|
*
|
|
20
20
|
* 启动流程:
|
|
21
|
-
* 1.
|
|
21
|
+
* 1. 初始化 ConfigManager → 2. 迁移旧版配置 → 3. 加载所有配置文件
|
|
22
|
+
* → 4. 创建 ConnectionManager → 5. 注册 MCP Tools → 6. 启动 stdio 传输
|
|
22
23
|
*/
|
|
23
24
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -4,28 +4,28 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 架构概述:
|
|
6
6
|
* - 本项目是一个 MCP (Model Context Protocol) Server,通过 stdio 传输与 AI 客户端(如 Claude Desktop/Claude Code)通信
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
7
|
+
* - 提供只读的数据库元数据查询能力(支持 PostgreSQL、MySQL 等),支持 ShardingSphere 风格的分片拓扑解析
|
|
8
|
+
* - 配置采用多文件 YAML 格式,由 ConfigManager 统一管理,支持从旧版单文件自动迁移
|
|
9
9
|
*
|
|
10
10
|
* 安全策略(多层防御):
|
|
11
|
-
* 1. 连接级别:
|
|
12
|
-
* 2. SQL 级别: query_metadata tool
|
|
11
|
+
* 1. 连接级别: 各数据库驱动在连接创建时强制只读模式,数据库层面禁止写操作
|
|
12
|
+
* 2. SQL 级别: query_metadata tool 通过驱动的 validateMetadataQuery() 验证 SQL 安全性
|
|
13
13
|
* 3. Tool 级别: 所有预定义 tool 只执行固定的元数据查询 SQL
|
|
14
14
|
*
|
|
15
15
|
* 模块结构:
|
|
16
|
-
* - src/config/ — 配置类型定义(types.ts)
|
|
17
|
-
* - src/db/ —
|
|
18
|
-
* - src/tools/ —
|
|
16
|
+
* - src/config/ — 配置类型定义(types.ts)、YAML 解析器(parser.ts)、配置管理器(config-manager.ts)
|
|
17
|
+
* - src/db/ — 驱动抽象(driver.ts/types.ts)、驱动注册(driver-registry.ts)、驱动实现(drivers/)、连接管理(connection-manager.ts)
|
|
18
|
+
* - src/tools/ — MCP Tool 的实现,每个 tool 一个文件
|
|
19
19
|
*
|
|
20
20
|
* 启动流程:
|
|
21
|
-
* 1.
|
|
21
|
+
* 1. 初始化 ConfigManager → 2. 迁移旧版配置 → 3. 加载所有配置文件
|
|
22
|
+
* → 4. 创建 ConnectionManager → 5. 注册 MCP Tools → 6. 启动 stdio 传输
|
|
22
23
|
*/
|
|
23
|
-
import fs from 'node:fs';
|
|
24
24
|
import os from 'node:os';
|
|
25
25
|
import path from 'node:path';
|
|
26
26
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
27
27
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
28
|
-
import {
|
|
28
|
+
import { ConfigManager } from './config/config-manager.js';
|
|
29
29
|
import { ConnectionManager } from './db/connection-manager.js';
|
|
30
30
|
import { registerListDatasources } from './tools/list-datasources.js';
|
|
31
31
|
import { registerListTables } from './tools/list-tables.js';
|
|
@@ -35,20 +35,21 @@ import { registerShardingTopology } from './tools/sharding-topology.js';
|
|
|
35
35
|
import { registerQueryMetadata } from './tools/query-metadata.js';
|
|
36
36
|
import { registerTestConnection } from './tools/test-connection.js';
|
|
37
37
|
import { registerSaveDatasourceConfig } from './tools/save-datasource-config.js';
|
|
38
|
+
import { registerDeleteDatasourceConfig } from './tools/delete-datasource-config.js';
|
|
39
|
+
import { registerUpdateDatasourceConfig } from './tools/update-datasource-config.js';
|
|
38
40
|
async function main() {
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
41
|
+
// 配置目录和旧版配置文件路径
|
|
42
|
+
const defaultConfigDir = path.join(os.homedir(), '.harneon-ai', 'db', 'configs');
|
|
43
|
+
const legacyConfigPath = path.join(os.homedir(), '.harneon-ai', 'db', 'datasource.yaml');
|
|
44
|
+
// 支持命令行参数或环境变量指定配置目录
|
|
45
|
+
const configDir = process.argv[2] || process.env.DB_CONFIG_DIR || defaultConfigDir;
|
|
46
|
+
const configManager = new ConfigManager(configDir);
|
|
47
|
+
// 自动迁移旧版单文件配置到新的多文件格式
|
|
48
|
+
const legacyPath = process.env.DB_CONFIG_PATH || legacyConfigPath;
|
|
49
|
+
configManager.migrateFromLegacy(legacyPath);
|
|
50
|
+
// 加载配置目录下的所有数据库配置文件
|
|
51
|
+
configManager.loadAll();
|
|
52
|
+
const config = configManager.combinedConfig;
|
|
52
53
|
// ConnectionManager 采用懒初始化策略,此时不会创建任何数据库连接
|
|
53
54
|
const connectionManager = new ConnectionManager(config);
|
|
54
55
|
const server = new McpServer({
|
|
@@ -63,18 +64,22 @@ async function main() {
|
|
|
63
64
|
// - describe_indexes: 查询表的索引信息
|
|
64
65
|
// - sharding_topology: 查看分片拓扑(纯配置解析,不连接数据库)
|
|
65
66
|
// - query_metadata: 执行自定义元数据查询(带安全过滤)
|
|
66
|
-
//
|
|
67
|
-
// - test_connection:
|
|
67
|
+
// 数据源配置管理工具:
|
|
68
|
+
// - test_connection: 测试数据库连接(独立于配置和连接池,自动检测数据库类型)
|
|
68
69
|
// - save_datasource_config: 保存数据源配置到 YAML 文件并更新内存状态
|
|
69
|
-
|
|
70
|
+
// - delete_datasource_config: 两步确认删除数据源配置(含磁盘文件和内存状态)
|
|
71
|
+
// - update_datasource_config: 按类别更新数据源配置(基本信息/连接/凭据/数据源增删)
|
|
72
|
+
registerListDatasources(server, config, connectionManager, configManager);
|
|
70
73
|
registerListTables(server, config, connectionManager);
|
|
71
74
|
registerDescribeTable(server, config, connectionManager);
|
|
72
75
|
registerDescribeIndexes(server, config, connectionManager);
|
|
73
76
|
registerShardingTopology(server, config, connectionManager);
|
|
74
77
|
registerQueryMetadata(server, config, connectionManager);
|
|
75
78
|
registerTestConnection(server);
|
|
76
|
-
registerSaveDatasourceConfig(server, config, connectionManager,
|
|
77
|
-
|
|
79
|
+
registerSaveDatasourceConfig(server, config, connectionManager, configManager);
|
|
80
|
+
registerDeleteDatasourceConfig(server, configManager, connectionManager);
|
|
81
|
+
registerUpdateDatasourceConfig(server, configManager, connectionManager);
|
|
82
|
+
// 优雅退出: 收到终止信号时关闭所有数据库连接
|
|
78
83
|
const shutdown = async () => {
|
|
79
84
|
await connectionManager.shutdown();
|
|
80
85
|
process.exit(0);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: delete_datasource_config
|
|
3
|
+
*
|
|
4
|
+
* 删除数据库配置文件,采用两步确认码机制防止误删:
|
|
5
|
+
* - 第一次调用(不传 confirmationCode): 返回配置概要和 6 位确认码
|
|
6
|
+
* - 第二次调用(传入 confirmationCode): 验证确认码并执行删除
|
|
7
|
+
* 确认码 1 小时内有效,使用即作废
|
|
8
|
+
*/
|
|
9
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
|
+
import { ConfigManager } from '../config/config-manager.js';
|
|
11
|
+
import { ConnectionManager } from '../db/connection-manager.js';
|
|
12
|
+
export declare function registerDeleteDatasourceConfig(server: McpServer, configManager: ConfigManager, connectionManager: ConnectionManager): void;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: delete_datasource_config
|
|
3
|
+
*
|
|
4
|
+
* 删除数据库配置文件,采用两步确认码机制防止误删:
|
|
5
|
+
* - 第一次调用(不传 confirmationCode): 返回配置概要和 6 位确认码
|
|
6
|
+
* - 第二次调用(传入 confirmationCode): 验证确认码并执行删除
|
|
7
|
+
* 确认码 1 小时内有效,使用即作废
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
export function registerDeleteDatasourceConfig(server, configManager, connectionManager) {
|
|
11
|
+
server.tool('delete_datasource_config', '删除数据库配置。首次调用返回确认码,再次调用传入确认码执行删除。', {
|
|
12
|
+
name: z.string().min(1).describe('要删除的数据库配置名称'),
|
|
13
|
+
confirmationCode: z.string().optional().describe('确认码(第二次调用时传入)'),
|
|
14
|
+
}, async (params) => {
|
|
15
|
+
try {
|
|
16
|
+
if (!params.confirmationCode) {
|
|
17
|
+
// 第一步: 展示配置概要并生成确认码
|
|
18
|
+
const dbConfig = configManager.getConfig(params.name);
|
|
19
|
+
if (!dbConfig) {
|
|
20
|
+
return {
|
|
21
|
+
content: [{
|
|
22
|
+
type: 'text',
|
|
23
|
+
text: JSON.stringify({
|
|
24
|
+
success: false,
|
|
25
|
+
error: `配置 "${params.name}" 不存在`,
|
|
26
|
+
availableConfigs: configManager.listConfigs().map(c => c.name),
|
|
27
|
+
}),
|
|
28
|
+
}],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const code = configManager.requestDelete(params.name);
|
|
32
|
+
return {
|
|
33
|
+
content: [{
|
|
34
|
+
type: 'text',
|
|
35
|
+
text: JSON.stringify({
|
|
36
|
+
success: true,
|
|
37
|
+
step: 'confirmation_required',
|
|
38
|
+
confirmationCode: code,
|
|
39
|
+
configName: params.name,
|
|
40
|
+
description: dbConfig.description ?? '',
|
|
41
|
+
datasources: Object.keys(dbConfig.dataSources),
|
|
42
|
+
message: `即将删除配置 "${params.name}",包含 ${Object.keys(dbConfig.dataSources).length} 个数据源。请使用确认码 ${code} 再次调用此工具确认删除。确认码 1 小时内有效。`,
|
|
43
|
+
}),
|
|
44
|
+
}],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// 第二步: 验证确认码并执行删除
|
|
49
|
+
const deleted = configManager.confirmDelete(params.name, params.confirmationCode);
|
|
50
|
+
await connectionManager.reloadConfig();
|
|
51
|
+
return {
|
|
52
|
+
content: [{
|
|
53
|
+
type: 'text',
|
|
54
|
+
text: JSON.stringify({
|
|
55
|
+
success: true,
|
|
56
|
+
step: 'deleted',
|
|
57
|
+
configName: deleted.name,
|
|
58
|
+
deletedDatasources: Object.keys(deleted.dataSources),
|
|
59
|
+
remainingConfigs: configManager.listConfigs().map(c => c.name),
|
|
60
|
+
}),
|
|
61
|
+
}],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
67
|
+
return {
|
|
68
|
+
content: [{
|
|
69
|
+
type: 'text',
|
|
70
|
+
text: JSON.stringify({ success: false, error: errorMessage }),
|
|
71
|
+
}],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
* MCP Tool: describe_indexes
|
|
3
3
|
*
|
|
4
4
|
* 查询指定表的所有索引信息。
|
|
5
|
-
*
|
|
5
|
+
* 通过驱动抽象层查询,返回索引名称、包含列、唯一性、索引方法
|
|
6
6
|
* 以及完整的 CREATE INDEX 语句定义。
|
|
7
7
|
*/
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
-
import { describeIndexes } from '../db/metadata-queries.js';
|
|
10
9
|
export function registerDescribeIndexes(server, _config, connectionManager) {
|
|
11
10
|
server.tool('describe_indexes', '查询指定表的所有索引信息', {
|
|
12
11
|
datasource: z.string().describe('数据源名称'),
|
|
13
12
|
table: z.string().describe('表名'),
|
|
14
|
-
schema: z.string().optional().default('public').describe('Schema
|
|
13
|
+
schema: z.string().optional().default('public').describe('Schema 名称(PostgreSQL 默认 public,MySQL 中对应 database 名,留空使用连接默认值)'),
|
|
15
14
|
}, async ({ datasource, table, schema }) => {
|
|
16
15
|
try {
|
|
17
|
-
const
|
|
18
|
-
|
|
16
|
+
const [conn, driver] = await Promise.all([
|
|
17
|
+
connectionManager.getConnection(datasource),
|
|
18
|
+
connectionManager.getDriver(datasource),
|
|
19
|
+
]);
|
|
20
|
+
const indexes = await driver.describeIndexes(conn, table, schema);
|
|
19
21
|
return {
|
|
20
22
|
content: [{
|
|
21
23
|
type: 'text',
|
|
@@ -7,22 +7,25 @@
|
|
|
7
7
|
* - stats: 统计信息(行数估算、表大小、索引大小)
|
|
8
8
|
*
|
|
9
9
|
* 三个查询并行执行以减少延迟。
|
|
10
|
+
* 通过驱动抽象层查询,支持不同数据库类型。
|
|
10
11
|
*/
|
|
11
12
|
import { z } from 'zod';
|
|
12
|
-
import { describeTable, getTableConstraints, getTableStats } from '../db/metadata-queries.js';
|
|
13
13
|
export function registerDescribeTable(server, _config, connectionManager) {
|
|
14
14
|
server.tool('describe_table', '查询指定表的详细结构(列信息、约束、统计)', {
|
|
15
15
|
datasource: z.string().describe('数据源名称'),
|
|
16
16
|
table: z.string().describe('表名'),
|
|
17
|
-
schema: z.string().optional().default('public').describe('Schema
|
|
17
|
+
schema: z.string().optional().default('public').describe('Schema 名称(PostgreSQL 默认 public,MySQL 中对应 database 名,留空使用连接默认值)'),
|
|
18
18
|
}, async ({ datasource, table, schema }) => {
|
|
19
19
|
try {
|
|
20
|
-
const
|
|
20
|
+
const [conn, driver] = await Promise.all([
|
|
21
|
+
connectionManager.getConnection(datasource),
|
|
22
|
+
connectionManager.getDriver(datasource),
|
|
23
|
+
]);
|
|
21
24
|
// 并行查询列信息、约束和统计,减少总延迟
|
|
22
25
|
const [columns, constraints, stats] = await Promise.all([
|
|
23
|
-
describeTable(
|
|
24
|
-
getTableConstraints(
|
|
25
|
-
getTableStats(
|
|
26
|
+
driver.describeTable(conn, table, schema),
|
|
27
|
+
driver.getTableConstraints(conn, table, schema),
|
|
28
|
+
driver.getTableStats(conn, table, schema),
|
|
26
29
|
]);
|
|
27
30
|
const result = { columns, constraints, stats };
|
|
28
31
|
return {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Tool: list_datasources
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 列出所有已配置的数据源连接信息,包括数据源所属的配置文件信息。
|
|
5
5
|
* 这是一个纯配置读取操作,不连接任何数据库。
|
|
6
6
|
* 密码字段始终显示为 '***',防止敏感信息泄露。
|
|
7
7
|
*/
|
|
8
8
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
9
|
import { type ParsedConfig } from '../config/types.js';
|
|
10
10
|
import { type ConnectionManager } from '../db/connection-manager.js';
|
|
11
|
-
|
|
11
|
+
import { type ConfigManager } from '../config/config-manager.js';
|
|
12
|
+
export declare function registerListDatasources(server: McpServer, config: ParsedConfig, _connectionManager: ConnectionManager, configManager?: ConfigManager): void;
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Tool: list_datasources
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 列出所有已配置的数据源连接信息,包括数据源所属的配置文件信息。
|
|
5
5
|
* 这是一个纯配置读取操作,不连接任何数据库。
|
|
6
6
|
* 密码字段始终显示为 '***',防止敏感信息泄露。
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
9
|
-
export function registerListDatasources(server, config, _connectionManager) {
|
|
10
|
-
server.tool('list_datasources', '
|
|
8
|
+
import { detectDriverType, getDriver } from '../db/driver-registry.js';
|
|
9
|
+
export function registerListDatasources(server, config, _connectionManager, configManager) {
|
|
10
|
+
server.tool('list_datasources', '列出所有配置的数据源信息,包括所属配置及配置概览(密码已隐藏)', {}, // 无输入参数
|
|
11
11
|
async () => {
|
|
12
|
-
const datasources = Object.entries(config.dataSources).map(([name, ds]) => {
|
|
12
|
+
const datasources = await Promise.all(Object.entries(config.dataSources).map(async ([name, ds]) => {
|
|
13
13
|
const jdbcUrl = ds.jdbcUrl || ds.url || '';
|
|
14
14
|
let host = '', port = 0, database = '';
|
|
15
|
+
let driverType = '';
|
|
15
16
|
try {
|
|
16
|
-
|
|
17
|
+
driverType = ds['driverType'] || detectDriverType(jdbcUrl);
|
|
18
|
+
const driver = await getDriver(driverType);
|
|
19
|
+
const parsed = driver.parseJdbcUrl(jdbcUrl);
|
|
17
20
|
host = parsed.host;
|
|
18
21
|
port = parsed.port;
|
|
19
22
|
database = parsed.database;
|
|
@@ -23,17 +26,25 @@ export function registerListDatasources(server, config, _connectionManager) {
|
|
|
23
26
|
}
|
|
24
27
|
return {
|
|
25
28
|
name,
|
|
29
|
+
configName: configManager?.getConfigNameByDatasource(name) ?? 'unknown',
|
|
30
|
+
driverType,
|
|
26
31
|
host,
|
|
27
32
|
port,
|
|
28
33
|
database,
|
|
29
34
|
username: ds.username,
|
|
30
35
|
password: '***', // 安全: 始终隐藏密码
|
|
31
36
|
};
|
|
32
|
-
});
|
|
37
|
+
}));
|
|
38
|
+
// 配置概览:列出所有数据库配置及其包含的数据源数量
|
|
39
|
+
const configs = configManager?.listConfigs().map(c => ({
|
|
40
|
+
name: c.name,
|
|
41
|
+
description: c.description ?? '',
|
|
42
|
+
datasourceCount: Object.keys(c.dataSources).length,
|
|
43
|
+
})) ?? [];
|
|
33
44
|
return {
|
|
34
45
|
content: [{
|
|
35
46
|
type: 'text',
|
|
36
|
-
text: JSON.stringify(datasources, null, 2),
|
|
47
|
+
text: JSON.stringify({ configs, datasources }, null, 2),
|
|
37
48
|
}],
|
|
38
49
|
};
|
|
39
50
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* MCP Tool: list_tables
|
|
3
3
|
*
|
|
4
4
|
* 列出指定数据源中的所有用户表和视图。
|
|
5
|
-
*
|
|
5
|
+
* 通过驱动抽象层查询,支持不同数据库类型(PostgreSQL、MySQL 等)。
|
|
6
6
|
*/
|
|
7
7
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
8
|
import { type ParsedConfig } from '../config/types.js';
|