Skip to main content
Glama

MCP SQL Server

by ryudg
mysql.adapter.ts9.5 kB
import { BaseDatabaseAdapter } from './base.adapter.js'; import { DatabaseConfig, QueryResult, TableInfo, QueryOptions, DatabaseType, SchemaInfo, ColumnInfo, } from '../../types/database.types.js'; import { Logger } from '../../core/logger.js'; // Mock mysql2 module for testing purposes interface MockConnection { query: (sql: string, values?: any[]) => Promise<[any, any]>; execute: (sql: string, values?: any[]) => Promise<[any, any]>; end: () => Promise<void>; } interface MockMysql { createConnection: (config: any) => Promise<MockConnection>; } // This will be mocked in tests const defaultMysql: MockMysql = { createConnection: async () => { throw new Error('MySQL adapter requires mysql2 library to be installed'); }, }; /** * MySQL Database Adapter */ export class MySQLAdapter extends BaseDatabaseAdapter { private connection: MockConnection | null = null; private mysql: MockMysql; constructor(config: DatabaseConfig, logger?: Logger, mysql?: MockMysql) { super(config, logger); this.mysql = mysql || defaultMysql; } /** * Connect to database */ async connect(): Promise<void> { try { const config = { host: this.config.host, port: this.config.port || 3306, database: this.config.database, user: this.config.user, password: this.config.password, ssl: this.config.ssl || false, connectTimeout: this.config.options?.connectionTimeout || 60000, acquireTimeout: this.config.options?.requestTimeout || 60000, timeout: this.config.options?.requestTimeout || 60000, }; this.connection = await this.mysql.createConnection(config); this.connected = true; } catch (error) { this.connected = false; throw new Error( `MySQL connection failed: ${error instanceof Error ? error.message : String(error)}` ); } } /** * Disconnect from database */ async disconnect(): Promise<void> { try { if (this.connection) { await this.connection.end(); this.connection = null; } this.connected = false; } catch (error) { throw new Error( `MySQL disconnection failed: ${error instanceof Error ? error.message : String(error)}` ); } } /** * Execute query */ async executeQuery(query: string, options?: QueryOptions): Promise<QueryResult> { if (!this.connection) { throw new Error('Not connected to database.'); } const startTime = Date.now(); try { let result: any; if (options?.parameters && options.parameters.length > 0) { [result] = await this.connection.execute(query, options.parameters); } else { [result] = await this.connection.query(query); } const executionTime = Date.now() - startTime; // Handle different result types let rowsAffected = 0; let recordset: any[] | undefined = undefined; if (Array.isArray(result)) { recordset = result; rowsAffected = result.length; } else if (result && typeof result === 'object') { if ('affectedRows' in result) { rowsAffected = result.affectedRows || 0; } if ('insertId' in result) { recordset = [{ insertId: result.insertId }]; } } return { success: true, recordset, data: recordset, rowsAffected, executionTime, }; } catch (error) { const executionTime = Date.now() - startTime; return { success: false, error: error instanceof Error ? error.message : String(error), rowsAffected: 0, executionTime, }; } } /** * Get table list (returns TableInfo array) */ async getTables(): Promise<TableInfo[]> { const query = ` SELECT TABLE_NAME as name, TABLE_SCHEMA as tableSchema, TABLE_TYPE as type FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE IN ('BASE TABLE', 'VIEW') ORDER BY TABLE_SCHEMA, TABLE_NAME `; const result = await this.executeQuery(query); if (!result.success || !result.recordset) { return []; } return result.recordset.map(row => ({ name: row.name, schema: row.tableSchema, type: row.type === 'BASE TABLE' ? ('table' as const) : ('view' as const), columns: [], primaryKeys: [], foreignKeys: [], indexes: [], })); } /** * Get table name list (returns string array) */ async getTableNames(): Promise<string[]> { const query = ` SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE IN ('BASE TABLE', 'VIEW') ORDER BY TABLE_NAME `; const result = await this.executeQuery(query); if (!result.success || !result.recordset) { return []; } return result.recordset.map(row => row.name); } /** * Get table detailed information list (maintained for implementation compatibility) */ async getTableInfos(): Promise<TableInfo[]> { return this.getTables(); } /** * Get specific table information */ async getTableInfo(tableName: string): Promise<TableInfo> { // Get column information const columnsQuery = ` SELECT COLUMN_NAME as name, DATA_TYPE as type, CHARACTER_MAXIMUM_LENGTH as maxLength, NUMERIC_PRECISION as precision, NUMERIC_SCALE as scale, IS_NULLABLE as isNullable, COLUMN_DEFAULT as defaultValue, ORDINAL_POSITION as ordinalPosition, EXTRA as extra FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION `; const columnsResult = await this.executeQuery(columnsQuery, { parameters: [tableName], }); // Get primary key information const primaryKeysQuery = ` SELECT COLUMN_NAME as name FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND CONSTRAINT_NAME = 'PRIMARY' `; const primaryKeysResult = await this.executeQuery(primaryKeysQuery, { parameters: [tableName], }); // Get foreign key information const foreignKeysQuery = ` SELECT COLUMN_NAME as name, REFERENCED_TABLE_NAME as referencedTable, REFERENCED_COLUMN_NAME as referencedColumn FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND REFERENCED_TABLE_NAME IS NOT NULL `; const foreignKeysResult = await this.executeQuery(foreignKeysQuery, { parameters: [tableName], }); // Get index information const indexesQuery = ` SELECT DISTINCT INDEX_NAME as name FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME != 'PRIMARY' `; const indexesResult = await this.executeQuery(indexesQuery, { parameters: [tableName], }); const primaryKeyNames = new Set(primaryKeysResult.recordset?.map(row => row.name) || []); const columns: ColumnInfo[] = columnsResult.recordset?.map(row => ({ name: row.name, dataType: row.type, maxLength: row.maxLength, precision: row.precision, scale: row.scale, isNullable: row.isNullable === 'YES', isIdentity: row.extra?.includes('auto_increment') || false, isPrimaryKey: primaryKeyNames.has(row.name), defaultValue: row.defaultValue, ordinalPosition: row.ordinalPosition, })) || []; return { name: tableName, schema: this.config.database, type: 'table', columns, primaryKeys: primaryKeysResult.recordset?.map(row => row.name) || [], foreignKeys: foreignKeysResult.recordset || [], indexes: indexesResult.recordset || [], }; } /** * Get schema information */ async getSchema(includeDetailedInfo: boolean = true): Promise<SchemaInfo> { const tables = await this.getTables(); // If detailed info is requested, populate each table's detailed information if (includeDetailedInfo && tables.length > 0) { for (let i = 0; i < tables.length; i++) { try { const tableInfo = await this.getTableInfo(tables[i].name); tables[i].columns = tableInfo.columns; tables[i].primaryKeys = tableInfo.primaryKeys; tables[i].foreignKeys = tableInfo.foreignKeys; tables[i].indexes = tableInfo.indexes; } catch (error) { // Keep empty arrays for failed tables } } } return { name: this.config.database, tables, views: tables.filter(t => t.type === 'view'), functions: [], // MySQL function query (separate implementation needed) procedures: [], // MySQL procedure query (separate implementation needed) }; } /** * Test connection status */ async testConnection(): Promise<boolean> { try { if (!this.connection) return false; const result = await this.executeQuery('SELECT 1 as test'); return result.success && result.recordset?.[0]?.test === 1; } catch { return false; } } /** * Return database type */ getType(): DatabaseType { return 'mysql'; } }

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/ryudg/mcp-sql'

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