@sigma4life/mysql-mcp-server 1.0.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 (149) hide show
  1. package/.env.example +35 -0
  2. package/.github/workflows/ci.yml +18 -0
  3. package/AUTHENTICATION.md +24 -0
  4. package/CLAUDE.md +37 -0
  5. package/CONTRIBUTING.md +19 -0
  6. package/LICENSE +21 -0
  7. package/QUERY_ACCESS_SETUP.md +65 -0
  8. package/README.md +144 -0
  9. package/SECURITY.md +10 -0
  10. package/TESTING.md +54 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +197 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/core/cache.d.ts +11 -0
  16. package/dist/core/cache.d.ts.map +1 -0
  17. package/dist/core/cache.js +30 -0
  18. package/dist/core/cache.js.map +1 -0
  19. package/dist/core/config.d.ts +24 -0
  20. package/dist/core/config.d.ts.map +1 -0
  21. package/dist/core/config.js +63 -0
  22. package/dist/core/config.js.map +1 -0
  23. package/dist/core/database-url.d.ts +11 -0
  24. package/dist/core/database-url.d.ts.map +1 -0
  25. package/dist/core/database-url.js +37 -0
  26. package/dist/core/database-url.js.map +1 -0
  27. package/dist/core/database-url.test.d.ts +2 -0
  28. package/dist/core/database-url.test.d.ts.map +1 -0
  29. package/dist/core/database-url.test.js +22 -0
  30. package/dist/core/database-url.test.js.map +1 -0
  31. package/dist/core/logger.d.ts +3 -0
  32. package/dist/core/logger.d.ts.map +1 -0
  33. package/dist/core/logger.js +17 -0
  34. package/dist/core/logger.js.map +1 -0
  35. package/dist/core/mcp.d.ts +14 -0
  36. package/dist/core/mcp.d.ts.map +1 -0
  37. package/dist/core/mcp.js +23 -0
  38. package/dist/core/mcp.js.map +1 -0
  39. package/dist/core/query-safety.d.ts +10 -0
  40. package/dist/core/query-safety.d.ts.map +1 -0
  41. package/dist/core/query-safety.js +50 -0
  42. package/dist/core/query-safety.js.map +1 -0
  43. package/dist/core/query-safety.test.d.ts +2 -0
  44. package/dist/core/query-safety.test.d.ts.map +1 -0
  45. package/dist/core/query-safety.test.js +30 -0
  46. package/dist/core/query-safety.test.js.map +1 -0
  47. package/dist/core/schema-types.d.ts +53 -0
  48. package/dist/core/schema-types.d.ts.map +1 -0
  49. package/dist/core/schema-types.js +2 -0
  50. package/dist/core/schema-types.js.map +1 -0
  51. package/dist/core/security/access-control.d.ts +32 -0
  52. package/dist/core/security/access-control.d.ts.map +1 -0
  53. package/dist/core/security/access-control.js +244 -0
  54. package/dist/core/security/access-control.js.map +1 -0
  55. package/dist/core/security/config-loader.d.ts +20 -0
  56. package/dist/core/security/config-loader.d.ts.map +1 -0
  57. package/dist/core/security/config-loader.js +227 -0
  58. package/dist/core/security/config-loader.js.map +1 -0
  59. package/dist/core/security/types.d.ts +64 -0
  60. package/dist/core/security/types.d.ts.map +1 -0
  61. package/dist/core/security/types.js +28 -0
  62. package/dist/core/security/types.js.map +1 -0
  63. package/dist/core/sql-parser.d.ts +23 -0
  64. package/dist/core/sql-parser.d.ts.map +1 -0
  65. package/dist/core/sql-parser.js +460 -0
  66. package/dist/core/sql-parser.js.map +1 -0
  67. package/dist/core/sql-parser.test.d.ts +2 -0
  68. package/dist/core/sql-parser.test.d.ts.map +1 -0
  69. package/dist/core/sql-parser.test.js +21 -0
  70. package/dist/core/sql-parser.test.js.map +1 -0
  71. package/dist/handlers/accessible-schema.d.ts +53 -0
  72. package/dist/handlers/accessible-schema.d.ts.map +1 -0
  73. package/dist/handlers/accessible-schema.js +192 -0
  74. package/dist/handlers/accessible-schema.js.map +1 -0
  75. package/dist/handlers/data.d.ts +17 -0
  76. package/dist/handlers/data.d.ts.map +1 -0
  77. package/dist/handlers/data.js +30 -0
  78. package/dist/handlers/data.js.map +1 -0
  79. package/dist/handlers/relationships.d.ts +14 -0
  80. package/dist/handlers/relationships.d.ts.map +1 -0
  81. package/dist/handlers/relationships.js +77 -0
  82. package/dist/handlers/relationships.js.map +1 -0
  83. package/dist/handlers/schema.d.ts +14 -0
  84. package/dist/handlers/schema.d.ts.map +1 -0
  85. package/dist/handlers/schema.js +104 -0
  86. package/dist/handlers/schema.js.map +1 -0
  87. package/dist/handlers/search.d.ts +14 -0
  88. package/dist/handlers/search.d.ts.map +1 -0
  89. package/dist/handlers/search.js +18 -0
  90. package/dist/handlers/search.js.map +1 -0
  91. package/dist/handlers/validation.d.ts +32 -0
  92. package/dist/handlers/validation.d.ts.map +1 -0
  93. package/dist/handlers/validation.js +116 -0
  94. package/dist/handlers/validation.js.map +1 -0
  95. package/dist/index.d.ts +3 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +294 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/mysql/connection.d.ts +15 -0
  100. package/dist/mysql/connection.d.ts.map +1 -0
  101. package/dist/mysql/connection.js +57 -0
  102. package/dist/mysql/connection.js.map +1 -0
  103. package/dist/mysql/identifiers.d.ts +3 -0
  104. package/dist/mysql/identifiers.d.ts.map +1 -0
  105. package/dist/mysql/identifiers.js +10 -0
  106. package/dist/mysql/identifiers.js.map +1 -0
  107. package/dist/mysql/identifiers.test.d.ts +2 -0
  108. package/dist/mysql/identifiers.test.d.ts.map +1 -0
  109. package/dist/mysql/identifiers.test.js +11 -0
  110. package/dist/mysql/identifiers.test.js.map +1 -0
  111. package/dist/mysql/queries.d.ts +49 -0
  112. package/dist/mysql/queries.d.ts.map +1 -0
  113. package/dist/mysql/queries.js +206 -0
  114. package/dist/mysql/queries.js.map +1 -0
  115. package/docs/clients/claude-code.md +28 -0
  116. package/docs/clients/claude-desktop.md +35 -0
  117. package/docs/clients/codex.md +28 -0
  118. package/docs/clients/cursor.md +26 -0
  119. package/docs/clients/opencode.md +35 -0
  120. package/docs/clients/vscode.md +25 -0
  121. package/package.json +49 -0
  122. package/query-access.example.json +21 -0
  123. package/src/cli.ts +221 -0
  124. package/src/core/cache.ts +41 -0
  125. package/src/core/config.ts +97 -0
  126. package/src/core/database-url.test.ts +28 -0
  127. package/src/core/database-url.ts +47 -0
  128. package/src/core/logger.ts +24 -0
  129. package/src/core/mcp.ts +23 -0
  130. package/src/core/query-safety.test.ts +36 -0
  131. package/src/core/query-safety.ts +63 -0
  132. package/src/core/schema-types.ts +58 -0
  133. package/src/core/security/access-control.ts +321 -0
  134. package/src/core/security/config-loader.ts +315 -0
  135. package/src/core/security/types.ts +114 -0
  136. package/src/core/sql-parser.test.ts +37 -0
  137. package/src/core/sql-parser.ts +572 -0
  138. package/src/handlers/accessible-schema.ts +314 -0
  139. package/src/handlers/data.ts +66 -0
  140. package/src/handlers/relationships.ts +114 -0
  141. package/src/handlers/schema.ts +154 -0
  142. package/src/handlers/search.ts +34 -0
  143. package/src/handlers/validation.ts +165 -0
  144. package/src/index.ts +337 -0
  145. package/src/mysql/connection.ts +68 -0
  146. package/src/mysql/identifiers.test.ts +12 -0
  147. package/src/mysql/identifiers.ts +10 -0
  148. package/src/mysql/queries.ts +285 -0
  149. package/tsconfig.json +24 -0
