@rmwxxwmr/mcp-database-service 0.1.2

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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +236 -0
  3. package/README.zh-CN.md +236 -0
  4. package/config/databases.example.json +48 -0
  5. package/dist/config/configSummary.d.ts +4 -0
  6. package/dist/config/configSummary.js +117 -0
  7. package/dist/config/configSummary.js.map +1 -0
  8. package/dist/config/configTypes.d.ts +81 -0
  9. package/dist/config/configTypes.js +2 -0
  10. package/dist/config/configTypes.js.map +1 -0
  11. package/dist/config/configValidation.d.ts +6 -0
  12. package/dist/config/configValidation.js +142 -0
  13. package/dist/config/configValidation.js.map +1 -0
  14. package/dist/config/loadConfig.d.ts +8 -0
  15. package/dist/config/loadConfig.js +85 -0
  16. package/dist/config/loadConfig.js.map +1 -0
  17. package/dist/core/errors.d.ts +15 -0
  18. package/dist/core/errors.js +28 -0
  19. package/dist/core/errors.js.map +1 -0
  20. package/dist/core/logger.d.ts +7 -0
  21. package/dist/core/logger.js +68 -0
  22. package/dist/core/logger.js.map +1 -0
  23. package/dist/core/resultTypes.d.ts +49 -0
  24. package/dist/core/resultTypes.js +2 -0
  25. package/dist/core/resultTypes.js.map +1 -0
  26. package/dist/db/clientFactory.d.ts +9 -0
  27. package/dist/db/clientFactory.js +27 -0
  28. package/dist/db/clientFactory.js.map +1 -0
  29. package/dist/db/readonlyGuard.d.ts +14 -0
  30. package/dist/db/readonlyGuard.js +253 -0
  31. package/dist/db/readonlyGuard.js.map +1 -0
  32. package/dist/db/redis/redisClient.d.ts +16 -0
  33. package/dist/db/redis/redisClient.js +106 -0
  34. package/dist/db/redis/redisClient.js.map +1 -0
  35. package/dist/db/sql/baseSqlAdapter.d.ts +52 -0
  36. package/dist/db/sql/baseSqlAdapter.js +310 -0
  37. package/dist/db/sql/baseSqlAdapter.js.map +1 -0
  38. package/dist/db/sql/mysqlClient.d.ts +33 -0
  39. package/dist/db/sql/mysqlClient.js +150 -0
  40. package/dist/db/sql/mysqlClient.js.map +1 -0
  41. package/dist/db/sql/openGaussClient.d.ts +9 -0
  42. package/dist/db/sql/openGaussClient.js +11 -0
  43. package/dist/db/sql/openGaussClient.js.map +1 -0
  44. package/dist/db/sql/oracleClient.d.ts +33 -0
  45. package/dist/db/sql/oracleClient.js +203 -0
  46. package/dist/db/sql/oracleClient.js.map +1 -0
  47. package/dist/db/sql/postgresClient.d.ts +33 -0
  48. package/dist/db/sql/postgresClient.js +160 -0
  49. package/dist/db/sql/postgresClient.js.map +1 -0
  50. package/dist/db/types.d.ts +24 -0
  51. package/dist/db/types.js +2 -0
  52. package/dist/db/types.js.map +1 -0
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.js +21 -0
  55. package/dist/server/createServer.d.ts +46 -0
  56. package/dist/server/createServer.js +441 -0
  57. package/dist/server/createServer.js.map +1 -0
  58. package/dist/server/toolRegistry.d.ts +36 -0
  59. package/dist/server/toolRegistry.js +927 -0
  60. package/dist/server/toolRegistry.js.map +1 -0
  61. package/dist/utils/normalize.d.ts +6 -0
  62. package/dist/utils/normalize.js +29 -0
  63. package/dist/utils/normalize.js.map +1 -0
  64. package/package.json +62 -0
