mysql-db-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +374 -0
  2. package/dist/config.d.ts +26 -0
  3. package/dist/config.js +83 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/db/MySqlClient.d.ts +16 -0
  6. package/dist/db/MySqlClient.js +115 -0
  7. package/dist/db/MySqlClient.js.map +1 -0
  8. package/dist/db/MySqlPool.d.ts +3 -0
  9. package/dist/db/MySqlPool.js +36 -0
  10. package/dist/db/MySqlPool.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +60 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/logger.d.ts +6 -0
  15. package/dist/logger.js +53 -0
  16. package/dist/logger.js.map +1 -0
  17. package/dist/security.d.ts +17 -0
  18. package/dist/security.js +165 -0
  19. package/dist/security.js.map +1 -0
  20. package/dist/tools/alterTableAddColumn.d.ts +3 -0
  21. package/dist/tools/alterTableAddColumn.js +49 -0
  22. package/dist/tools/alterTableAddColumn.js.map +1 -0
  23. package/dist/tools/alterTableAddIndex.d.ts +3 -0
  24. package/dist/tools/alterTableAddIndex.js +46 -0
  25. package/dist/tools/alterTableAddIndex.js.map +1 -0
  26. package/dist/tools/alterTableDropColumn.d.ts +3 -0
  27. package/dist/tools/alterTableDropColumn.js +36 -0
  28. package/dist/tools/alterTableDropColumn.js.map +1 -0
  29. package/dist/tools/alterTableDropIndex.d.ts +3 -0
  30. package/dist/tools/alterTableDropIndex.js +33 -0
  31. package/dist/tools/alterTableDropIndex.js.map +1 -0
  32. package/dist/tools/alterTableModifyColumn.d.ts +3 -0
  33. package/dist/tools/alterTableModifyColumn.js +46 -0
  34. package/dist/tools/alterTableModifyColumn.js.map +1 -0
  35. package/dist/tools/alterTableRenameColumn.d.ts +3 -0
  36. package/dist/tools/alterTableRenameColumn.js +38 -0
  37. package/dist/tools/alterTableRenameColumn.js.map +1 -0
  38. package/dist/tools/describeTable.d.ts +3 -0
  39. package/dist/tools/describeTable.js +18 -0
  40. package/dist/tools/describeTable.js.map +1 -0
  41. package/dist/tools/explainSelect.d.ts +3 -0
  42. package/dist/tools/explainSelect.js +18 -0
  43. package/dist/tools/explainSelect.js.map +1 -0
  44. package/dist/tools/insertRow.d.ts +3 -0
  45. package/dist/tools/insertRow.js +30 -0
  46. package/dist/tools/insertRow.js.map +1 -0
  47. package/dist/tools/insertRows.d.ts +3 -0
  48. package/dist/tools/insertRows.js +40 -0
  49. package/dist/tools/insertRows.js.map +1 -0
  50. package/dist/tools/listSchemas.d.ts +3 -0
  51. package/dist/tools/listSchemas.js +7 -0
  52. package/dist/tools/listSchemas.js.map +1 -0
  53. package/dist/tools/listTables.d.ts +3 -0
  54. package/dist/tools/listTables.js +16 -0
  55. package/dist/tools/listTables.js.map +1 -0
  56. package/dist/tools/querySelect.d.ts +3 -0
  57. package/dist/tools/querySelect.js +20 -0
  58. package/dist/tools/querySelect.js.map +1 -0
  59. package/dist/tools/shared.d.ts +43 -0
  60. package/dist/tools/shared.js +113 -0
  61. package/dist/tools/shared.js.map +1 -0
  62. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,374 @@
