@harneon-ai/db 0.0.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.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * 数据库连接池管理器
3
+ *
4
+ * 管理多个 PostgreSQL 数据源的连接池,采用懒初始化策略:
5
+ * - 构造时不创建任何连接,首次调用 getPool() 时才创建对应数据源的连接池
6
+ * - 使用 Promise Map 避免并发调用时重复创建连接池(竞态安全)
7
+ * - 所有连接池强制设置 default_transaction_read_only=on(数据库层面只读保护)
8
+ *
9
+ * 连接池参数:
10
+ * - max=3: 元数据查询不需要大连接池,3 个连接足够
11
+ * - idleTimeoutMillis=30000: 空闲 30 秒后释放连接
12
+ */
13
+ import pg from 'pg';
14
+ import { parseJdbcUrl } from '../config/parser.js';
15
+ export class ConnectionManager {
16
+ /**
17
+ * 使用 Promise<pg.Pool> 而非 pg.Pool 存储,确保并发调用 getPool() 时
18
+ * 多个调用者 await 同一个 Promise,避免重复创建连接池导致资源泄漏
19
+ */
20
+ poolPromises = new Map();
21
+ config;
22
+ constructor(config) {
23
+ this.config = config;
24
+ }
25
+ /**
26
+ * 获取指定数据源的连接池(懒初始化,并发安全)
27
+ * @param dsName 数据源名称,对应 YAML 配置中 dataSources 下的 key(如 ds_0, ds_1)
28
+ * @throws 数据源不存在或 JDBC URL 解析失败时抛出错误
29
+ */
30
+ async getPool(dsName) {
31
+ const existing = this.poolPromises.get(dsName);
32
+ if (existing) {
33
+ return existing;
34
+ }
35
+ // 立即存入 Promise,后续并发调用会命中缓存
36
+ const promise = this.createPool(dsName);
37
+ this.poolPromises.set(dsName, promise);
38
+ return promise;
39
+ }
40
+ async createPool(dsName) {
41
+ const dsConfig = this.config.dataSources[dsName];
42
+ if (!dsConfig) {
43
+ throw new Error(`数据源 "${dsName}" 不存在,可用数据源: ${this.getAllDataSourceNames().join(', ')}`);
44
+ }
45
+ const jdbcUrl = dsConfig.jdbcUrl || dsConfig.url;
46
+ if (!jdbcUrl) {
47
+ throw new Error(`数据源 "${dsName}" 缺少 jdbcUrl 或 url 配置`);
48
+ }
49
+ const { host, port, database } = parseJdbcUrl(jdbcUrl);
50
+ return new pg.Pool({
51
+ host,
52
+ port,
53
+ database,
54
+ user: dsConfig.username,
55
+ password: dsConfig.password,
56
+ max: 3,
57
+ idleTimeoutMillis: 30000,
58
+ // 关键安全设置: 连接级别强制只读事务,即使 SQL 注入也无法执行写操作
59
+ options: '-c default_transaction_read_only=on',
60
+ });
61
+ }
62
+ /** 返回所有已配置的数据源名称列表 */
63
+ getAllDataSourceNames() {
64
+ return Object.keys(this.config.dataSources);
65
+ }
66
+ /**
67
+ * 重新加载配置: 关闭所有已创建的连接池并清空缓存
68
+ *
69
+ * 调用前应先原地修改 config 对象(因为所有 tool 闭包引用同一对象)。
70
+ * 调用后,下次 getPool() 会基于更新后的 config 懒初始化新连接池。
71
+ */
72
+ async reloadConfig() {
73
+ const pools = await Promise.all(Array.from(this.poolPromises.values()));
74
+ await Promise.all(pools.map(pool => pool.end()));
75
+ this.poolPromises.clear();
76
+ }
77
+ /** 关闭所有已创建的连接池,用于进程退出时的资源清理 */
78
+ async shutdown() {
79
+ const pools = await Promise.all(Array.from(this.poolPromises.values()));
80
+ await Promise.all(pools.map(pool => pool.end()));
81
+ this.poolPromises.clear();
82
+ }
83
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * PostgreSQL 元数据查询封装
3
+ *
4
+ * 提供一组纯函数,每个函数接收 pg.Pool 参数,执行只读的元数据查询。
5
+ * 所有 SQL 仅查询 information_schema 和 pg_catalog 系统目录,不访问任何业务数据。
6
+ * 全部使用参数化查询($1, $2)防止 SQL 注入。
7
+ *
8
+ * 函数一览:
9
+ * - listTables: 列出指定 schema 下的所有表和视图
10
+ * - describeTable: 查询表的列信息(含列注释)
11
+ * - describeIndexes: 查询表的索引信息(含索引方法和列)
12
+ * - getTableConstraints: 查询表的约束(主键、外键、唯一)
13
+ * - getTableStats: 查询表的统计信息(行数估算、表大小)
14
+ */
15
+ import type pg from 'pg';
16
+ /** information_schema.tables 查询结果 */
17
+ export interface TableInfo {
18
+ table_name: string;
19
+ table_type: string;
20
+ }
21
+ /** information_schema.columns + pg_description 联查结果 */
22
+ export interface ColumnInfo {
23
+ column_name: string;
24
+ data_type: string;
25
+ is_nullable: string;
26
+ column_default: string | null;
27
+ character_maximum_length: number | null;
28
+ comment: string | null;
29
+ ordinal_position: number;
30
+ }
31
+ /** pg_catalog 索引查询结果 */
32
+ export interface IndexInfo {
33
+ index_name: string;
34
+ index_columns: string;
35
+ is_unique: boolean;
36
+ index_method: string;
37
+ index_definition: string;
38
+ }
39
+ /** information_schema.table_constraints 查询结果 */
40
+ export interface ConstraintInfo {
41
+ constraint_name: string;
42
+ constraint_type: string;
43
+ columns: string;
44
+ }
45
+ /** pg_catalog 表统计查询结果 */
46
+ export interface TableStats {
47
+ estimated_rows: number;
48
+ total_size: string;
49
+ table_size: string;
50
+ index_size: string;
51
+ }
52
+ /** 列出指定 schema 下的所有用户表和视图 */
53
+ export declare function listTables(pool: pg.Pool, schema?: string): Promise<TableInfo[]>;
54
+ /**
55
+ * 查询表的列信息,包含列注释
56
+ *
57
+ * 通过 LEFT JOIN pg_catalog.pg_description 获取列注释:
58
+ * - pg_statio_all_tables 用于获取表的 OID (relid)
59
+ * - pg_description 通过 objoid + objsubid(列序号) 关联到列注释
60
+ */
61
+ export declare function describeTable(pool: pg.Pool, tableName: string, schema?: string): Promise<ColumnInfo[]>;
62
+ /**
63
+ * 查询表的索引信息
64
+ *
65
+ * 通过 pg_catalog 系统表联查:
66
+ * - pg_index: 索引元数据(唯一性等)
67
+ * - pg_class: 索引和表的 OID 信息
68
+ * - pg_am: 索引访问方法(btree/hash/gin/gist 等)
69
+ * - pg_attribute: 索引包含的列名
70
+ */
71
+ export declare function describeIndexes(pool: pg.Pool, tableName: string, schema?: string): Promise<IndexInfo[]>;
72
+ /** 查询表的约束信息(主键、外键、唯一约束) */
73
+ export declare function getTableConstraints(pool: pg.Pool, tableName: string, schema?: string): Promise<ConstraintInfo[]>;
74
+ /**
75
+ * 查询表的统计信息
76
+ *
77
+ * 注意: estimated_rows 来自 pg_stat_user_tables.n_live_tup,是基于 ANALYZE 的估算值
78
+ * 如需精确行数需要执行 COUNT(*),但这可能很慢且不在本工具的元数据查询范围内
79
+ *
80
+ * @returns 表统计信息,表不存在时返回 null
81
+ */
82
+ export declare function getTableStats(pool: pg.Pool, tableName: string, schema?: string): Promise<TableStats | null>;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * PostgreSQL 元数据查询封装
3
+ *
4
+ * 提供一组纯函数,每个函数接收 pg.Pool 参数,执行只读的元数据查询。
5
+ * 所有 SQL 仅查询 information_schema 和 pg_catalog 系统目录,不访问任何业务数据。
6
+ * 全部使用参数化查询($1, $2)防止 SQL 注入。
7
+ *
8
+ * 函数一览:
9
+ * - listTables: 列出指定 schema 下的所有表和视图
10
+ * - describeTable: 查询表的列信息(含列注释)
11
+ * - describeIndexes: 查询表的索引信息(含索引方法和列)
12
+ * - getTableConstraints: 查询表的约束(主键、外键、唯一)
13
+ * - getTableStats: 查询表的统计信息(行数估算、表大小)
14
+ */
15
+ /** 列出指定 schema 下的所有用户表和视图 */
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')
21
+ ORDER BY table_name`, [schema]);
22
+ return result.rows;
23
+ }
24
+ /**
25
+ * 查询表的列信息,包含列注释
26
+ *
27
+ * 通过 LEFT JOIN pg_catalog.pg_description 获取列注释:
28
+ * - pg_statio_all_tables 用于获取表的 OID (relid)
29
+ * - pg_description 通过 objoid + objsubid(列序号) 关联到列注释
30
+ */
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
46
+ ORDER BY c.ordinal_position`, [schema, tableName]);
47
+ return result.rows;
48
+ }
49
+ /**
50
+ * 查询表的索引信息
51
+ *
52
+ * 通过 pg_catalog 系统表联查:
53
+ * - pg_index: 索引元数据(唯一性等)
54
+ * - pg_class: 索引和表的 OID 信息
55
+ * - pg_am: 索引访问方法(btree/hash/gin/gist 等)
56
+ * - pg_attribute: 索引包含的列名
57
+ */
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
76
+ ORDER BY i.relname`, [schema, tableName]);
77
+ return result.rows;
78
+ }
79
+ /** 查询表的约束信息(主键、外键、唯一约束) */
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
91
+ ORDER BY tc.constraint_type, tc.constraint_name`, [schema, tableName]);
92
+ return result.rows;
93
+ }
94
+ /**
95
+ * 查询表的统计信息
96
+ *
97
+ * 注意: estimated_rows 来自 pg_stat_user_tables.n_live_tup,是基于 ANALYZE 的估算值
98
+ * 如需精确行数需要执行 COUNT(*),但这可能很慢且不在本工具的元数据查询范围内
99
+ *
100
+ * @returns 表统计信息,表不存在时返回 null
101
+ */
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
111
+ WHERE s.schemaname = $1 AND s.relname = $2`, [schema, tableName]);
112
+ return result.rows[0] ?? null;
113
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DB Info Sync MCP Server 入口文件
4
+ *
5
+ * 架构概述:
6
+ * - 本项目是一个 MCP (Model Context Protocol) Server,通过 stdio 传输与 AI 客户端(如 Claude Desktop/Claude Code)通信
7
+ * - 提供只读的 PostgreSQL 数据库元数据查询能力,支持 ShardingSphere 风格的分片拓扑解析
8
+ * - 配置文件采用 YAML 格式,兼容 ShardingSphere 配置,管理多个数据源和分片规则
9
+ *
10
+ * 安全策略(多层防御):
11
+ * 1. 连接级别: pg.Pool 设置 default_transaction_read_only=on,数据库层面禁止写操作
12
+ * 2. SQL 级别: query_metadata tool 仅允许查询 information_schema/pg_catalog 的 SELECT 语句
13
+ * 3. Tool 级别: 所有预定义 tool 只执行固定的元数据查询 SQL
14
+ *
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 一个文件
19
+ *
20
+ * 启动流程:
21
+ * 1. 读取 YAML 配置文件 → 2. 创建 ConnectionManager → 3. 注册 MCP Tools → 4. 启动 stdio 传输
22
+ */
23
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DB Info Sync MCP Server 入口文件
4
+ *
5
+ * 架构概述:
6
+ * - 本项目是一个 MCP (Model Context Protocol) Server,通过 stdio 传输与 AI 客户端(如 Claude Desktop/Claude Code)通信
7
+ * - 提供只读的 PostgreSQL 数据库元数据查询能力,支持 ShardingSphere 风格的分片拓扑解析
8
+ * - 配置文件采用 YAML 格式,兼容 ShardingSphere 配置,管理多个数据源和分片规则
9
+ *
10
+ * 安全策略(多层防御):
11
+ * 1. 连接级别: pg.Pool 设置 default_transaction_read_only=on,数据库层面禁止写操作
12
+ * 2. SQL 级别: query_metadata tool 仅允许查询 information_schema/pg_catalog 的 SELECT 语句
13
+ * 3. Tool 级别: 所有预定义 tool 只执行固定的元数据查询 SQL
14
+ *
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 一个文件
19
+ *
20
+ * 启动流程:
21
+ * 1. 读取 YAML 配置文件 → 2. 创建 ConnectionManager → 3. 注册 MCP Tools → 4. 启动 stdio 传输
22
+ */
23
+ import fs from 'node:fs';
24
+ import os from 'node:os';
25
+ import path from 'node:path';
26
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
27
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
28
+ import { parseConfig } from './config/parser.js';
29
+ import { ConnectionManager } from './db/connection-manager.js';
30
+ import { registerListDatasources } from './tools/list-datasources.js';
31
+ import { registerListTables } from './tools/list-tables.js';
32
+ import { registerDescribeTable } from './tools/describe-table.js';
33
+ import { registerDescribeIndexes } from './tools/describe-indexes.js';
34
+ import { registerShardingTopology } from './tools/sharding-topology.js';
35
+ import { registerQueryMetadata } from './tools/query-metadata.js';
36
+ import { registerTestConnection } from './tools/test-connection.js';
37
+ import { registerSaveDatasourceConfig } from './tools/save-datasource-config.js';
38
+ 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
+ }
52
+ // ConnectionManager 采用懒初始化策略,此时不会创建任何数据库连接
53
+ const connectionManager = new ConnectionManager(config);
54
+ const server = new McpServer({
55
+ name: '@harneon-ai/db',
56
+ version: '0.0.1',
57
+ });
58
+ // 注册所有 MCP Tools
59
+ // 只读工具:
60
+ // - list_datasources: 查看数据源配置(纯配置读取,不连接数据库)
61
+ // - list_tables: 列出指定数据源的表
62
+ // - describe_table: 查询表结构(列、约束、统计信息)
63
+ // - describe_indexes: 查询表的索引信息
64
+ // - sharding_topology: 查看分片拓扑(纯配置解析,不连接数据库)
65
+ // - query_metadata: 执行自定义元数据查询(带安全过滤)
66
+ // 数据源注册工具:
67
+ // - test_connection: 测试 PostgreSQL 连接(独立于配置和连接池)
68
+ // - save_datasource_config: 保存数据源配置到 YAML 文件并更新内存状态
69
+ registerListDatasources(server, config, connectionManager);
70
+ registerListTables(server, config, connectionManager);
71
+ registerDescribeTable(server, config, connectionManager);
72
+ registerDescribeIndexes(server, config, connectionManager);
73
+ registerShardingTopology(server, config, connectionManager);
74
+ registerQueryMetadata(server, config, connectionManager);
75
+ registerTestConnection(server);
76
+ registerSaveDatasourceConfig(server, config, connectionManager, configPath);
77
+ // 优雅退出: 收到终止信号时关闭所有数据库连接池
78
+ const shutdown = async () => {
79
+ await connectionManager.shutdown();
80
+ process.exit(0);
81
+ };
82
+ process.on('SIGINT', shutdown);
83
+ process.on('SIGTERM', shutdown);
84
+ // MCP 使用 stdio 传输: stdin 接收请求,stdout 发送响应
85
+ // 注意: 应用日志必须输出到 stderr,避免干扰 MCP 协议通信
86
+ const transport = new StdioServerTransport();
87
+ await server.connect(transport);
88
+ }
89
+ main().catch((error) => {
90
+ console.error('MCP Server 启动失败:', error);
91
+ process.exit(1);
92
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * MCP Tool: describe_indexes
3
+ *
4
+ * 查询指定表的所有索引信息。
5
+ * 通过 pg_catalog 系统表查询,返回索引名称、包含列、唯一性、索引方法(btree/hash/gin 等)
6
+ * 以及完整的 CREATE INDEX 语句定义。
7
+ */
8
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { type ParsedConfig } from '../config/types.js';
10
+ import { type ConnectionManager } from '../db/connection-manager.js';
11
+ export declare function registerDescribeIndexes(server: McpServer, _config: ParsedConfig, connectionManager: ConnectionManager): void;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * MCP Tool: describe_indexes
3
+ *
4
+ * 查询指定表的所有索引信息。
5
+ * 通过 pg_catalog 系统表查询,返回索引名称、包含列、唯一性、索引方法(btree/hash/gin 等)
6
+ * 以及完整的 CREATE INDEX 语句定义。
7
+ */
8
+ import { z } from 'zod';
9
+ import { describeIndexes } from '../db/metadata-queries.js';
10
+ export function registerDescribeIndexes(server, _config, connectionManager) {
11
+ server.tool('describe_indexes', '查询指定表的所有索引信息', {
12
+ datasource: z.string().describe('数据源名称'),
13
+ table: z.string().describe('表名'),
14
+ schema: z.string().optional().default('public').describe('Schema 名称,默认 public'),
15
+ }, async ({ datasource, table, schema }) => {
16
+ try {
17
+ const pool = await connectionManager.getPool(datasource);
18
+ const indexes = await describeIndexes(pool, table, schema);
19
+ return {
20
+ content: [{
21
+ type: 'text',
22
+ text: JSON.stringify(indexes, null, 2),
23
+ }],
24
+ };
25
+ }
26
+ catch (err) {
27
+ return {
28
+ content: [{
29
+ type: 'text',
30
+ text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }),
31
+ }],
32
+ isError: true,
33
+ };
34
+ }
35
+ });
36
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * MCP Tool: describe_table
3
+ *
4
+ * 查询指定表的完整结构信息,包含:
5
+ * - columns: 列定义(名称、类型、可空、默认值、注释)
6
+ * - constraints: 约束信息(主键、外键、唯一约束)
7
+ * - stats: 统计信息(行数估算、表大小、索引大小)
8
+ *
9
+ * 三个查询并行执行以减少延迟。
10
+ */
11
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
+ import { type ParsedConfig } from '../config/types.js';
13
+ import { type ConnectionManager } from '../db/connection-manager.js';
14
+ export declare function registerDescribeTable(server: McpServer, _config: ParsedConfig, connectionManager: ConnectionManager): void;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * MCP Tool: describe_table
3
+ *
4
+ * 查询指定表的完整结构信息,包含:
5
+ * - columns: 列定义(名称、类型、可空、默认值、注释)
6
+ * - constraints: 约束信息(主键、外键、唯一约束)
7
+ * - stats: 统计信息(行数估算、表大小、索引大小)
8
+ *
9
+ * 三个查询并行执行以减少延迟。
10
+ */
11
+ import { z } from 'zod';
12
+ import { describeTable, getTableConstraints, getTableStats } from '../db/metadata-queries.js';
13
+ export function registerDescribeTable(server, _config, connectionManager) {
14
+ server.tool('describe_table', '查询指定表的详细结构(列信息、约束、统计)', {
15
+ datasource: z.string().describe('数据源名称'),
16
+ table: z.string().describe('表名'),
17
+ schema: z.string().optional().default('public').describe('Schema 名称,默认 public'),
18
+ }, async ({ datasource, table, schema }) => {
19
+ try {
20
+ const pool = await connectionManager.getPool(datasource);
21
+ // 并行查询列信息、约束和统计,减少总延迟
22
+ const [columns, constraints, stats] = await Promise.all([
23
+ describeTable(pool, table, schema),
24
+ getTableConstraints(pool, table, schema),
25
+ getTableStats(pool, table, schema),
26
+ ]);
27
+ const result = { columns, constraints, stats };
28
+ return {
29
+ content: [{
30
+ type: 'text',
31
+ text: JSON.stringify(result, null, 2),
32
+ }],
33
+ };
34
+ }
35
+ catch (err) {
36
+ return {
37
+ content: [{
38
+ type: 'text',
39
+ text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }),
40
+ }],
41
+ isError: true,
42
+ };
43
+ }
44
+ });
45
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * MCP Tool: list_datasources
3
+ *
4
+ * 列出所有已配置的数据源连接信息。
5
+ * 这是一个纯配置读取操作,不连接任何数据库。
6
+ * 密码字段始终显示为 '***',防止敏感信息泄露。
7
+ */
8
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { type ParsedConfig } from '../config/types.js';
10
+ import { type ConnectionManager } from '../db/connection-manager.js';
11
+ export declare function registerListDatasources(server: McpServer, config: ParsedConfig, _connectionManager: ConnectionManager): void;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * MCP Tool: list_datasources
3
+ *
4
+ * 列出所有已配置的数据源连接信息。
5
+ * 这是一个纯配置读取操作,不连接任何数据库。
6
+ * 密码字段始终显示为 '***',防止敏感信息泄露。
7
+ */
8
+ import { parseJdbcUrl } from '../config/parser.js';
9
+ export function registerListDatasources(server, config, _connectionManager) {
10
+ server.tool('list_datasources', '列出所有配置的数据源信息(密码已隐藏)', {}, // 无输入参数
11
+ async () => {
12
+ const datasources = Object.entries(config.dataSources).map(([name, ds]) => {
13
+ const jdbcUrl = ds.jdbcUrl || ds.url || '';
14
+ let host = '', port = 0, database = '';
15
+ try {
16
+ const parsed = parseJdbcUrl(jdbcUrl);
17
+ host = parsed.host;
18
+ port = parsed.port;
19
+ database = parsed.database;
20
+ }
21
+ catch {
22
+ // URL 解析失败时保留空值,不阻断返回
23
+ }
24
+ return {
25
+ name,
26
+ host,
27
+ port,
28
+ database,
29
+ username: ds.username,
30
+ password: '***', // 安全: 始终隐藏密码
31
+ };
32
+ });
33
+ return {
34
+ content: [{
35
+ type: 'text',
36
+ text: JSON.stringify(datasources, null, 2),
37
+ }],
38
+ };
39
+ });
40
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * MCP Tool: list_tables
3
+ *
4
+ * 列出指定数据源中的所有用户表和视图。
5
+ * 查询 information_schema.tables,仅返回 BASE TABLE 和 VIEW 类型。
6
+ */
7
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { type ParsedConfig } from '../config/types.js';
9
+ import { type ConnectionManager } from '../db/connection-manager.js';
10
+ export declare function registerListTables(server: McpServer, _config: ParsedConfig, connectionManager: ConnectionManager): void;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * MCP Tool: list_tables
3
+ *
4
+ * 列出指定数据源中的所有用户表和视图。
5
+ * 查询 information_schema.tables,仅返回 BASE TABLE 和 VIEW 类型。
6
+ */
7
+ import { z } from 'zod';
8
+ import { listTables } from '../db/metadata-queries.js';
9
+ export function registerListTables(server, _config, connectionManager) {
10
+ server.tool('list_tables', '列出指定数据源中的所有表和视图', {
11
+ datasource: z.string().describe('数据源名称,如 ds_0'),
12
+ schema: z.string().optional().default('public').describe('Schema 名称,默认 public'),
13
+ }, async ({ datasource, schema }) => {
14
+ try {
15
+ const pool = await connectionManager.getPool(datasource);
16
+ const tables = await listTables(pool, schema);
17
+ return {
18
+ content: [{
19
+ type: 'text',
20
+ text: JSON.stringify(tables, null, 2),
21
+ }],
22
+ };
23
+ }
24
+ catch (err) {
25
+ return {
26
+ content: [{
27
+ type: 'text',
28
+ text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }),
29
+ }],
30
+ isError: true,
31
+ };
32
+ }
33
+ });
34
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * MCP Tool: query_metadata
3
+ *
4
+ * 允许 AI 执行自定义的元数据查询,提供灵活性的同时通过严格的安全过滤确保安全。
5
+ *
6
+ * 安全验证规则(全部通过才允许执行):
7
+ * 1. SQL 必须以 SELECT 开头
8
+ * 2. SQL 不能包含分号(防止多语句攻击)
9
+ * 3. SQL 不能包含写操作关键字(INSERT/UPDATE/DELETE/DROP/ALTER/CREATE/TRUNCATE/GRANT/REVOKE)
10
+ * 4. SQL 的 FROM 子句必须直接引用 information_schema 或 pg_catalog(防止通过构造字符串绕过)
11
+ *
12
+ * 注意: 这是 SQL 级别的安全层,连接级别还有 default_transaction_read_only=on 作为兜底
13
+ */
14
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
15
+ import { type ParsedConfig } from '../config/types.js';
16
+ import { type ConnectionManager } from '../db/connection-manager.js';
17
+ export declare function registerQueryMetadata(server: McpServer, _config: ParsedConfig, connectionManager: ConnectionManager): void;