@@ -0,0 +1,203 @@
1
+ import { ApplicationError, toApplicationError } from "../../core/errors.js";
2
+ import { BaseSqlAdapter } from "./baseSqlAdapter.js";
3
+ export class OracleAdapter extends BaseSqlAdapter {
4
+ oracleConfig;
5
+ connection = null;
6
+ constructor(oracleConfig, queryTimeoutMs) {
7
+ super(oracleConfig, queryTimeoutMs);
8
+ this.oracleConfig = oracleConfig;
9
+ }
10
+ async connect() {
11
+ try {
12
+ const oracledb = await loadOracleDb(this.oracleConfig);
13
+ const connectString = this.oracleConfig.connection.serviceName
14
+ ? `${this.oracleConfig.connection.host}:${this.oracleConfig.connection.port ?? 1521}/${this.oracleConfig.connection.serviceName}`
15
+ : `${this.oracleConfig.connection.host}:${this.oracleConfig.connection.port ?? 1521}:${this.oracleConfig.connection.sid}`;
16
+ this.connection = await oracledb.getConnection({
17
+ user: this.oracleConfig.connection.user,
18
+ password: this.oracleConfig.connection.password,
19
+ connectString
20
+ });
21
+ }
22
+ catch (error) {
23
+ throw toApplicationError(error, "CONNECTION_ERROR");
24
+ }
25
+ }
26
+ async close() {
27
+ if (!this.connection) {
28
+ return;
29
+ }
30
+ await this.connection.close();
31
+ this.connection = null;
32
+ }
33
+ async executeRaw(sql, params) {
34
+ if (!this.connection) {
35
+ throw new ApplicationError("CONNECTION_ERROR", "Oracle connection is not open");
36
+ }
37
+ const oracledb = await loadOracleDb(this.oracleConfig);
38
+ const result = await this.connection.execute(sql, params ?? [], {
39
+ outFormat: oracledb.OUT_FORMAT_OBJECT
40
+ });
41
+ return (result.rows ?? []);
42
+ }
43
+ async executeStatementRaw(sql, params) {
44
+ if (!this.connection) {
45
+ throw new ApplicationError("CONNECTION_ERROR", "Oracle connection is not open");
46
+ }
47
+ const result = await this.connection.execute(sql, params ?? []);
48
+ const affectedRows = typeof result === "object" && result !== null && "rowsAffected" in result
49
+ ? Number(result.rowsAffected ?? 0)
50
+ : null;
51
+ return { affectedRows };
52
+ }
53
+ async explainQueryRows(sql, params) {
54
+ await this.executeStatementRaw(`EXPLAIN PLAN FOR ${sql}`, params);
55
+ return this.executeRaw(`
56
+ SELECT plan_table_output AS planLine
57
+ FROM TABLE(DBMS_XPLAN.DISPLAY())
58
+ `);
59
+ }
60
+ async analyzeQueryRows(_sql, _params) {
61
+ throw new ApplicationError("NOT_SUPPORTED", "analyze_query is not supported for Oracle in the current implementation");
62
+ }
63
+ pingSql() {
64
+ return "SELECT 1 AS ok FROM dual";
65
+ }
66
+ listSchemasSql() {
67
+ return "SELECT username AS schema FROM all_users ORDER BY username";
68
+ }
69
+ listTablesSql(schema) {
70
+ return {
71
+ sql: `
72
+ SELECT owner AS schema_name, table_name AS name, 'TABLE' AS type
73
+ FROM all_tables
74
+ WHERE owner = COALESCE(:schema, SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA'))
75
+ UNION ALL
76
+ SELECT owner AS schema_name, view_name AS name, 'VIEW' AS type
77
+ FROM all_views
78
+ WHERE owner = COALESCE(:schema, SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA'))
79
+ ORDER BY name
80
+ `,
81
+ params: { schema: schema?.toUpperCase() ?? null }
82
+ };
83
+ }
84
+ describeTableSql(schema, table) {
85
+ return {
86
+ sql: `
87
+ SELECT
88
+ c.column_name AS name,
89
+ c.data_type AS datatype,
90
+ c.nullable AS nullable,
91
+ c.data_default AS defaultValue,
92
+ com.comments AS comment_text,
93
+ CASE
94
+ WHEN EXISTS (
95
+ SELECT 1
96
+ FROM all_constraints cons
97
+ JOIN all_cons_columns cols
98
+ ON cons.owner = cols.owner
99
+ AND cons.constraint_name = cols.constraint_name
100
+ WHERE cons.constraint_type = 'P'
101
+ AND cons.owner = c.owner
102
+ AND cons.table_name = c.table_name
103
+ AND cols.column_name = c.column_name
104
+ ) THEN 1 ELSE 0
105
+ END AS primary_key
106
+ FROM all_tab_columns c
107
+ LEFT JOIN all_col_comments com
108
+ ON com.owner = c.owner
109
+ AND com.table_name = c.table_name
110
+ AND com.column_name = c.column_name
111
+ WHERE c.owner = COALESCE(:schema, SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA'))
112
+ AND c.table_name = :tableName
113
+ ORDER BY c.column_id
114
+ `,
115
+ params: {
116
+ schema: schema?.toUpperCase() ?? null,
117
+ tableName: table.toUpperCase()
118
+ }
119
+ };
120
+ }
121
+ listIndexesSql(schema, table) {
122
+ return {
123
+ sql: `
124
+ SELECT
125
+ idx.table_owner AS schema_name,
126
+ idx.table_name AS table_name,
127
+ idx.index_name AS index_name,
128
+ col.column_name AS column_name,
129
+ CASE WHEN idx.uniqueness = 'UNIQUE' THEN 1 ELSE 0 END AS is_unique,
130
+ col.column_position AS column_position,
131
+ col.descend AS sort_order,
132
+ idx.index_type AS index_type
133
+ FROM all_indexes idx
134
+ LEFT JOIN all_ind_columns col
135
+ ON idx.owner = col.index_owner
136
+ AND idx.index_name = col.index_name
137
+ AND idx.table_owner = col.table_owner
138
+ AND idx.table_name = col.table_name
139
+ WHERE idx.table_owner = COALESCE(:schema, SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA'))
140
+ AND idx.table_name = :tableName
141
+ ORDER BY idx.index_name, col.column_position
142
+ `,
143
+ params: {
144
+ schema: schema?.toUpperCase() ?? null,
145
+ tableName: table.toUpperCase()
146
+ }
147
+ };
148
+ }
149
+ tableStatisticsSql(schema, table) {
150
+ return {
151
+ sql: `
152
+ SELECT
153
+ owner AS schema_name,
154
+ table_name AS table_name,
155
+ num_rows AS approximateRowCount,
156
+ blocks,
157
+ avg_row_len AS averageRowLength,
158
+ sample_size AS sampleSize,
159
+ last_analyzed AS lastAnalyzed,
160
+ temporary AS isTemporary,
161
+ partitioned AS isPartitioned
162
+ FROM all_tables
163
+ WHERE owner = COALESCE(:schema, SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA'))
164
+ AND table_name = :tableName
165
+ `,
166
+ params: {
167
+ schema: schema?.toUpperCase() ?? null,
168
+ tableName: table.toUpperCase()
169
+ }
170
+ };
171
+ }
172
+ }
173
+ let initializedOracleClient = null;
174
+ async function loadOracleDb(config) {
175
+ const module = await import("oracledb");
176
+ const oracledb = (module.default ?? module);
177
+ initializeOracleClientIfNeeded(oracledb, config);
178
+ return oracledb;
179
+ }
180
+ function initializeOracleClientIfNeeded(oracledb, config) {
181
+ const desiredMode = config.connection.clientMode ?? (config.connection.clientLibDir ? "thick" : "thin");
182
+ const desiredLibDir = config.connection.clientLibDir;
183
+ if (!initializedOracleClient) {
184
+ if (desiredMode === "thick") {
185
+ oracledb.initOracleClient?.({ libDir: desiredLibDir });
186
+ }
187
+ initializedOracleClient = {
188
+ mode: desiredMode,
189
+ clientLibDir: desiredLibDir
190
+ };
191
+ return;
192
+ }
193
+ if (initializedOracleClient.mode !== desiredMode) {
194
+ throw new ApplicationError("CONFIG_ERROR", `Oracle client mode conflict: already initialized as ${initializedOracleClient.mode}, requested ${desiredMode}`);
195
+ }
196
+ if (desiredMode === "thick" && initializedOracleClient.clientLibDir !== desiredLibDir) {
197
+ throw new ApplicationError("CONFIG_ERROR", "Oracle thick mode already initialized with a different clientLibDir", {
198
+ currentLibDir: initializedOracleClient.clientLibDir ?? null,
199
+ requestedLibDir: desiredLibDir ?? null
200
+ });
201
+ }
202
+ }
203
+ //# sourceMappingURL=oracleClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oracleClient.js","sourceRoot":"","sources":["../../../src/db/sql/oracleClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { PostgresDatabaseConfig } from "../../config/configTypes.js";
2
+ import { BaseSqlAdapter } from "./baseSqlAdapter.js";
3
+ export declare class PostgresAdapter extends BaseSqlAdapter {
4
+ private readonly postgresConfig;
5
+ private client;
6
+ constructor(postgresConfig: PostgresDatabaseConfig, queryTimeoutMs: number | null);
7
+ connect(): Promise<void>;
8
+ close(): Promise<void>;
9
+ protected executeRaw(sql: string, params?: unknown[] | Record<string, unknown>): Promise<Record<string, unknown>[]>;
10
+ protected executeStatementRaw(sql: string, params?: unknown[] | Record<string, unknown>): Promise<{
11
+ affectedRows: number | null;
12
+ }>;
13
+ protected explainQueryRows(sql: string, params?: unknown[] | Record<string, unknown>): Promise<Record<string, unknown>[]>;
14
+ protected analyzeQueryRows(sql: string, params?: unknown[] | Record<string, unknown>): Promise<Record<string, unknown>[]>;
15
+ protected pingSql(): string;
16
+ protected listSchemasSql(): string;
17
+ protected listTablesSql(schema?: string): {
18
+ sql: string;
19
+ params?: unknown[];
20
+ };
21
+ protected describeTableSql(schema: string | undefined, table: string): {
22
+ sql: string;
23
+ params?: unknown[];
24
+ };
25
+ protected listIndexesSql(schema: string | undefined, table: string): {
26
+ sql: string;
27
+ params?: unknown[];
28
+ };
29
+ protected tableStatisticsSql(schema: string | undefined, table: string): {
30
+ sql: string;
31
+ params?: unknown[];
32
+ };
33
+ }
@@ -0,0 +1,160 @@
1
+ import { ApplicationError, toApplicationError } from "../../core/errors.js";
2
+ import { BaseSqlAdapter } from "./baseSqlAdapter.js";
3
+ export class PostgresAdapter extends BaseSqlAdapter {
4
+ postgresConfig;
5
+ client = null;
6
+ constructor(postgresConfig, queryTimeoutMs) {
7
+ super(postgresConfig, queryTimeoutMs);
8
+ this.postgresConfig = postgresConfig;
9
+ }
10
+ async connect() {
11
+ try {
12
+ const { Client: PgClient } = await import("pg");
13
+ this.client = new PgClient({
14
+ host: this.postgresConfig.connection.host,
15
+ port: this.postgresConfig.connection.port ?? 5432,
16
+ database: this.postgresConfig.connection.databaseName,
17
+ user: this.postgresConfig.connection.user,
18
+ password: this.postgresConfig.connection.password,
19
+ connectionTimeoutMillis: this.postgresConfig.connection.connectTimeoutMs,
20
+ ssl: this.postgresConfig.connection.ssl
21
+ });
22
+ await this.client.connect();
23
+ if (this.postgresConfig.readonly) {
24
+ await this.client.query("SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY");
25
+ }
26
+ if (this.queryTimeoutMs) {
27
+ await this.client.query(`SET statement_timeout = ${this.queryTimeoutMs}`);
28
+ }
29
+ }
30
+ catch (error) {
31
+ throw toApplicationError(error, "CONNECTION_ERROR");
32
+ }
33
+ }
34
+ async close() {
35
+ if (!this.client) {
36
+ return;
37
+ }
38
+ await this.client.end();
39
+ this.client = null;
40
+ }
41
+ async executeRaw(sql, params) {
42
+ if (!this.client) {
43
+ throw new ApplicationError("CONNECTION_ERROR", "PostgreSQL connection is not open");
44
+ }
45
+ const result = await this.client.query(sql, Array.isArray(params) ? params : []);
46
+ return result.rows;
47
+ }
48
+ async executeStatementRaw(sql, params) {
49
+ if (!this.client) {
50
+ throw new ApplicationError("CONNECTION_ERROR", "PostgreSQL connection is not open");
51
+ }
52
+ const result = await this.client.query(sql, Array.isArray(params) ? params : []);
53
+ return {
54
+ affectedRows: typeof result.rowCount === "number" ? result.rowCount : null
55
+ };
56
+ }
57
+ async explainQueryRows(sql, params) {
58
+ return this.executeRaw(`EXPLAIN ${sql}`, params);
59
+ }
60
+ async analyzeQueryRows(sql, params) {
61
+ return this.executeRaw(`EXPLAIN (ANALYZE, BUFFERS, VERBOSE) ${sql}`, params);
62
+ }
63
+ pingSql() {
64
+ return "SELECT 1 AS ok";
65
+ }
66
+ listSchemasSql() {
67
+ return `
68
+ SELECT schema_name AS schema
69
+ FROM information_schema.schemata
70
+ WHERE schema_name NOT IN ('information_schema', 'pg_catalog')
71
+ ORDER BY schema_name
72
+ `;
73
+ }
74
+ listTablesSql(schema) {
75
+ return {
76
+ sql: `
77
+ SELECT table_schema AS schema, table_name AS name, table_type AS type
78
+ FROM information_schema.tables
79
+ WHERE table_schema = COALESCE($1, current_schema())
80
+ ORDER BY table_name
81
+ `,
82
+ params: [schema ?? null]
83
+ };
84
+ }
85
+ describeTableSql(schema, table) {
86
+ return {
87
+ sql: `
88
+ SELECT
89
+ c.column_name AS name,
90
+ c.data_type AS dataType,
91
+ c.is_nullable AS nullable,
92
+ c.column_default AS defaultValue,
93
+ pgd.description AS comment,
94
+ EXISTS (
95
+ SELECT 1
96
+ FROM information_schema.table_constraints tc
97
+ JOIN information_schema.key_column_usage kcu
98
+ ON tc.constraint_name = kcu.constraint_name
99
+ AND tc.table_schema = kcu.table_schema
100
+ WHERE tc.constraint_type = 'PRIMARY KEY'
101
+ AND tc.table_schema = c.table_schema
102
+ AND tc.table_name = c.table_name
103
+ AND kcu.column_name = c.column_name
104
+ ) AS primaryKey
105
+ FROM information_schema.columns c
106
+ LEFT JOIN pg_catalog.pg_statio_all_tables st
107
+ ON st.relname = c.table_name
108
+ AND st.schemaname = c.table_schema
109
+ LEFT JOIN pg_catalog.pg_description pgd
110
+ ON pgd.objoid = st.relid
111
+ AND pgd.objsubid = c.ordinal_position
112
+ WHERE c.table_schema = COALESCE($1, current_schema())
113
+ AND c.table_name = $2
114
+ ORDER BY c.ordinal_position
115
+ `,
116
+ params: [schema ?? null, table]
117
+ };
118
+ }
119
+ listIndexesSql(schema, table) {
120
+ return {
121
+ sql: `
122
+ SELECT
123
+ schemaname AS schema_name,
124
+ tablename AS table_name,
125
+ indexname AS index_name,
126
+ indexdef AS definition
127
+ FROM pg_indexes
128
+ WHERE schemaname = COALESCE($1, current_schema())
129
+ AND tablename = $2
130
+ ORDER BY indexname
131
+ `,
132
+ params: [schema ?? null, table]
133
+ };
134
+ }
135
+ tableStatisticsSql(schema, table) {
136
+ return {
137
+ sql: `
138
+ SELECT
139
+ schemaname AS schema_name,
140
+ relname AS table_name,
141
+ n_live_tup AS approximateRowCount,
142
+ n_dead_tup AS deadRowCount,
143
+ seq_scan AS sequentialScans,
144
+ idx_scan AS indexScans,
145
+ pg_total_relation_size(relid) AS totalBytes,
146
+ pg_relation_size(relid) AS tableBytes,
147
+ pg_indexes_size(relid) AS indexBytes,
148
+ last_analyze AS lastAnalyze,
149
+ last_autoanalyze AS lastAutoAnalyze,
150
+ last_vacuum AS lastVacuum,
151
+ last_autovacuum AS lastAutoVacuum
152
+ FROM pg_stat_user_tables
153
+ WHERE schemaname = COALESCE($1, current_schema())
154
+ AND relname = $2
155
+ `,
156
+ params: [schema ?? null, table]
157
+ };
158
+ }
159
+ }
160
+ //# sourceMappingURL=postgresClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgresClient.js","sourceRoot":"","sources":["../../../src/db/sql/postgresClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { DatabaseConfig } from "../config/configTypes.js";
2
+ import type { ColumnInfo, IndexInfo, PingResult, QueryResult, RedisScanResult, SchemaInfo, StatementResult, TableStatistics, TableInfo } from "../core/resultTypes.js";
3
+ export interface DatabaseAdapter {
4
+ readonly config: DatabaseConfig;
5
+ connect(): Promise<void>;
6
+ close(): Promise<void>;
7
+ ping(): Promise<PingResult>;
8
+ }
9
+ export interface SqlDatabaseAdapter extends DatabaseAdapter {
10
+ listSchemas(): Promise<SchemaInfo[]>;
11
+ listTables(schema?: string): Promise<TableInfo[]>;
12
+ describeTable(schema: string | undefined, table: string): Promise<ColumnInfo[]>;
13
+ listIndexes(schema: string | undefined, table: string): Promise<IndexInfo[]>;
14
+ getTableStatistics(schema: string | undefined, table: string): Promise<TableStatistics | null>;
15
+ explainQuery(sql: string, params: unknown[] | undefined, maxRows: number): Promise<QueryResult>;
16
+ analyzeQuery(sql: string, params: unknown[] | undefined, maxRows: number): Promise<QueryResult>;
17
+ executeQuery(sql: string, params: unknown[] | undefined, maxRows: number): Promise<QueryResult>;
18
+ executeStatement(sql: string, params?: unknown[]): Promise<StatementResult>;
19
+ }
20
+ export interface RedisDatabaseAdapter extends DatabaseAdapter {
21
+ get(key: string): Promise<string | null>;
22
+ hgetall(key: string): Promise<Record<string, string>>;
23
+ scan(cursor: string, pattern: string | undefined, count: number): Promise<RedisScanResult>;
24
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/db/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { loadConfig } from "./config/loadConfig.js";
3
+ import { log } from "./core/logger.js";
4
+ import { createServer } from "./server/createServer.js";
5
+ /**
6
+ * Main entrypoint: load validated config, start the MCP server, and leave all
7
+ * actual database work to per-request lazy adapters.
8
+ */
9
+ async function main() {
10
+ const config = await loadConfig(process.argv.slice(2), process.env);
11
+ await createServer(config);
12
+ log("info", "MCP database server started", {
13
+ databaseCount: config.databases.length
14
+ });
15
+ }
16
+ main().catch((error) => {
17
+ const message = error instanceof Error ? error.message : String(error);
18
+ log("error", "Failed to start MCP database server", { message });
19
+ process.exitCode = 1;
20
+ });
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,46 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import type { LoadedConfig } from "../config/configTypes.js";
3
+ interface PendingStatementConfirmation {
4
+ databaseKey: string;
5
+ sql: string;
6
+ params?: unknown[];
7
+ expiresAt: number;
8
+ }
9
+ interface StatementConfirmationInput {
10
+ databaseKey: string;
11
+ sql: string;
12
+ params?: unknown[];
13
+ confirmationId?: string;
14
+ confirmExecution?: boolean;
15
+ }
16
+ interface StatementConfirmationContext {
17
+ database: LoadedConfig["databases"][number];
18
+ input: StatementConfirmationInput;
19
+ pendingConfirmations: Map<string, PendingStatementConfirmation>;
20
+ supportsInteractiveConfirmation: boolean;
21
+ elicitConfirmation?: (message: string) => Promise<boolean>;
22
+ now?: () => number;
23
+ createId?: () => string;
24
+ maxPendingConfirmations?: number;
25
+ }
26
+ type StatementConfirmationResult = {
27
+ status: "confirmed";
28
+ } | {
29
+ status: "pending";
30
+ confirmationId: string;
31
+ confirmationMode: "two_step";
32
+ message: string;
33
+ statement: string;
34
+ targetObject: string;
35
+ riskLevel: "normal" | "high" | "critical";
36
+ riskDetails: string;
37
+ sqlPreview: string;
38
+ paramsPreview: string;
39
+ };
40
+ /**
41
+ * The MCP layer is intentionally thin: validate input, route to the correct
42
+ * adapter, and return normalized JSON text for clients.
43
+ */
44
+ export declare function createServer(config: LoadedConfig): Promise<Server>;
45
+ export declare function confirmStatementExecutionWithFallback(context: StatementConfirmationContext): Promise<StatementConfirmationResult>;
46
+ export {};