1
+ # mysql-db-mcp
2
+
3
+ A secure MySQL database MCP Server for reading schemas, tables, columns, running SELECT queries, running EXPLAIN, and optionally performing structured inserts and controlled table DDL.
4
+
5
+ The default mode is read-only:
6
+
7
+ ```bash
8
+ MYSQL_ENABLE_WRITE=false
9
+ MYSQL_ENABLE_DDL=false
10
+ ```
11
+
12
+ No arbitrary `execute_sql`, `execute_write`, or `execute_ddl` tool is provided. Write and DDL operations are only available through structured tool parameters, so identifiers can be validated, values can use MySQL parameter binding, and high-risk operations can require explicit confirmation.
13
+
14
+ ## Tools
15
+
16
+ Read-only tools enabled by default:
17
+
18
+ - `list_schemas`
19
+ - `list_tables`
20
+ - `describe_table`
21
+ - `query_select`
22
+ - `explain_select`
23
+
24
+ Write tools disabled by default:
25
+
26
+ - `insert_row`
27
+ - `insert_rows`
28
+
29
+ DDL tools disabled by default:
30
+
31
+ - `alter_table_add_column`
32
+ - `alter_table_modify_column`
33
+ - `alter_table_drop_column`
34
+ - `alter_table_rename_column`
35
+ - `alter_table_add_index`
36
+ - `alter_table_drop_index`
37
+
38
+ ## Environment Variables
39
+
40
+ | Variable | Default | Description |
41
+ | --- | --- | --- |
42
+ | `MYSQL_HOST` | `127.0.0.1` | MySQL host |
43
+ | `MYSQL_PORT` | `3306` | MySQL port |
44
+ | `MYSQL_USER` | `root` | MySQL user |
45
+ | `MYSQL_PASSWORD` | empty | MySQL password |
46
+ | `MYSQL_DATABASE` | unset | Default schema |
47
+ | `MYSQL_SSL` | `false` | Enable mysql2 SSL option |
48
+ | `MYSQL_CONNECTION_LIMIT` | `10` | Pool connection limit |
49
+ | `MYSQL_MAX_ROWS` | `200` | Maximum returned SELECT rows |
50
+ | `MYSQL_ENABLE_WRITE` | `false` | Enables `insert_row` and `insert_rows` |
51
+ | `MYSQL_ENABLE_DDL` | `false` | Enables controlled ALTER TABLE tools |
52
+ | `MYSQL_WRITE_ALLOWED_SCHEMAS` | unset | Comma-separated schemas allowed for writes |
53
+ | `MYSQL_WRITE_ALLOWED_TABLES` | unset | Comma-separated tables allowed for writes |
54
+ | `MYSQL_DDL_ALLOWED_SCHEMAS` | unset | Comma-separated schemas allowed for DDL |
55
+ | `MYSQL_DDL_ALLOWED_TABLES` | unset | Comma-separated tables allowed for DDL |
56
+ | `MYSQL_BLOCKED_TABLES` | unset | Extra comma-separated blocked tables |
57
+ | `MYSQL_MAX_INSERT_ROWS` | `100` | Max rows for `insert_rows` |
58
+ | `MYSQL_REQUIRE_CONFIRMATION` | `true` | Requires `confirm: true` for write and DDL tools |
59
+
60
+ System schemas are always blocked: `mysql`, `information_schema`, `performance_schema`, and `sys`.
61
+
62
+ ## Install And Run
63
+
64
+ ```bash
65
+ mkdir mysql-db-mcp
66
+ cd mysql-db-mcp
67
+ npm install
68
+ npm run build
69
+ npm link
70
+ mysql-db-mcp
71
+ ```
72
+
73
+ For local development:
74
+
75
+ ```bash
76
+ npm run dev
77
+ ```
78
+
79
+ After publishing to npm, users can start it with:
80
+
81
+ ```bash
82
+ npx mysql-db-mcp
83
+ ```
84
+
85
+ ## MCP Client Config
86
+
87
+ Read-only mode:
88
+
89
+ ```json
90
+ {
91
+ "mcpServers": {
92
+ "mysql-db": {
93
+ "command": "npx",
94
+ "args": ["-y", "mysql-db-mcp"],
95
+ "env": {
96
+ "MYSQL_HOST": "127.0.0.1",
97
+ "MYSQL_PORT": "3306",
98
+ "MYSQL_USER": "readonly_user",
99
+ "MYSQL_PASSWORD": "your_password",
100
+ "MYSQL_DATABASE": "your_database",
101
+ "MYSQL_MAX_ROWS": "200"
102
+ }
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ Allow inserts:
109
+
110
+ ```json
111
+ {
112
+ "mcpServers": {
113
+ "mysql-db": {
114
+ "command": "npx",
115
+ "args": ["-y", "mysql-db-mcp"],
116
+ "env": {
117
+ "MYSQL_HOST": "127.0.0.1",
118
+ "MYSQL_PORT": "3306",
119
+ "MYSQL_USER": "writer_user",
120
+ "MYSQL_PASSWORD": "your_password",
121
+ "MYSQL_DATABASE": "your_database",
122
+ "MYSQL_ENABLE_WRITE": "true",
123
+ "MYSQL_WRITE_ALLOWED_SCHEMAS": "your_database",
124
+ "MYSQL_WRITE_ALLOWED_TABLES": "users,orders",
125
+ "MYSQL_REQUIRE_CONFIRMATION": "true",
126
+ "MYSQL_MAX_INSERT_ROWS": "100"
127
+ }
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ Allow DDL:
134
+
135
+ ```json
136
+ {
137
+ "mcpServers": {
138
+ "mysql-db": {
139
+ "command": "npx",
140
+ "args": ["-y", "mysql-db-mcp"],
141
+ "env": {
142
+ "MYSQL_HOST": "127.0.0.1",
143
+ "MYSQL_PORT": "3306",
144
+ "MYSQL_USER": "ddl_user",
145
+ "MYSQL_PASSWORD": "your_password",
146
+ "MYSQL_DATABASE": "your_database",
147
+ "MYSQL_ENABLE_DDL": "true",
148
+ "MYSQL_DDL_ALLOWED_SCHEMAS": "your_database",
149
+ "MYSQL_DDL_ALLOWED_TABLES": "users,orders",
150
+ "MYSQL_REQUIRE_CONFIRMATION": "true"
151
+ }
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ Allow inserts and DDL:
158
+
159
+ ```json
160
+ {
161
+ "mcpServers": {
162
+ "mysql-db": {
163
+ "command": "npx",
164
+ "args": ["-y", "mysql-db-mcp"],
165
+ "env": {
166
+ "MYSQL_HOST": "127.0.0.1",
167
+ "MYSQL_PORT": "3306",
168
+ "MYSQL_USER": "admin_limited_user",
169
+ "MYSQL_PASSWORD": "your_password",
170
+ "MYSQL_DATABASE": "your_database",
171
+ "MYSQL_ENABLE_WRITE": "true",
172
+ "MYSQL_ENABLE_DDL": "true",
173
+ "MYSQL_WRITE_ALLOWED_SCHEMAS": "your_database",
174
+ "MYSQL_WRITE_ALLOWED_TABLES": "users,orders",
175
+ "MYSQL_DDL_ALLOWED_SCHEMAS": "your_database",
176
+ "MYSQL_DDL_ALLOWED_TABLES": "users,orders",
177
+ "MYSQL_REQUIRE_CONFIRMATION": "true"
178
+ }
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ ## Tool Examples
185
+
186
+ SELECT:
187
+
188
+ ```json
189
+ {
190
+ "sql": "SELECT id, email FROM users WHERE status = ? LIMIT 10",
191
+ "params": ["active"]
192
+ }
193
+ ```
194
+
195
+ EXPLAIN:
196
+
197
+ ```json
198
+ {
199
+ "sql": "SELECT id, email FROM users WHERE status = ? LIMIT 10",
200
+ "params": ["active"]
201
+ }
202
+ ```
203
+
204
+ Insert one row:
205
+
206
+ ```json
207
+ {
208
+ "schema": "your_database",
209
+ "table": "users",
210
+ "data": {
211
+ "email": "a@example.com",
212
+ "status": "active"
213
+ },
214
+ "confirm": true
215
+ }
216
+ ```
217
+
218
+ Insert multiple rows:
219
+
220
+ ```json
221
+ {
222
+ "schema": "your_database",
223
+ "table": "users",
224
+ "rows": [
225
+ { "email": "a@example.com", "status": "active" },
226
+ { "email": "b@example.com", "status": "pending" }
227
+ ],
228
+ "confirm": true
229
+ }
230
+ ```
231
+
232
+ Add a column:
233
+
234
+ ```json
235
+ {
236
+ "schema": "your_database",
237
+ "table": "users",
238
+ "columnName": "nickname",
239
+ "columnType": "VARCHAR(255)",
240
+ "nullable": true,
241
+ "comment": "Display name",
242
+ "afterColumn": "email",
243
+ "confirm": true
244
+ }
245
+ ```
246
+
247
+ Modify a column:
248
+
249
+ ```json
250
+ {
251
+ "schema": "your_database",
252
+ "table": "users",
253
+ "columnName": "nickname",
254
+ "columnType": "VARCHAR(128)",
255
+ "nullable": true,
256
+ "comment": "Short display name",
257
+ "confirm": true
258
+ }
259
+ ```
260
+
261
+ Drop a column:
262
+
263
+ ```json
264
+ {
265
+ "schema": "your_database",
266
+ "table": "users",
267
+ "columnName": "nickname",
268
+ "confirm": true
269
+ }
270
+ ```
271
+
272
+ Rename a column:
273
+
274
+ ```json
275
+ {
276
+ "schema": "your_database",
277
+ "table": "users",
278
+ "oldColumnName": "nickname",
279
+ "newColumnName": "display_name",
280
+ "confirm": true
281
+ }
282
+ ```
283
+
284
+ Add an index:
285
+
286
+ ```json
287
+ {
288
+ "schema": "your_database",
289
+ "table": "users",
290
+ "indexName": "idx_users_status",
291
+ "columns": ["status"],
292
+ "unique": false,
293
+ "confirm": true
294
+ }
295
+ ```
296
+
297
+ Drop an index:
298
+
299
+ ```json
300
+ {
301
+ "schema": "your_database",
302
+ "table": "users",
303
+ "indexName": "idx_users_status",
304
+ "confirm": true
305
+ }
306
+ ```
307
+
308
+ ## MCP Inspector
309
+
310
+ Build first, then run the MCP Inspector:
311
+
312
+ ```bash
313
+ npm run build
314
+ npx @modelcontextprotocol/inspector node dist/index.js
315
+ ```
316
+
317
+ Set MySQL environment variables in the Inspector process before testing tools.
318
+
319
+ ## MySQL Account Recommendations
320
+
321
+ Do not use `root` in production.
322
+
323
+ Read-only user:
324
+
325
+ ```sql
326
+ CREATE USER 'mcp_readonly'@'%' IDENTIFIED BY 'your_password';
327
+ GRANT SELECT ON your_database.* TO 'mcp_readonly'@'%';
328
+ FLUSH PRIVILEGES;
329
+ ```
330
+
331
+ Insert user:
332
+
333
+ ```sql
334
+ CREATE USER 'mcp_writer'@'%' IDENTIFIED BY 'your_password';
335
+ GRANT SELECT, INSERT ON your_database.* TO 'mcp_writer'@'%';
336
+ FLUSH PRIVILEGES;
337
+ ```
338
+
339
+ DDL user:
340
+
341
+ ```sql
342
+ CREATE USER 'mcp_ddl'@'%' IDENTIFIED BY 'your_password';
343
+ GRANT SELECT, ALTER, INDEX ON your_database.* TO 'mcp_ddl'@'%';
344
+ FLUSH PRIVILEGES;
345
+ ```
346
+
347
+ Development user:
348
+
349
+ ```sql
350
+ CREATE USER 'mcp_dev'@'%' IDENTIFIED BY 'your_password';
351
+ GRANT SELECT, INSERT, ALTER, INDEX ON your_database.* TO 'mcp_dev'@'%';
352
+ FLUSH PRIVILEGES;
353
+ ```
354
+
355
+ The MCP Server does not execute authorization SQL. A DBA or developer should run those SQL statements manually.
356
+
357
+ ## Safety Notes
358
+
359
+ - Keep the default read-only mode for production unless there is a clear operational need.
360
+ - Prefer separate database accounts for read-only, insert, and DDL use cases.
361
+ - Use `MYSQL_WRITE_ALLOWED_SCHEMAS` and `MYSQL_WRITE_ALLOWED_TABLES` to narrow write scope.
362
+ - Use `MYSQL_DDL_ALLOWED_SCHEMAS` and `MYSQL_DDL_ALLOWED_TABLES` to narrow DDL scope.
363
+ - DDL should be enabled only in development or tightly controlled environments.
364
+ - MySQL DDL commonly causes implicit commits and cannot be treated as safely rollbackable.
365
+ - `confirm: true` exists to make write and DDL calls explicit at the tool boundary.
366
+ - Passwords and full connection strings are never written to logs by this server.
367
+
368
+ ## Publish To npm
369
+
370
+ ```bash
371
+ npm login
372
+ npm run build
373
+ npm publish --access public
374
+ ```
@@ -0,0 +1,26 @@
1
+ export declare const config: {
2
+ readonly mysql: {
3
+ readonly host: string;
4
+ readonly port: number;
5
+ readonly user: string;
6
+ readonly password: string;
7
+ readonly database: string | undefined;
8
+ readonly ssl: boolean;
9
+ readonly connectionLimit: number;
10
+ readonly maxRows: number;
11
+ };
12
+ readonly security: {
13
+ readonly enableWrite: boolean;
14
+ readonly enableDDL: boolean;
15
+ readonly writeAllowedSchemas: string[];
16
+ readonly writeAllowedTables: string[];
17
+ readonly ddlAllowedSchemas: string[];
18
+ readonly ddlAllowedTables: string[];
19
+ readonly blockedSchemas: readonly ["mysql", "information_schema", "performance_schema", "sys"];
20
+ readonly blockedTables: string[];
21
+ readonly maxInsertRows: number;
22
+ readonly requireConfirmation: boolean;
23
+ };
24
+ };
25
+ export type AppConfig = typeof config;
26
+ export declare function defaultSchema(): string;
package/dist/config.js ADDED
@@ -0,0 +1,83 @@
1
+ import { z } from "zod";
2
+ function parseBoolean(value, defaultValue) {
3
+ if (value === undefined || value === "") {
4
+ return defaultValue;
5
+ }
6
+ if (/^(true|1|yes|on)$/i.test(value)) {
7
+ return true;
8
+ }
9
+ if (/^(false|0|no|off)$/i.test(value)) {
10
+ return false;
11
+ }
12
+ throw new Error(`Invalid boolean value: ${value}`);
13
+ }
14
+ function parseInteger(value, defaultValue, min, max) {
15
+ if (value === undefined || value === "") {
16
+ return defaultValue;
17
+ }
18
+ const parsed = Number(value);
19
+ if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
20
+ throw new Error(`Invalid integer value: ${value}. Expected ${min}-${max}.`);
21
+ }
22
+ return parsed;
23
+ }
24
+ function parseCsv(value) {
25
+ if (!value) {
26
+ return [];
27
+ }
28
+ return value
29
+ .split(",")
30
+ .map((item) => item.trim())
31
+ .filter((item) => item.length > 0);
32
+ }
33
+ const envSchema = z.object({
34
+ MYSQL_HOST: z.string().default("127.0.0.1"),
35
+ MYSQL_PORT: z.string().optional(),
36
+ MYSQL_USER: z.string().default("root"),
37
+ MYSQL_PASSWORD: z.string().optional(),
38
+ MYSQL_DATABASE: z.string().optional(),
39
+ MYSQL_SSL: z.string().optional(),
40
+ MYSQL_CONNECTION_LIMIT: z.string().optional(),
41
+ MYSQL_MAX_ROWS: z.string().optional(),
42
+ MYSQL_ENABLE_WRITE: z.string().optional(),
43
+ MYSQL_ENABLE_DDL: z.string().optional(),
44
+ MYSQL_WRITE_ALLOWED_SCHEMAS: z.string().optional(),
45
+ MYSQL_WRITE_ALLOWED_TABLES: z.string().optional(),
46
+ MYSQL_DDL_ALLOWED_SCHEMAS: z.string().optional(),
47
+ MYSQL_DDL_ALLOWED_TABLES: z.string().optional(),
48
+ MYSQL_BLOCKED_TABLES: z.string().optional(),
49
+ MYSQL_MAX_INSERT_ROWS: z.string().optional(),
50
+ MYSQL_REQUIRE_CONFIRMATION: z.string().optional()
51
+ });
52
+ const env = envSchema.parse(process.env);
53
+ export const config = {
54
+ mysql: {
55
+ host: env.MYSQL_HOST,
56
+ port: parseInteger(env.MYSQL_PORT, 3306, 1, 65535),
57
+ user: env.MYSQL_USER,
58
+ password: env.MYSQL_PASSWORD ?? "",
59
+ database: env.MYSQL_DATABASE,
60
+ ssl: parseBoolean(env.MYSQL_SSL, false),
61
+ connectionLimit: parseInteger(env.MYSQL_CONNECTION_LIMIT, 10, 1, 100),
62
+ maxRows: parseInteger(env.MYSQL_MAX_ROWS, 200, 1, 10000)
63
+ },
64
+ security: {
65
+ enableWrite: parseBoolean(env.MYSQL_ENABLE_WRITE, false),
66
+ enableDDL: parseBoolean(env.MYSQL_ENABLE_DDL, false),
67
+ writeAllowedSchemas: parseCsv(env.MYSQL_WRITE_ALLOWED_SCHEMAS),
68
+ writeAllowedTables: parseCsv(env.MYSQL_WRITE_ALLOWED_TABLES),
69
+ ddlAllowedSchemas: parseCsv(env.MYSQL_DDL_ALLOWED_SCHEMAS),
70
+ ddlAllowedTables: parseCsv(env.MYSQL_DDL_ALLOWED_TABLES),
71
+ blockedSchemas: ["mysql", "information_schema", "performance_schema", "sys"],
72
+ blockedTables: parseCsv(env.MYSQL_BLOCKED_TABLES),
73
+ maxInsertRows: parseInteger(env.MYSQL_MAX_INSERT_ROWS, 100, 1, 10000),
74
+ requireConfirmation: parseBoolean(env.MYSQL_REQUIRE_CONFIRMATION, true)
75
+ }
76
+ };
77
+ export function defaultSchema() {
78
+ if (!config.mysql.database) {
79
+ throw new Error("schema is required when MYSQL_DATABASE is not configured.");
80
+ }
81
+ return config.mysql.database;
82
+ }
83
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { ResultSetHeader } from "mysql2";
2
+ export type QueryValue = string | number | boolean | null | Date;
3
+ export type QueryParams = QueryValue[];
4
+ export type DbRow = Record<string, unknown>;
5
+ export declare class MySqlClient {
6
+ queryRows(sql: string, params?: QueryParams): Promise<DbRow[]>;
7
+ execute(sql: string, params?: QueryParams): Promise<ResultSetHeader>;
8
+ listSchemas(): Promise<DbRow[]>;
9
+ listTables(schema: string): Promise<DbRow[]>;
10
+ describeTable(schema: string, table: string): Promise<DbRow[]>;
11
+ columnExists(schema: string, table: string, column: string): Promise<boolean>;
12
+ columnsExist(schema: string, table: string, columns: readonly string[]): Promise<boolean>;
13
+ isPrimaryKeyColumn(schema: string, table: string, column: string): Promise<boolean>;
14
+ isUniqueIndexColumn(schema: string, table: string, column: string): Promise<boolean>;
15
+ indexExists(schema: string, table: string, indexName: string): Promise<boolean>;
16
+ }
@@ -0,0 +1,115 @@
1
+ import { config } from "../config.js";
2
+ import { getPool } from "./MySqlPool.js";
3
+ function convertRows(rows) {
4
+ if (Array.isArray(rows) && rows.every((row) => !Array.isArray(row))) {
5
+ return rows;
6
+ }
7
+ return [];
8
+ }
9
+ export class MySqlClient {
10
+ async queryRows(sql, params = []) {
11
+ const [rows] = await getPool().execute(sql, params);
12
+ return convertRows(rows).slice(0, config.mysql.maxRows);
13
+ }
14
+ async execute(sql, params = []) {
15
+ const [result] = await getPool().execute(sql, params);
16
+ return result;
17
+ }
18
+ async listSchemas() {
19
+ return this.queryRows(`
20
+ SELECT SCHEMA_NAME AS schemaName
21
+ FROM INFORMATION_SCHEMA.SCHEMATA
22
+ WHERE SCHEMA_NAME NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
23
+ ORDER BY SCHEMA_NAME
24
+ `);
25
+ }
26
+ async listTables(schema) {
27
+ return this.queryRows(`
28
+ SELECT TABLE_SCHEMA AS schemaName,
29
+ TABLE_NAME AS tableName,
30
+ TABLE_TYPE AS tableType,
31
+ ENGINE AS engine,
32
+ TABLE_ROWS AS estimatedRows,
33
+ CREATE_TIME AS createdAt,
34
+ UPDATE_TIME AS updatedAt,
35
+ TABLE_COMMENT AS tableComment
36
+ FROM INFORMATION_SCHEMA.TABLES
37
+ WHERE TABLE_SCHEMA = ?
38
+ ORDER BY TABLE_NAME
39
+ `, [schema]);
40
+ }
41
+ async describeTable(schema, table) {
42
+ return this.queryRows(`
43
+ SELECT COLUMN_NAME AS columnName,
44
+ ORDINAL_POSITION AS ordinalPosition,
45
+ COLUMN_DEFAULT AS columnDefault,
46
+ IS_NULLABLE AS isNullable,
47
+ DATA_TYPE AS dataType,
48
+ COLUMN_TYPE AS columnType,
49
+ CHARACTER_MAXIMUM_LENGTH AS characterMaximumLength,
50
+ NUMERIC_PRECISION AS numericPrecision,
51
+ NUMERIC_SCALE AS numericScale,
52
+ COLUMN_KEY AS columnKey,
53
+ EXTRA AS extra,
54
+ COLUMN_COMMENT AS columnComment
55
+ FROM INFORMATION_SCHEMA.COLUMNS
56
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
57
+ ORDER BY ORDINAL_POSITION
58
+ `, [schema, table]);
59
+ }
60
+ async columnExists(schema, table, column) {
61
+ const rows = await this.queryRows(`
62
+ SELECT 1 AS ok
63
+ FROM INFORMATION_SCHEMA.COLUMNS
64
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?
65
+ LIMIT 1
66
+ `, [schema, table, column]);
67
+ return rows.length > 0;
68
+ }
69
+ async columnsExist(schema, table, columns) {
70
+ if (columns.length === 0) {
71
+ return false;
72
+ }
73
+ const placeholders = columns.map(() => "?").join(", ");
74
+ const rows = await this.queryRows(`
75
+ SELECT COLUMN_NAME AS columnName
76
+ FROM INFORMATION_SCHEMA.COLUMNS
77
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME IN (${placeholders})
78
+ `, [schema, table, ...columns]);
79
+ return new Set(rows.map((row) => String(row.columnName))).size === new Set(columns).size;
80
+ }
81
+ async isPrimaryKeyColumn(schema, table, column) {
82
+ const rows = await this.queryRows(`
83
+ SELECT 1 AS ok
84
+ FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
85
+ WHERE TABLE_SCHEMA = ?
86
+ AND TABLE_NAME = ?
87
+ AND COLUMN_NAME = ?
88
+ AND CONSTRAINT_NAME = 'PRIMARY'
89
+ LIMIT 1
90
+ `, [schema, table, column]);
91
+ return rows.length > 0;
92
+ }
93
+ async isUniqueIndexColumn(schema, table, column) {
94
+ const rows = await this.queryRows(`
95
+ SELECT 1 AS ok
96
+ FROM INFORMATION_SCHEMA.STATISTICS
97
+ WHERE TABLE_SCHEMA = ?
98
+ AND TABLE_NAME = ?
99
+ AND COLUMN_NAME = ?
100
+ AND NON_UNIQUE = 0
101
+ LIMIT 1
102
+ `, [schema, table, column]);
103
+ return rows.length > 0;
104
+ }
105
+ async indexExists(schema, table, indexName) {
106
+ const rows = await this.queryRows(`
107
+ SELECT 1 AS ok
108
+ FROM INFORMATION_SCHEMA.STATISTICS
109
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND INDEX_NAME = ?
110
+ LIMIT 1
111
+ `, [schema, table, indexName]);
112
+ return rows.length > 0;
113
+ }
114
+ }
115
+ //# sourceMappingURL=MySqlClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MySqlClient.js","sourceRoot":"","sources":["../../src/db/MySqlClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAMzC,SAAS,WAAW,CAAC,IAAyC;IAC5D,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACpE,OAAO,IAAe,CAAC;IACzB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,OAAO,WAAW;IACtB,KAAK,CAAC,SAAS,CAAC,GAAW,EAAE,SAAsB,EAAE;QACnD,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,OAAO,EAAE,CAAC,OAAO,CAAkB,GAAG,EAAE,MAAM,CAAC,CAAC;QACrE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1D,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW,EAAE,SAAsB,EAAE;QACjD,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,OAAO,EAAE,CAAC,OAAO,CAAkB,GAAG,EAAE,MAAM,CAAC,CAAC;QACvE,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,WAAW;QACf,OAAO,IAAI,CAAC,SAAS,CACnB;;;;;OAKC,CACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAc;QAC7B,OAAO,IAAI,CAAC,SAAS,CACnB;;;;;;;;;;;;OAYC,EACD,CAAC,MAAM,CAAC,CACT,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MAAc,EAAE,KAAa;QAC/C,OAAO,IAAI,CAAC,SAAS,CACnB;;;;;;;;;;;;;;;;OAgBC,EACD,CAAC,MAAM,EAAE,KAAK,CAAC,CAChB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAc,EAAE,KAAa,EAAE,MAAc;QAC9D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAC/B;;;;;OAKC,EACD,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CACxB,CAAC;QACF,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAc,EAAE,KAAa,EAAE,OAA0B;QAC1E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAC/B;;;sEAGgE,YAAY;OAC3E,EACD,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,CAC5B,CAAC;QAEF,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;IAC3F,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,MAAc,EAAE,KAAa,EAAE,MAAc;QACpE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAC/B;;;;;;;;OAQC,EACD,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CACxB,CAAC;QACF,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,MAAc,EAAE,KAAa,EAAE,MAAc;QACrE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAC/B;;;;;;;;OAQC,EACD,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CACxB,CAAC;QACF,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAc,EAAE,KAAa,EAAE,SAAiB;QAChE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAC/B;;;;;OAKC,EACD,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,CAC3B,CAAC;QACF,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IACzB,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ import { type Pool } from "mysql2/promise";
2
+ export declare function getPool(): Pool;
3
+ export declare function closePool(): Promise<void>;
@@ -0,0 +1,36 @@
1
+ import mysql from "mysql2/promise";
2
+ import { config } from "../config.js";
3
+ import { logger } from "../logger.js";
4
+ let pool;
5
+ export function getPool() {
6
+ if (!pool) {
7
+ pool = mysql.createPool({
8
+ host: config.mysql.host,
9
+ port: config.mysql.port,
10
+ user: config.mysql.user,
11
+ password: config.mysql.password,
12
+ database: config.mysql.database,
13
+ waitForConnections: true,
14
+ connectionLimit: config.mysql.connectionLimit,
15
+ multipleStatements: false,
16
+ ssl: config.mysql.ssl ? {} : undefined,
17
+ namedPlaceholders: false
18
+ });
19
+ logger.info("MySQL pool created", {
20
+ host: config.mysql.host,
21
+ port: config.mysql.port,
22
+ user: config.mysql.user,
23
+ database: config.mysql.database,
24
+ connectionLimit: config.mysql.connectionLimit
25
+ });
26
+ }
27
+ return pool;
28
+ }
29
+ export async function closePool() {
30
+ if (pool) {
31
+ await pool.end();
32
+ pool = undefined;
33
+ logger.info("MySQL pool closed");
34
+ }
35
+ }
36
+ //# sourceMappingURL=MySqlPool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MySqlPool.js","sourceRoot":"","sources":["../../src/db/MySqlPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,IAAI,IAAsB,CAAC;AAE3B,MAAM,UAAU,OAAO;IACrB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC;YACtB,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI;YACvB,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI;YACvB,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI;YACvB,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ;YAC/B,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ;YAC/B,kBAAkB,EAAE,IAAI;YACxB,eAAe,EAAE,MAAM,CAAC,KAAK,CAAC,eAAe;YAC7C,kBAAkB,EAAE,KAAK;YACzB,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;YACtC,iBAAiB,EAAE,KAAK;SACzB,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE;YAChC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI;YACvB,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI;YACvB,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI;YACvB,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ;YAC/B,eAAe,EAAE,MAAM,CAAC,KAAK,CAAC,eAAe;SAC9C,CAAC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACjB,IAAI,GAAG,SAAS,CAAC;QACjB,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACnC,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};