@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.
Files changed (44) hide show
  1. package/README.md +338 -338
  2. package/config/datasource.example.yaml +68 -62
  3. package/dist/config/config-manager.d.ts +110 -0
  4. package/dist/config/config-manager.js +275 -0
  5. package/dist/config/parser.d.ts +35 -21
  6. package/dist/config/parser.js +84 -36
  7. package/dist/config/types.d.ts +349 -1
  8. package/dist/config/types.js +19 -1
  9. package/dist/config/writer.d.ts +35 -1
  10. package/dist/config/writer.js +83 -0
  11. package/dist/db/connection-manager.d.ts +52 -18
  12. package/dist/db/connection-manager.js +93 -35
  13. package/dist/db/driver-registry.d.ts +51 -0
  14. package/dist/db/driver-registry.js +89 -0
  15. package/dist/db/driver.d.ts +63 -0
  16. package/dist/db/driver.js +15 -0
  17. package/dist/db/drivers/mysql.d.ts +89 -0
  18. package/dist/db/drivers/mysql.js +268 -0
  19. package/dist/db/drivers/postgresql.d.ts +95 -0
  20. package/dist/db/drivers/postgresql.js +252 -0
  21. package/dist/db/metadata-queries.js +53 -53
  22. package/dist/db/types.d.ts +95 -0
  23. package/dist/db/types.js +14 -0
  24. package/dist/index.d.ts +9 -8
  25. package/dist/index.js +33 -28
  26. package/dist/tools/delete-datasource-config.d.ts +12 -0
  27. package/dist/tools/delete-datasource-config.js +75 -0
  28. package/dist/tools/describe-indexes.d.ts +1 -1
  29. package/dist/tools/describe-indexes.js +7 -5
  30. package/dist/tools/describe-table.d.ts +1 -0
  31. package/dist/tools/describe-table.js +9 -6
  32. package/dist/tools/list-datasources.d.ts +3 -2
  33. package/dist/tools/list-datasources.js +19 -8
  34. package/dist/tools/list-tables.d.ts +1 -1
  35. package/dist/tools/list-tables.js +7 -5
  36. package/dist/tools/query-metadata.d.ts +2 -7
  37. package/dist/tools/query-metadata.js +18 -51
  38. package/dist/tools/save-datasource-config.d.ts +5 -4
  39. package/dist/tools/save-datasource-config.js +25 -28
  40. package/dist/tools/test-connection.d.ts +4 -2
  41. package/dist/tools/test-connection.js +47 -40
  42. package/dist/tools/update-datasource-config.d.ts +14 -0
  43. package/dist/tools/update-datasource-config.js +243 -0
  44. 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
+ }
@@ -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
- * - 提供只读的 PostgreSQL 数据库元数据查询能力,支持 ShardingSphere 风格的分片拓扑解析
8
- * - 配置文件采用 YAML 格式,兼容 ShardingSphere 配置,管理多个数据源和分片规则
7
+ * - 提供只读的数据库元数据查询能力(支持 PostgreSQL、MySQL 等),支持 ShardingSphere 风格的分片拓扑解析
8
+ * - 配置采用多文件 YAML 格式,由 ConfigManager 统一管理,支持从旧版单文件自动迁移
9
9
  *
10
10
  * 安全策略(多层防御):
11
- * 1. 连接级别: pg.Pool 设置 default_transaction_read_only=on,数据库层面禁止写操作
12
- * 2. SQL 级别: query_metadata tool 仅允许查询 information_schema/pg_catalog SELECT 语句
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)YAML 解析器(parser.ts)
17
- * - src/db/ — 数据库连接池管理(connection-manager.ts)和元数据查询封装(metadata-queries.ts)
18
- * - src/tools/ — 8 个 MCP Tool 的实现,每个 tool 一个文件
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. 读取 YAML 配置文件 → 2. 创建 ConnectionManager → 3. 注册 MCP Tools → 4. 启动 stdio 传输
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
- * - 提供只读的 PostgreSQL 数据库元数据查询能力,支持 ShardingSphere 风格的分片拓扑解析
8
- * - 配置文件采用 YAML 格式,兼容 ShardingSphere 配置,管理多个数据源和分片规则
7
+ * - 提供只读的数据库元数据查询能力(支持 PostgreSQL、MySQL 等),支持 ShardingSphere 风格的分片拓扑解析
8
+ * - 配置采用多文件 YAML 格式,由 ConfigManager 统一管理,支持从旧版单文件自动迁移
9
9
  *
