@@ -7,9 +7,10 @@ import {
7
7
ListResourcesRequestSchema ,
8
8
ListToolsRequestSchema ,
9
9
ReadResourceRequestSchema ,
10
- } from '@modelcontextprotocol/sdk/types.js'
11
- import * as mysql2 from 'mysql2/promise'
12
- import * as dotenv from 'dotenv'
10
+ } from "@modelcontextprotocol/sdk/types.js" ;
11
+ import * as mysql2 from "mysql2/promise" ;
12
+ import * as dotenv from "dotenv" ;
13
+ import SqlParser , { AST } from 'node-sql-parser' ;
13
14
14
15
export interface TableRow {
15
16
table_name: string
@@ -278,6 +279,27 @@ const getServer = (): Promise<Server> => {
278
279
return serverInstance
279
280
}
280
281
282
+ const { Parser } = SqlParser ;
283
+ const parser = new Parser ( ) ;
284
+
285
+ async function getQueryTypes ( query : string ) : Promise < string [ ] > {
286
+ try {
287
+ console . log ( "Parsing SQL query: " , query ) ;
288
+ // Parse into AST or array of ASTs
289
+ const astOrArray : AST | AST [ ] = parser . astify ( query , { database : 'mysql' } ) ;
290
+ const statements = Array . isArray ( astOrArray ) ? astOrArray : [ astOrArray ] ;
291
+
292
+ console . log ( "Parsed SQL AST: " , statements . map ( stmt => stmt . type ?. toLowerCase ( ) ?? 'unknown' ) ) ;
293
+
294
+ // Map each statement to its lowercased type (e.g., 'select', 'update', 'insert', 'delete', etc.)
295
+ return statements . map ( stmt => stmt . type ?. toLowerCase ( ) ?? 'unknown' ) ;
296
+ } catch ( err : any ) {
297
+ console . error ( "sqlParser error, query: " , query ) ;
298
+ console . error ( 'Error parsing SQL query:' , err ) ;
299
+ throw new Error ( `Parsing failed: ${ err . message } ` ) ;
300
+ }
301
+ }
302
+
281
303
async function executeQuery < T > (
282
304
sql : string ,
283
305
params : string [ ] = [ ] ,
@@ -304,9 +326,17 @@ async function executeReadOnlyQuery<T>(sql: string): Promise<T> {
304
326
let connection
305
327
try {
306
328
// @INFO : Check if the query is a write operation
307
- const normalizedSql = sql . trim ( ) . toUpperCase ( )
329
+ const normalizedSql = sql . trim ( ) . toUpperCase ( ) ;
330
+
331
+ // Check the type of query
332
+ // possible types: "replace" | "update" | "insert" | "delete" | "use" | "select" | "alter" | "create" | "drop"
333
+ const queryTypes = await getQueryTypes ( normalizedSql ) ;
334
+ const isUpdateOperation = queryTypes . some ( type => [ 'update' ] . includes ( type ) ) ;
335
+ const isInsertOperation = queryTypes . some ( type => [ 'insert' ] . includes ( type ) ) ;
336
+ const isDeleteOperation = queryTypes . some ( type => [ 'delete' ] . includes ( type ) ) ;
337
+
308
338
309
- if ( normalizedSql . startsWith ( 'INSERT' ) && ! ALLOW_INSERT_OPERATION ) {
339
+ if ( isInsertOperation && ! ALLOW_INSERT_OPERATION ) {
310
340
console . error (
311
341
'INSERT operations are not allowed. Set ALLOW_INSERT_OPERATION=true to enable.' ,
312
342
)
@@ -321,7 +351,7 @@ async function executeReadOnlyQuery<T>(sql: string): Promise<T> {
321
351
} as T
322
352
}
323
353
324
- if ( normalizedSql . startsWith ( 'UPDATE' ) && ! ALLOW_UPDATE_OPERATION ) {
354
+ if ( isUpdateOperation && ! ALLOW_UPDATE_OPERATION ) {
325
355
console . error (
326
356
'UPDATE operations are not allowed. Set ALLOW_UPDATE_OPERATION=true to enable.' ,
327
357
)
@@ -336,7 +366,7 @@ async function executeReadOnlyQuery<T>(sql: string): Promise<T> {
336
366
} as T
337
367
}
338
368
339
- if ( normalizedSql . startsWith ( 'DELETE' ) && ! ALLOW_DELETE_OPERATION ) {
369
+ if ( isDeleteOperation && ! ALLOW_DELETE_OPERATION ) {
340
370
console . error (
341
371
'DELETE operations are not allowed. Set ALLOW_DELETE_OPERATION=true to enable.' ,
342
372
)
@@ -353,9 +383,9 @@ async function executeReadOnlyQuery<T>(sql: string): Promise<T> {
353
383
354
384
// @INFO : For write operations that are allowed, use executeWriteQuery
355
385
if (
356
- ( normalizedSql . startsWith ( 'INSERT' ) && ALLOW_INSERT_OPERATION ) ||
357
- ( normalizedSql . startsWith ( 'UPDATE' ) && ALLOW_UPDATE_OPERATION ) ||
358
- ( normalizedSql . startsWith ( 'DELETE' ) && ALLOW_DELETE_OPERATION )
386
+ ( isInsertOperation && ALLOW_INSERT_OPERATION ) ||
387
+ ( isUpdateOperation && ALLOW_UPDATE_OPERATION ) ||
388
+ ( isDeleteOperation && ALLOW_DELETE_OPERATION )
359
389
) {
360
390
return executeWriteQuery ( sql )
361
391
}
@@ -441,14 +471,22 @@ async function executeWriteQuery<T>(sql: string): Promise<T> {
441
471
let responseText
442
472
const normalizedSql = sql . trim ( ) . toUpperCase ( )
443
473
474
+ // Check the type of query
475
+ // possible types: "replace" | "update" | "insert" | "delete" | "use" | "select" | "alter" | "create" | "drop"
476
+ const queryTypes = await getQueryTypes ( normalizedSql ) ;
477
+ const isUpdateOperation = queryTypes . some ( type => [ 'update' ] . includes ( type ) ) ;
478
+ const isInsertOperation = queryTypes . some ( type => [ 'insert' ] . includes ( type ) ) ;
479
+ const isDeleteOperation = queryTypes . some ( type => [ 'delete' ] . includes ( type ) ) ;
480
+
481
+
444
482
// @INFO : Type assertion for ResultSetHeader which has affectedRows, insertId, etc.
445
- if ( normalizedSql . startsWith ( 'INSERT' ) ) {
483
+ if ( isInsertOperation ) {
446
484
const resultHeader = response as mysql2 . ResultSetHeader
447
485
responseText = `Insert successful. Affected rows: ${ resultHeader . affectedRows } , Last insert ID: ${ resultHeader . insertId } `
448
- } else if ( normalizedSql . startsWith ( 'UPDATE' ) ) {
486
+ } else if ( isUpdateOperation ) {
449
487
const resultHeader = response as mysql2 . ResultSetHeader
450
488
responseText = `Update successful. Affected rows: ${ resultHeader . affectedRows } , Changed rows: ${ resultHeader . changedRows || 0 } `
451
- } else if ( normalizedSql . startsWith ( 'DELETE' ) ) {
489
+ } else if ( isDeleteOperation ) {
452
490
const resultHeader = response as mysql2 . ResultSetHeader
453
491
responseText = `Delete successful. Affected rows: ${ resultHeader . affectedRows } `
454
492
} else {
0 commit comments