Skip to main content
Glama

MySQL MCP Server

by QiPanTanYi
database.service.ts5.86 kB
import { Injectable } from '@nestjs/common'; import { InjectConnection } from '@nestjs/typeorm'; import { Connection } from 'typeorm'; import { ConfigService } from '../config/config.service'; import { PermissionsService } from '../common/permissions.service'; import { AuditLoggerService } from '../common/audit-logger.service'; @Injectable() export class DatabaseService { constructor( @InjectConnection() private connection: Connection, private configService: ConfigService, private permissionsService: PermissionsService, private auditLogger: AuditLoggerService, ) {} // 验证 SQL 安全性 private validateSql(sql: string): void { const validation = this.permissionsService.validateSql(sql); if (!validation.valid) { throw new Error(validation.error); } } // 验证表名是否可访问 private validateTable(tableName: string): void { if (!this.permissionsService.canAccessTable(tableName)) { throw new Error(`无权访问表 '${tableName}'`); } } // 验证表操作权限 private validateOperation(tableName: string, operation: string): void { if (!this.permissionsService.canPerformOperation(tableName, operation)) { if (this.permissionsService.isReadOnlyTable(tableName)) { throw new Error(`表 '${tableName}' 是只读的,不允许 ${operation} 操作`); } throw new Error(`无权在表 '${tableName}' 上执行 ${operation} 操作`); } } // 列出所有表 async listTables(): Promise<string[]> { const result = await this.connection.query('SHOW TABLES'); const dbName = this.configService.dbConfig.database; return result.map(row => row[`Tables_in_${dbName}`]); } // 获取表结构 async getTableSchema(tableName: string): Promise<any> { this.validateTable(tableName); const columns = await this.connection.query( `DESCRIBE ${tableName}` ); return { tableName, columns: columns.map(col => ({ field: col.Field, type: col.Type, null: col.Null, key: col.Key, default: col.Default, extra: col.Extra, })), }; } // 执行查询 async query(sql: string, params: any[] = []): Promise<any> { this.validateSql(sql); // 添加 LIMIT 限制 const maxLimit = this.permissionsService.getMaxQueryLimit(); if (sql.toUpperCase().includes('SELECT') && !sql.toUpperCase().includes('LIMIT')) { sql += ` LIMIT ${maxLimit}`; } const result = await this.connection.query(sql, params); // 记录审计日志 if (this.permissionsService.isAuditEnabled()) { await this.auditLogger.logQuery('unknown', sql, params, result); } return result; } // 插入记录 async insert(tableName: string, data: Record<string, any>): Promise<any> { this.validateTable(tableName); this.validateOperation(tableName, 'INSERT'); const keys = Object.keys(data); const values = Object.values(data); const placeholders = keys.map(() => '?').join(', '); const sql = `INSERT INTO ${tableName} (${keys.join(', ')}) VALUES (${placeholders})`; const result = await this.connection.query(sql, values); // 记录审计日志 if (this.permissionsService.isAuditEnabled()) { await this.auditLogger.logInsert(tableName, data, result); } return { insertId: result.insertId, affectedRows: result.affectedRows, insertedData: data, }; } // 更新记录 async update(tableName: string, data: Record<string, any>, where: Record<string, any>): Promise<any> { this.validateTable(tableName); this.validateOperation(tableName, 'UPDATE'); // 获取修改前的数据 let beforeData = null; if (this.permissionsService.shouldTrackChanges()) { const whereClause = Object.keys(where).map(key => `${key} = ?`).join(' AND '); const selectSql = `SELECT * FROM ${tableName} WHERE ${whereClause}`; beforeData = await this.connection.query(selectSql, Object.values(where)); } const setClause = Object.keys(data).map(key => `${key} = ?`).join(', '); const whereClause = Object.keys(where).map(key => `${key} = ?`).join(' AND '); const sql = `UPDATE ${tableName} SET ${setClause} WHERE ${whereClause}`; const params = [...Object.values(data), ...Object.values(where)]; const result = await this.connection.query(sql, params); // 记录审计日志 if (this.permissionsService.isAuditEnabled()) { await this.auditLogger.logUpdate(tableName, data, where, beforeData, result); } return { affectedRows: result.affectedRows, changedRows: result.changedRows, beforeData, afterData: data, }; } // 删除记录 async delete(tableName: string, where: Record<string, any>): Promise<any> { this.validateTable(tableName); this.validateOperation(tableName, 'DELETE'); // 获取删除前的数据 let beforeData = null; if (this.permissionsService.shouldTrackChanges()) { const whereClause = Object.keys(where).map(key => `${key} = ?`).join(' AND '); const selectSql = `SELECT * FROM ${tableName} WHERE ${whereClause}`; beforeData = await this.connection.query(selectSql, Object.values(where)); } const whereClause = Object.keys(where).map(key => `${key} = ?`).join(' AND '); const sql = `DELETE FROM ${tableName} WHERE ${whereClause}`; const params = Object.values(where); const result = await this.connection.query(sql, params); // 记录审计日志 if (this.permissionsService.isAuditEnabled()) { await this.auditLogger.logDelete(tableName, where, beforeData, result); } return { affectedRows: result.affectedRows, deletedData: beforeData, }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/QiPanTanYi/mysql-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server