10
10
  * 安全策略(多层防御):
11
- * 1. 连接级别: pg.Pool 设置 default_transaction_read_only=on,数据库层面禁止写操作
12
- * 2. SQL 级别: query_metadata tool 仅允许查询 information_schema/pg_catalog SELECT 语句
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)YAML 解析器(parser.ts)
17
- * - src/db/ — 数据库连接池管理(connection-manager.ts)和元数据查询封装(metadata-queries.ts)
18
- * - src/tools/ — 8 个 MCP Tool 的实现,每个 tool 一个文件
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. 读取 YAML 配置文件 → 2. 创建 ConnectionManager → 3. 注册 MCP Tools → 4. 启动 stdio 传输
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 { parseConfig } from './config/parser.js';
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
- // 全局安装时默认配置路径为 ~/.harneon-ai/db/datasource.yaml,与 npm 组织名 @harneon-ai 一致
41
- const defaultConfigPath = path.join(os.homedir(), '.harneon-ai', 'db', 'datasource.yaml');
42
- const configPath = process.argv[2] || process.env.DB_CONFIG_PATH || defaultConfigPath;
43
- // 容错启动: 配置文件不存在时使用空配置,允许用户通过 save_datasource_config 创建首份配置
44
- let config;
45
- if (fs.existsSync(configPath)) {
46
- config = parseConfig(configPath);
47
- }
48
- else {
49
- config = { dataSources: {} };
50
- console.error(`配置文件 ${configPath} 不存在,使用空配置启动。可通过 save_datasource_config 工具创建配置。`);
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: 测试 PostgreSQL 连接(独立于配置和连接池)
67
+ // 数据源配置管理工具:
68
+ // - test_connection: 测试数据库连接(独立于配置和连接池,自动检测数据库类型)
68
69
  // - save_datasource_config: 保存数据源配置到 YAML 文件并更新内存状态
69
- registerListDatasources(server, config, connectionManager);
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, configPath);
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,7 +2,7 @@
2
2
  * MCP Tool: describe_indexes
3
3
  *
4
4
  * 查询指定表的所有索引信息。
5
- * 通过 pg_catalog 系统表查询,返回索引名称、包含列、唯一性、索引方法(btree/hash/gin 等)
5
+ * 通过驱动抽象层查询,返回索引名称、包含列、唯一性、索引方法
6
6
  * 以及完整的 CREATE INDEX 语句定义。
7
7
  */
8
8
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
@@ -2,20 +2,22 @@
2
2
  * MCP Tool: describe_indexes
3
3
  *
4
4
  * 查询指定表的所有索引信息。
5
- * 通过 pg_catalog 系统表查询,返回索引名称、包含列、唯一性、索引方法(btree/hash/gin 等)
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 名称,默认 public'),
13
+ schema: z.string().optional().default('public').describe('Schema 名称(PostgreSQL 默认 public,MySQL 中对应 database 名,留空使用连接默认值)'),
15
14
  }, async ({ datasource, table, schema }) => {
16
15
  try {
17
- const pool = await connectionManager.getPool(datasource);
18
- const indexes = await describeIndexes(pool, table, schema);
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,6 +7,7 @@
7
7
  * - stats: 统计信息(行数估算、表大小、索引大小)
8
8
  *
9
9
  * 三个查询并行执行以减少延迟。
10
+ * 通过驱动抽象层查询,支持不同数据库类型。
10
11
  */
11
12
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
13
  import { type ParsedConfig } from '../config/types.js';
@@ -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 名称,默认 public'),
17
+ schema: z.string().optional().default('public').describe('Schema 名称(PostgreSQL 默认 public,MySQL 中对应 database 名,留空使用连接默认值)'),
18
18
  }, async ({ datasource, table, schema }) => {
19
19
  try {
20
- const pool = await connectionManager.getPool(datasource);
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(pool, table, schema),
24
- getTableConstraints(pool, table, schema),
25
- getTableStats(pool, table, schema),
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
- export declare function registerListDatasources(server: McpServer, config: ParsedConfig, _connectionManager: ConnectionManager): void;
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 { parseJdbcUrl } from '../config/parser.js';
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
- const parsed = parseJdbcUrl(jdbcUrl);
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
- * 查询 information_schema.tables,仅返回 BASE TABLE 和 VIEW 类型。
5
+ * 通过驱动抽象层查询,支持不同数据库类型(PostgreSQL、MySQL 等)。
6
6
  */
7
7
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
8
  import { type ParsedConfig } from '../config/types.js';