@@ -0,0 +1,285 @@
1
+ import { appConfig } from '../core/config.js';
2
+ import {
3
+ ColumnMetadata,
4
+ ForeignKeyMetadata,
5
+ IndexMetadata,
6
+ PrimaryKeyMetadata,
7
+ StatisticsMetadata,
8
+ } from '../core/schema-types.js';
9
+ import { db } from './connection.js';
10
+ import { likePattern } from './identifiers.js';
11
+
12
+ interface TableRow {
13
+ schemaName: string;
14
+ tableName: string;
15
+ tableType: 'BASE TABLE' | 'VIEW';
16
+ createDate: Date | null;
17
+ rowCount: number | null;
18
+ }
19
+
20
+ export interface TableSearchResult {
21
+ schemaName: string;
22
+ tableName: string;
23
+ rowCount?: number;
24
+ createDate?: Date | null;
25
+ }
26
+
27
+ export interface ObjectSearchResult {
28
+ schemaName: string;
29
+ tableName?: string;
30
+ columnName?: string;
31
+ }
32
+
33
+ export interface Relationship {
34
+ fromSchema: string;
35
+ fromTable: string;
36
+ fromColumn: string;
37
+ toSchema: string;
38
+ toTable: string;
39
+ toColumn: string;
40
+ constraintName: string;
41
+ deleteAction: string;
42
+ updateAction: string;
43
+ }
44
+
45
+ export async function listTables(tableNames?: string[]): Promise<TableRow[]> {
46
+ const tableFilter = tableNames?.length
47
+ ? 'AND t.TABLE_NAME IN (:tableNames)'
48
+ : '';
49
+ return db.query<TableRow & any>(
50
+ `
51
+ SELECT
52
+ t.TABLE_SCHEMA AS schemaName,
53
+ t.TABLE_NAME AS tableName,
54
+ t.TABLE_TYPE AS tableType,
55
+ t.CREATE_TIME AS createDate,
56
+ t.TABLE_ROWS AS rowCount
57
+ FROM information_schema.TABLES t
58
+ WHERE t.TABLE_SCHEMA = :database
59
+ AND t.TABLE_TYPE IN ('BASE TABLE', 'VIEW')
60
+ ${tableFilter}
61
+ ORDER BY t.TABLE_NAME
62
+ `,
63
+ { database: appConfig.db.name, tableNames },
64
+ );
65
+ }
66
+
67
+ export async function getColumns(tableNames?: string[]): Promise<ColumnMetadata[]> {
68
+ const tableFilter = tableNames?.length
69
+ ? 'AND c.TABLE_NAME IN (:tableNames)'
70
+ : '';
71
+ return db.query<ColumnMetadata & any>(
72
+ `
73
+ SELECT
74
+ c.COLUMN_NAME AS name,
75
+ c.ORDINAL_POSITION AS ordinal,
76
+ c.COLUMN_TYPE AS dataType,
77
+ CASE WHEN c.IS_NULLABLE = 'YES' THEN true ELSE false END AS nullable,
78
+ CASE WHEN c.EXTRA LIKE '%auto_increment%' THEN true ELSE false END AS isIdentity,
79
+ CASE WHEN c.EXTRA LIKE '%VIRTUAL GENERATED%' OR c.EXTRA LIKE '%STORED GENERATED%' THEN true ELSE false END AS isComputed,
80
+ c.COLUMN_DEFAULT AS defaultValue,
81
+ c.COLUMN_COMMENT AS description,
82
+ CASE WHEN kcu.CONSTRAINT_NAME = 'PRIMARY' THEN true ELSE false END AS isPrimaryKey,
83
+ CASE WHEN fk.CONSTRAINT_NAME IS NOT NULL THEN true ELSE false END AS isForeignKey,
84
+ c.TABLE_SCHEMA AS \`schema\`,
85
+ c.TABLE_NAME AS tableName
86
+ FROM information_schema.COLUMNS c
87
+ LEFT JOIN information_schema.KEY_COLUMN_USAGE kcu
88
+ ON kcu.TABLE_SCHEMA = c.TABLE_SCHEMA
89
+ AND kcu.TABLE_NAME = c.TABLE_NAME
90
+ AND kcu.COLUMN_NAME = c.COLUMN_NAME
91
+ AND kcu.CONSTRAINT_NAME = 'PRIMARY'
92
+ LEFT JOIN information_schema.KEY_COLUMN_USAGE fk
93
+ ON fk.TABLE_SCHEMA = c.TABLE_SCHEMA
94
+ AND fk.TABLE_NAME = c.TABLE_NAME
95
+ AND fk.COLUMN_NAME = c.COLUMN_NAME
96
+ AND fk.REFERENCED_TABLE_NAME IS NOT NULL
97
+ WHERE c.TABLE_SCHEMA = :database
98
+ ${tableFilter}
99
+ ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION
100
+ `,
101
+ { database: appConfig.db.name, tableNames },
102
+ );
103
+ }
104
+
105
+ export async function getPrimaryKeys(tableNames?: string[]): Promise<(PrimaryKeyMetadata & { tableName: string })[]> {
106
+ const tableFilter = tableNames?.length
107
+ ? 'AND kcu.TABLE_NAME IN (:tableNames)'
108
+ : '';
109
+ return db.query<PrimaryKeyMetadata & { tableName: string } & any>(
110
+ `
111
+ SELECT
112
+ kcu.TABLE_NAME AS tableName,
113
+ kcu.CONSTRAINT_NAME AS constraintName,
114
+ GROUP_CONCAT(kcu.COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION SEPARATOR ',') AS columns
115
+ FROM information_schema.KEY_COLUMN_USAGE kcu
116
+ WHERE kcu.TABLE_SCHEMA = :database
117
+ AND kcu.CONSTRAINT_NAME = 'PRIMARY'
118
+ ${tableFilter}
119
+ GROUP BY kcu.TABLE_NAME, kcu.CONSTRAINT_NAME
120
+ `,
121
+ { database: appConfig.db.name, tableNames },
122
+ );
123
+ }
124
+
125
+ export async function getForeignKeys(tableNames?: string[]): Promise<(ForeignKeyMetadata & { tableName: string })[]> {
126
+ const tableFilter = tableNames?.length
127
+ ? 'AND kcu.TABLE_NAME IN (:tableNames)'
128
+ : '';
129
+ return db.query<ForeignKeyMetadata & { tableName: string } & any>(
130
+ `
131
+ SELECT
132
+ kcu.TABLE_NAME AS tableName,
133
+ kcu.CONSTRAINT_NAME AS constraintName,
134
+ kcu.TABLE_SCHEMA AS fromSchema,
135
+ kcu.TABLE_NAME AS fromTable,
136
+ GROUP_CONCAT(kcu.COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION SEPARATOR ',') AS fromColumns,
137
+ kcu.REFERENCED_TABLE_SCHEMA AS toSchema,
138
+ kcu.REFERENCED_TABLE_NAME AS toTable,
139
+ GROUP_CONCAT(kcu.REFERENCED_COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION SEPARATOR ',') AS toColumns,
140
+ rc.DELETE_RULE AS onDelete,
141
+ rc.UPDATE_RULE AS onUpdate
142
+ FROM information_schema.KEY_COLUMN_USAGE kcu
143
+ JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
144
+ ON rc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
145
+ AND rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
146
+ WHERE kcu.TABLE_SCHEMA = :database
147
+ AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
148
+ ${tableFilter}
149
+ GROUP BY
150
+ kcu.TABLE_NAME,
151
+ kcu.CONSTRAINT_NAME,
152
+ kcu.TABLE_SCHEMA,
153
+ kcu.REFERENCED_TABLE_SCHEMA,
154
+ kcu.REFERENCED_TABLE_NAME,
155
+ rc.DELETE_RULE,
156
+ rc.UPDATE_RULE
157
+ `,
158
+ { database: appConfig.db.name, tableNames },
159
+ );
160
+ }
161
+
162
+ export async function getIndexes(tableNames?: string[]): Promise<(IndexMetadata & { tableName: string })[]> {
163
+ const tableFilter = tableNames?.length
164
+ ? 'AND s.TABLE_NAME IN (:tableNames)'
165
+ : '';
166
+ return db.query<IndexMetadata & { tableName: string } & any>(
167
+ `
168
+ SELECT
169
+ s.TABLE_NAME AS tableName,
170
+ s.INDEX_NAME AS name,
171
+ s.INDEX_TYPE AS type,
172
+ CASE WHEN s.NON_UNIQUE = 0 THEN true ELSE false END AS isUnique,
173
+ CASE WHEN s.INDEX_NAME = 'PRIMARY' THEN true ELSE false END AS isPrimaryKey,
174
+ GROUP_CONCAT(s.COLUMN_NAME ORDER BY s.SEQ_IN_INDEX SEPARATOR ',') AS columns
175
+ FROM information_schema.STATISTICS s
176
+ WHERE s.TABLE_SCHEMA = :database
177
+ ${tableFilter}
178
+ GROUP BY s.TABLE_NAME, s.INDEX_NAME, s.INDEX_TYPE, s.NON_UNIQUE
179
+ `,
180
+ { database: appConfig.db.name, tableNames },
181
+ );
182
+ }
183
+
184
+ export async function getStatistics(tableNames?: string[]): Promise<(StatisticsMetadata & { tableName: string })[]> {
185
+ const tableFilter = tableNames?.length
186
+ ? 'AND t.TABLE_NAME IN (:tableNames)'
187
+ : '';
188
+ return db.query<StatisticsMetadata & { tableName: string } & any>(
189
+ `
190
+ SELECT
191
+ t.TABLE_NAME AS tableName,
192
+ t.TABLE_ROWS AS rowCount,
193
+ ROUND(((t.DATA_LENGTH + t.INDEX_LENGTH) / 1024), 0) AS totalSizeKB,
194
+ ROUND((t.DATA_LENGTH / 1024), 0) AS usedSizeKB
195
+ FROM information_schema.TABLES t
196
+ WHERE t.TABLE_SCHEMA = :database
197
+ ${tableFilter}
198
+ `,
199
+ { database: appConfig.db.name, tableNames },
200
+ );
201
+ }
202
+
203
+ export async function findTables(pattern?: string, hasColumn?: string): Promise<TableSearchResult[]> {
204
+ const params = {
205
+ database: appConfig.db.name,
206
+ pattern: likePattern(pattern),
207
+ hasColumn: likePattern(hasColumn),
208
+ };
209
+ return db.query<TableSearchResult & any>(
210
+ `
211
+ SELECT DISTINCT
212
+ t.TABLE_SCHEMA AS schemaName,
213
+ t.TABLE_NAME AS tableName,
214
+ t.TABLE_ROWS AS rowCount,
215
+ t.CREATE_TIME AS createDate
216
+ FROM information_schema.TABLES t
217
+ ${hasColumn ? `JOIN information_schema.COLUMNS c
218
+ ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME` : ''}
219
+ WHERE t.TABLE_SCHEMA = :database
220
+ AND t.TABLE_TYPE IN ('BASE TABLE', 'VIEW')
221
+ ${pattern ? 'AND t.TABLE_NAME LIKE :pattern' : ''}
222
+ ${hasColumn ? 'AND c.COLUMN_NAME LIKE :hasColumn' : ''}
223
+ ORDER BY t.TABLE_NAME
224
+ LIMIT 100
225
+ `,
226
+ params,
227
+ );
228
+ }
229
+
230
+ export async function searchObjects(search: string, type?: string): Promise<ObjectSearchResult[]> {
231
+ const pattern = likePattern(search) || `%${search}%`;
232
+ const includeTables = !type || type === 'table';
233
+ const includeColumns = !type || type === 'column';
234
+ const parts: string[] = [];
235
+
236
+ if (includeTables) {
237
+ parts.push(`
238
+ SELECT t.TABLE_SCHEMA AS schemaName, t.TABLE_NAME AS tableName, NULL AS columnName
239
+ FROM information_schema.TABLES t
240
+ WHERE t.TABLE_SCHEMA = :database
241
+ AND t.TABLE_NAME LIKE :pattern`);
242
+ }
243
+
244
+ if (includeColumns) {
245
+ parts.push(`
246
+ SELECT c.TABLE_SCHEMA AS schemaName, c.TABLE_NAME AS tableName, c.COLUMN_NAME AS columnName
247
+ FROM information_schema.COLUMNS c
248
+ WHERE c.TABLE_SCHEMA = :database
249
+ AND c.COLUMN_NAME LIKE :pattern`);
250
+ }
251
+
252
+ if (parts.length === 0) {
253
+ return [];
254
+ }
255
+
256
+ return db.query<ObjectSearchResult & any>(
257
+ `${parts.join('\nUNION ALL\n')}\nORDER BY tableName, columnName\nLIMIT 100`,
258
+ { database: appConfig.db.name, pattern },
259
+ );
260
+ }
261
+
262
+ export async function getRelationships(): Promise<Relationship[]> {
263
+ return db.query<Relationship & any>(
264
+ `
265
+ SELECT
266
+ kcu.TABLE_SCHEMA AS fromSchema,
267
+ kcu.TABLE_NAME AS fromTable,
268
+ kcu.COLUMN_NAME AS fromColumn,
269
+ kcu.REFERENCED_TABLE_SCHEMA AS toSchema,
270
+ kcu.REFERENCED_TABLE_NAME AS toTable,
271
+ kcu.REFERENCED_COLUMN_NAME AS toColumn,
272
+ kcu.CONSTRAINT_NAME AS constraintName,
273
+ rc.DELETE_RULE AS deleteAction,
274
+ rc.UPDATE_RULE AS updateAction
275
+ FROM information_schema.KEY_COLUMN_USAGE kcu
276
+ JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
277
+ ON rc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
278
+ AND rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
279
+ WHERE kcu.TABLE_SCHEMA = :database
280
+ AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
281
+ ORDER BY kcu.TABLE_NAME, kcu.ORDINAL_POSITION
282
+ `,
283
+ { database: appConfig.db.name },
284
+ );
285
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "node",
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noImplicitReturns": true,
20
+ "noFallthroughCasesInSwitch": true
21
+ },
22
+ "include": ["src/**/*"],
23
+ "exclude": ["node_modules", "dist"]
24
+ }