@berthojoris/mcp-mysql-server 1.0.2 → 1.2.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.
- package/LICENSE +21 -21
- package/README.md +138 -25
- package/dist/index.d.ts +39 -0
- package/dist/index.js +21 -0
- package/dist/mcp-server.js +133 -0
- package/dist/tools/crudTools.d.ts +39 -0
- package/dist/tools/crudTools.js +410 -0
- package/dist/tools/databaseTools.d.ts +2 -1
- package/dist/tools/databaseTools.js +51 -19
- package/dist/tools/storedProcedureTools.d.ts +4 -0
- package/dist/tools/storedProcedureTools.js +67 -23
- package/dist/tools/utilityTools.js +25 -25
- package/dist/validation/schemas.d.ts +179 -4
- package/dist/validation/schemas.js +192 -6
- package/manifest.json +247 -247
- package/package.json +3 -1
- package/dist/auth/authService.d.ts +0 -29
- package/dist/auth/authService.js +0 -114
package/dist/tools/crudTools.js
CHANGED
|
@@ -439,5 +439,415 @@ class CrudTools {
|
|
|
439
439
|
};
|
|
440
440
|
}
|
|
441
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* Bulk insert multiple records into the specified table
|
|
444
|
+
*/
|
|
445
|
+
async bulkInsert(params) {
|
|
446
|
+
// Validate input schema
|
|
447
|
+
if (!(0, schemas_1.validateBulkInsert)(params)) {
|
|
448
|
+
return {
|
|
449
|
+
status: 'error',
|
|
450
|
+
error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateBulkInsert.errors)
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
const { table_name, data, batch_size = 1000 } = params;
|
|
455
|
+
// Validate table name
|
|
456
|
+
const tableValidation = this.security.validateIdentifier(table_name);
|
|
457
|
+
if (!tableValidation.valid) {
|
|
458
|
+
return {
|
|
459
|
+
status: 'error',
|
|
460
|
+
error: `Invalid table name: ${tableValidation.error}`
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
// Ensure data is not empty
|
|
464
|
+
if (!data || data.length === 0) {
|
|
465
|
+
return {
|
|
466
|
+
status: 'error',
|
|
467
|
+
error: 'Data array cannot be empty'
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
// Validate that all records have the same columns
|
|
471
|
+
const firstRecord = data[0];
|
|
472
|
+
const columns = Object.keys(firstRecord);
|
|
473
|
+
for (let i = 1; i < data.length; i++) {
|
|
474
|
+
const recordColumns = Object.keys(data[i]);
|
|
475
|
+
if (recordColumns.length !== columns.length ||
|
|
476
|
+
!recordColumns.every(col => columns.includes(col))) {
|
|
477
|
+
return {
|
|
478
|
+
status: 'error',
|
|
479
|
+
error: `All records must have the same columns. Record ${i + 1} has different columns.`
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// Validate column names
|
|
484
|
+
for (const column of columns) {
|
|
485
|
+
const columnValidation = this.security.validateIdentifier(column);
|
|
486
|
+
if (!columnValidation.valid) {
|
|
487
|
+
return {
|
|
488
|
+
status: 'error',
|
|
489
|
+
error: `Invalid column name '${column}': ${columnValidation.error}`
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// Process in batches
|
|
494
|
+
const results = [];
|
|
495
|
+
let totalInserted = 0;
|
|
496
|
+
for (let i = 0; i < data.length; i += batch_size) {
|
|
497
|
+
const batch = data.slice(i, i + batch_size);
|
|
498
|
+
// Prepare batch values
|
|
499
|
+
const batchValues = [];
|
|
500
|
+
for (const record of batch) {
|
|
501
|
+
const values = columns.map(col => record[col]);
|
|
502
|
+
// Validate and sanitize parameter values for this record
|
|
503
|
+
const paramValidation = this.security.validateParameters(values);
|
|
504
|
+
if (!paramValidation.valid) {
|
|
505
|
+
return {
|
|
506
|
+
status: 'error',
|
|
507
|
+
error: `Invalid parameter values in record ${i + batchValues.length / columns.length + 1}: ${paramValidation.error}`
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
batchValues.push(...paramValidation.sanitizedParams);
|
|
511
|
+
}
|
|
512
|
+
// Build the query with escaped identifiers
|
|
513
|
+
const escapedTableName = this.security.escapeIdentifier(table_name);
|
|
514
|
+
const escapedColumns = columns.map(col => this.security.escapeIdentifier(col));
|
|
515
|
+
const valueGroups = batch.map(() => `(${columns.map(() => '?').join(', ')})`).join(', ');
|
|
516
|
+
const query = `INSERT INTO ${escapedTableName} (${escapedColumns.join(', ')}) VALUES ${valueGroups}`;
|
|
517
|
+
// Execute the batch query
|
|
518
|
+
const result = await this.db.query(query, batchValues);
|
|
519
|
+
results.push({
|
|
520
|
+
batchNumber: Math.floor(i / batch_size) + 1,
|
|
521
|
+
recordsInserted: batch.length,
|
|
522
|
+
firstInsertId: result.insertId,
|
|
523
|
+
affectedRows: result.affectedRows
|
|
524
|
+
});
|
|
525
|
+
totalInserted += result.affectedRows;
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
status: 'success',
|
|
529
|
+
data: {
|
|
530
|
+
totalRecords: data.length,
|
|
531
|
+
totalInserted,
|
|
532
|
+
batches: results.length,
|
|
533
|
+
batchResults: results
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
return {
|
|
539
|
+
status: 'error',
|
|
540
|
+
error: error.message
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Bulk update multiple records with different conditions and data
|
|
546
|
+
*/
|
|
547
|
+
async bulkUpdate(params) {
|
|
548
|
+
// Validate input schema
|
|
549
|
+
if (!(0, schemas_1.validateBulkUpdate)(params)) {
|
|
550
|
+
return {
|
|
551
|
+
status: 'error',
|
|
552
|
+
error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateBulkUpdate.errors)
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const { table_name, updates, batch_size = 100 } = params;
|
|
557
|
+
// Validate table name
|
|
558
|
+
const tableValidation = this.security.validateIdentifier(table_name);
|
|
559
|
+
if (!tableValidation.valid) {
|
|
560
|
+
return {
|
|
561
|
+
status: 'error',
|
|
562
|
+
error: `Invalid table name: ${tableValidation.error}`
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
// Ensure updates is not empty
|
|
566
|
+
if (!updates || updates.length === 0) {
|
|
567
|
+
return {
|
|
568
|
+
status: 'error',
|
|
569
|
+
error: 'Updates array cannot be empty'
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
// Validate each update operation
|
|
573
|
+
for (let i = 0; i < updates.length; i++) {
|
|
574
|
+
const update = updates[i];
|
|
575
|
+
// Validate column names in data
|
|
576
|
+
const columns = Object.keys(update.data);
|
|
577
|
+
for (const column of columns) {
|
|
578
|
+
const columnValidation = this.security.validateIdentifier(column);
|
|
579
|
+
if (!columnValidation.valid) {
|
|
580
|
+
return {
|
|
581
|
+
status: 'error',
|
|
582
|
+
error: `Invalid column name '${column}' in update ${i + 1}: ${columnValidation.error}`
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
// Validate condition fields
|
|
587
|
+
for (const condition of update.conditions) {
|
|
588
|
+
const fieldValidation = this.security.validateIdentifier(condition.field);
|
|
589
|
+
if (!fieldValidation.valid) {
|
|
590
|
+
return {
|
|
591
|
+
status: 'error',
|
|
592
|
+
error: `Invalid condition field '${condition.field}' in update ${i + 1}: ${fieldValidation.error}`
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
// Process in batches using transactions for consistency
|
|
598
|
+
const results = [];
|
|
599
|
+
let totalAffected = 0;
|
|
600
|
+
for (let i = 0; i < updates.length; i += batch_size) {
|
|
601
|
+
const batch = updates.slice(i, i + batch_size);
|
|
602
|
+
// Start a transaction for this batch
|
|
603
|
+
await this.db.query('START TRANSACTION');
|
|
604
|
+
try {
|
|
605
|
+
const batchResults = [];
|
|
606
|
+
for (const update of batch) {
|
|
607
|
+
// Build SET clause
|
|
608
|
+
const setClause = Object.entries(update.data)
|
|
609
|
+
.map(([column, _]) => `${this.security.escapeIdentifier(column)} = ?`)
|
|
610
|
+
.join(', ');
|
|
611
|
+
const setValues = Object.values(update.data);
|
|
612
|
+
// Build WHERE clause
|
|
613
|
+
const whereConditions = [];
|
|
614
|
+
const whereValues = [];
|
|
615
|
+
update.conditions.forEach(condition => {
|
|
616
|
+
const escapedField = this.security.escapeIdentifier(condition.field);
|
|
617
|
+
switch (condition.operator) {
|
|
618
|
+
case 'eq':
|
|
619
|
+
whereConditions.push(`${escapedField} = ?`);
|
|
620
|
+
whereValues.push(condition.value);
|
|
621
|
+
break;
|
|
622
|
+
case 'neq':
|
|
623
|
+
whereConditions.push(`${escapedField} != ?`);
|
|
624
|
+
whereValues.push(condition.value);
|
|
625
|
+
break;
|
|
626
|
+
case 'gt':
|
|
627
|
+
whereConditions.push(`${escapedField} > ?`);
|
|
628
|
+
whereValues.push(condition.value);
|
|
629
|
+
break;
|
|
630
|
+
case 'gte':
|
|
631
|
+
whereConditions.push(`${escapedField} >= ?`);
|
|
632
|
+
whereValues.push(condition.value);
|
|
633
|
+
break;
|
|
634
|
+
case 'lt':
|
|
635
|
+
whereConditions.push(`${escapedField} < ?`);
|
|
636
|
+
whereValues.push(condition.value);
|
|
637
|
+
break;
|
|
638
|
+
case 'lte':
|
|
639
|
+
whereConditions.push(`${escapedField} <= ?`);
|
|
640
|
+
whereValues.push(condition.value);
|
|
641
|
+
break;
|
|
642
|
+
case 'like':
|
|
643
|
+
whereConditions.push(`${escapedField} LIKE ?`);
|
|
644
|
+
whereValues.push(`%${condition.value}%`);
|
|
645
|
+
break;
|
|
646
|
+
case 'in':
|
|
647
|
+
if (Array.isArray(condition.value)) {
|
|
648
|
+
const placeholders = condition.value.map(() => '?').join(', ');
|
|
649
|
+
whereConditions.push(`${escapedField} IN (${placeholders})`);
|
|
650
|
+
whereValues.push(...condition.value);
|
|
651
|
+
}
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
const whereClause = whereConditions.length > 0
|
|
656
|
+
? 'WHERE ' + whereConditions.join(' AND ')
|
|
657
|
+
: '';
|
|
658
|
+
// Validate all parameters
|
|
659
|
+
const allParams = [...setValues, ...whereValues];
|
|
660
|
+
const paramValidation = this.security.validateParameters(allParams);
|
|
661
|
+
if (!paramValidation.valid) {
|
|
662
|
+
throw new Error(`Invalid parameters: ${paramValidation.error}`);
|
|
663
|
+
}
|
|
664
|
+
// Build and execute the query
|
|
665
|
+
const escapedTableName = this.security.escapeIdentifier(table_name);
|
|
666
|
+
const query = `UPDATE ${escapedTableName} SET ${setClause} ${whereClause}`;
|
|
667
|
+
const result = await this.db.query(query, paramValidation.sanitizedParams);
|
|
668
|
+
batchResults.push({
|
|
669
|
+
affectedRows: result.affectedRows
|
|
670
|
+
});
|
|
671
|
+
totalAffected += result.affectedRows;
|
|
672
|
+
}
|
|
673
|
+
// Commit the transaction
|
|
674
|
+
await this.db.query('COMMIT');
|
|
675
|
+
results.push({
|
|
676
|
+
batchNumber: Math.floor(i / batch_size) + 1,
|
|
677
|
+
updatesProcessed: batch.length,
|
|
678
|
+
results: batchResults
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
// Rollback on error
|
|
683
|
+
await this.db.query('ROLLBACK');
|
|
684
|
+
throw error;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return {
|
|
688
|
+
status: 'success',
|
|
689
|
+
data: {
|
|
690
|
+
totalUpdates: updates.length,
|
|
691
|
+
totalAffectedRows: totalAffected,
|
|
692
|
+
batches: results.length,
|
|
693
|
+
batchResults: results
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
return {
|
|
699
|
+
status: 'error',
|
|
700
|
+
error: error.message
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Bulk delete records based on multiple condition sets
|
|
706
|
+
*/
|
|
707
|
+
async bulkDelete(params) {
|
|
708
|
+
// Validate input schema
|
|
709
|
+
if (!(0, schemas_1.validateBulkDelete)(params)) {
|
|
710
|
+
return {
|
|
711
|
+
status: 'error',
|
|
712
|
+
error: 'Invalid parameters: ' + JSON.stringify(schemas_1.validateBulkDelete.errors)
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
try {
|
|
716
|
+
const { table_name, condition_sets, batch_size = 100 } = params;
|
|
717
|
+
// Validate table name
|
|
718
|
+
const tableValidation = this.security.validateIdentifier(table_name);
|
|
719
|
+
if (!tableValidation.valid) {
|
|
720
|
+
return {
|
|
721
|
+
status: 'error',
|
|
722
|
+
error: `Invalid table name: ${tableValidation.error}`
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
// Ensure condition_sets is not empty
|
|
726
|
+
if (!condition_sets || condition_sets.length === 0) {
|
|
727
|
+
return {
|
|
728
|
+
status: 'error',
|
|
729
|
+
error: 'Condition sets array cannot be empty'
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
// Validate each condition set
|
|
733
|
+
for (let i = 0; i < condition_sets.length; i++) {
|
|
734
|
+
const conditions = condition_sets[i];
|
|
735
|
+
// Ensure there are conditions for safety
|
|
736
|
+
if (!conditions || conditions.length === 0) {
|
|
737
|
+
return {
|
|
738
|
+
status: 'error',
|
|
739
|
+
error: `DELETE operations require at least one condition for safety. Condition set ${i + 1} is empty.`
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
// Validate condition fields
|
|
743
|
+
for (const condition of conditions) {
|
|
744
|
+
const fieldValidation = this.security.validateIdentifier(condition.field);
|
|
745
|
+
if (!fieldValidation.valid) {
|
|
746
|
+
return {
|
|
747
|
+
status: 'error',
|
|
748
|
+
error: `Invalid condition field '${condition.field}' in condition set ${i + 1}: ${fieldValidation.error}`
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// Process in batches using transactions for consistency
|
|
754
|
+
const results = [];
|
|
755
|
+
let totalDeleted = 0;
|
|
756
|
+
for (let i = 0; i < condition_sets.length; i += batch_size) {
|
|
757
|
+
const batch = condition_sets.slice(i, i + batch_size);
|
|
758
|
+
// Start a transaction for this batch
|
|
759
|
+
await this.db.query('START TRANSACTION');
|
|
760
|
+
try {
|
|
761
|
+
const batchResults = [];
|
|
762
|
+
for (const conditions of batch) {
|
|
763
|
+
// Build WHERE clause
|
|
764
|
+
const whereConditions = [];
|
|
765
|
+
const whereValues = [];
|
|
766
|
+
conditions.forEach(condition => {
|
|
767
|
+
const escapedField = this.security.escapeIdentifier(condition.field);
|
|
768
|
+
switch (condition.operator) {
|
|
769
|
+
case 'eq':
|
|
770
|
+
whereConditions.push(`${escapedField} = ?`);
|
|
771
|
+
whereValues.push(condition.value);
|
|
772
|
+
break;
|
|
773
|
+
case 'neq':
|
|
774
|
+
whereConditions.push(`${escapedField} != ?`);
|
|
775
|
+
whereValues.push(condition.value);
|
|
776
|
+
break;
|
|
777
|
+
case 'gt':
|
|
778
|
+
whereConditions.push(`${escapedField} > ?`);
|
|
779
|
+
whereValues.push(condition.value);
|
|
780
|
+
break;
|
|
781
|
+
case 'gte':
|
|
782
|
+
whereConditions.push(`${escapedField} >= ?`);
|
|
783
|
+
whereValues.push(condition.value);
|
|
784
|
+
break;
|
|
785
|
+
case 'lt':
|
|
786
|
+
whereConditions.push(`${escapedField} < ?`);
|
|
787
|
+
whereValues.push(condition.value);
|
|
788
|
+
break;
|
|
789
|
+
case 'lte':
|
|
790
|
+
whereConditions.push(`${escapedField} <= ?`);
|
|
791
|
+
whereValues.push(condition.value);
|
|
792
|
+
break;
|
|
793
|
+
case 'like':
|
|
794
|
+
whereConditions.push(`${escapedField} LIKE ?`);
|
|
795
|
+
whereValues.push(`%${condition.value}%`);
|
|
796
|
+
break;
|
|
797
|
+
case 'in':
|
|
798
|
+
if (Array.isArray(condition.value)) {
|
|
799
|
+
const placeholders = condition.value.map(() => '?').join(', ');
|
|
800
|
+
whereConditions.push(`${escapedField} IN (${placeholders})`);
|
|
801
|
+
whereValues.push(...condition.value);
|
|
802
|
+
}
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
const whereClause = 'WHERE ' + whereConditions.join(' AND ');
|
|
807
|
+
// Validate all parameters
|
|
808
|
+
const paramValidation = this.security.validateParameters(whereValues);
|
|
809
|
+
if (!paramValidation.valid) {
|
|
810
|
+
throw new Error(`Invalid parameters: ${paramValidation.error}`);
|
|
811
|
+
}
|
|
812
|
+
// Build and execute the query
|
|
813
|
+
const escapedTableName = this.security.escapeIdentifier(table_name);
|
|
814
|
+
const query = `DELETE FROM ${escapedTableName} ${whereClause}`;
|
|
815
|
+
const result = await this.db.query(query, paramValidation.sanitizedParams);
|
|
816
|
+
batchResults.push({
|
|
817
|
+
affectedRows: result.affectedRows
|
|
818
|
+
});
|
|
819
|
+
totalDeleted += result.affectedRows;
|
|
820
|
+
}
|
|
821
|
+
// Commit the transaction
|
|
822
|
+
await this.db.query('COMMIT');
|
|
823
|
+
results.push({
|
|
824
|
+
batchNumber: Math.floor(i / batch_size) + 1,
|
|
825
|
+
deletesProcessed: batch.length,
|
|
826
|
+
results: batchResults
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
// Rollback on error
|
|
831
|
+
await this.db.query('ROLLBACK');
|
|
832
|
+
throw error;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
status: 'success',
|
|
837
|
+
data: {
|
|
838
|
+
totalDeletes: condition_sets.length,
|
|
839
|
+
totalDeletedRows: totalDeleted,
|
|
840
|
+
batches: results.length,
|
|
841
|
+
batchResults: results
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
return {
|
|
847
|
+
status: 'error',
|
|
848
|
+
error: error.message
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
}
|
|
442
852
|
}
|
|
443
853
|
exports.CrudTools = CrudTools;
|
|
@@ -3,7 +3,8 @@ export declare class DatabaseTools {
|
|
|
3
3
|
private db;
|
|
4
4
|
constructor();
|
|
5
5
|
/**
|
|
6
|
-
* List
|
|
6
|
+
* List only the connected database (security restriction)
|
|
7
|
+
* This prevents access to other databases on the MySQL server
|
|
7
8
|
*/
|
|
8
9
|
listDatabases(): Promise<{
|
|
9
10
|
status: string;
|
|
@@ -6,20 +6,37 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.DatabaseTools = void 0;
|
|
7
7
|
const connection_1 = __importDefault(require("../db/connection"));
|
|
8
8
|
const schemas_1 = require("../validation/schemas");
|
|
9
|
+
const config_1 = require("../config/config");
|
|
9
10
|
class DatabaseTools {
|
|
10
11
|
constructor() {
|
|
11
12
|
this.db = connection_1.default.getInstance();
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
|
-
* List
|
|
15
|
+
* List only the connected database (security restriction)
|
|
16
|
+
* This prevents access to other databases on the MySQL server
|
|
15
17
|
*/
|
|
16
18
|
async listDatabases() {
|
|
17
19
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
// Only return the database specified in the connection string
|
|
21
|
+
// This is a security measure to prevent access to other databases
|
|
22
|
+
if (!config_1.dbConfig.database) {
|
|
23
|
+
return {
|
|
24
|
+
status: 'error',
|
|
25
|
+
error: 'No database specified in connection string. Please specify a database name in your MySQL connection URL.'
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// Verify the database exists and is accessible
|
|
29
|
+
const results = await this.db.query('SELECT DATABASE() as current_database');
|
|
30
|
+
const currentDatabase = results[0]?.current_database;
|
|
31
|
+
if (!currentDatabase) {
|
|
32
|
+
return {
|
|
33
|
+
status: 'error',
|
|
34
|
+
error: 'No database selected. Please ensure your connection string includes a valid database name.'
|
|
35
|
+
};
|
|
36
|
+
}
|
|
20
37
|
return {
|
|
21
38
|
status: 'success',
|
|
22
|
-
data:
|
|
39
|
+
data: [currentDatabase]
|
|
23
40
|
};
|
|
24
41
|
}
|
|
25
42
|
catch (error) {
|
|
@@ -41,8 +58,23 @@ class DatabaseTools {
|
|
|
41
58
|
};
|
|
42
59
|
}
|
|
43
60
|
try {
|
|
61
|
+
// Security validation: if database is specified, ensure it matches the connected database
|
|
62
|
+
if (params.database) {
|
|
63
|
+
if (!config_1.dbConfig.database) {
|
|
64
|
+
return {
|
|
65
|
+
status: 'error',
|
|
66
|
+
error: 'No database specified in connection string. Cannot access other databases.'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (params.database !== config_1.dbConfig.database) {
|
|
70
|
+
return {
|
|
71
|
+
status: 'error',
|
|
72
|
+
error: `Access denied. You can only access the connected database '${config_1.dbConfig.database}'. Requested database '${params.database}' is not allowed.`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
44
76
|
let query = 'SHOW TABLES';
|
|
45
|
-
// If database is specified, use it
|
|
77
|
+
// If database is specified and validated, use it
|
|
46
78
|
if (params.database) {
|
|
47
79
|
query = `SHOW TABLES FROM \`${params.database}\``;
|
|
48
80
|
}
|
|
@@ -76,20 +108,20 @@ class DatabaseTools {
|
|
|
76
108
|
};
|
|
77
109
|
}
|
|
78
110
|
try {
|
|
79
|
-
const query = `
|
|
80
|
-
SELECT
|
|
81
|
-
COLUMN_NAME as column_name,
|
|
82
|
-
DATA_TYPE as data_type,
|
|
83
|
-
IS_NULLABLE as is_nullable,
|
|
84
|
-
COLUMN_KEY as column_key,
|
|
85
|
-
COLUMN_DEFAULT as column_default,
|
|
86
|
-
EXTRA as extra
|
|
87
|
-
FROM
|
|
88
|
-
INFORMATION_SCHEMA.COLUMNS
|
|
89
|
-
WHERE
|
|
90
|
-
TABLE_NAME = ?
|
|
91
|
-
ORDER BY
|
|
92
|
-
ORDINAL_POSITION
|
|
111
|
+
const query = `
|
|
112
|
+
SELECT
|
|
113
|
+
COLUMN_NAME as column_name,
|
|
114
|
+
DATA_TYPE as data_type,
|
|
115
|
+
IS_NULLABLE as is_nullable,
|
|
116
|
+
COLUMN_KEY as column_key,
|
|
117
|
+
COLUMN_DEFAULT as column_default,
|
|
118
|
+
EXTRA as extra
|
|
119
|
+
FROM
|
|
120
|
+
INFORMATION_SCHEMA.COLUMNS
|
|
121
|
+
WHERE
|
|
122
|
+
TABLE_NAME = ?
|
|
123
|
+
ORDER BY
|
|
124
|
+
ORDINAL_POSITION
|
|
93
125
|
`;
|
|
94
126
|
const results = await this.db.query(query, [params.table_name]);
|
|
95
127
|
return {
|
|
@@ -3,6 +3,10 @@ export declare class StoredProcedureTools {
|
|
|
3
3
|
private db;
|
|
4
4
|
private security;
|
|
5
5
|
constructor(security: SecurityLayer);
|
|
6
|
+
/**
|
|
7
|
+
* Validate database access - ensures only the connected database can be accessed
|
|
8
|
+
*/
|
|
9
|
+
private validateDatabaseAccess;
|
|
6
10
|
/**
|
|
7
11
|
* List all stored procedures in the current database
|
|
8
12
|
*/
|