Skip to main content
Glama

MySQL MCP Server

by yuki777
mcp-server.ts20.9 kB
import { MCPServerDefinition, MCPToolDefinition, MCPResourceDefinition, MCPToolRequest, MCPToolResponse, MySQLConfig, NamedStoredConnection } from './types.js'; import { ConfigManager } from '../config/config.js'; import { MySQLConnection } from '../mysql/connection.js'; /** * MCP サーバーのメインクラス */ export class MCPServer { private serverDefinition: MCPServerDefinition; private configManager: ConfigManager; private mysqlConnection: MySQLConnection; private tools: Map<string, (args: Record<string, any>) => Promise<any>>; /** * コンストラクタ * @param configManager 設定マネージャー */ constructor(configManager: ConfigManager) { this.configManager = configManager; this.mysqlConnection = new MySQLConnection( configManager.getMySQLConfig(), configManager.getQueryTimeout() ); this.tools = new Map(); // サーバー定義の初期化 this.serverDefinition = { name: 'mysql-mcp-server', version: '1.0.0', tools: [], resources: [] }; // ツールとリソースの登録 this.registerTools(); } /** * サーバーを初期化 * @param autoConnect 自動的にデータベースに接続するかどうか */ public async initialize(autoConnect: boolean = false): Promise<void> { try { // MySQLコネクションを初期化(接続は行わない) await this.mysqlConnection.initialize(autoConnect); console.error('MySQL MCP Server initialized'); if (autoConnect && this.mysqlConnection.isConnectedToDatabase()) { // 接続成功時は設定を保存 this.configManager.saveCurrentConnection(); } } catch (error) { console.error('Failed to initialize MCP Server:', error); throw error; } } /** * ツールとリソースを登録 */ private registerTools(): void { // -- データベース接続管理ツール -- // connect_database ツール this.registerTool({ name: 'connect_database', description: 'データベースに接続します', inputSchema: { type: 'object', properties: { host: { type: 'string', description: 'MySQLホスト' }, port: { type: 'number', description: 'MySQLポート' }, user: { type: 'string', description: 'MySQLユーザー' }, password: { type: 'string', description: 'MySQLパスワード' }, database: { type: 'string', description: 'データベース名(オプション)' }, profileName: { type: 'string', description: '接続プロファイル名(オプション)。接続後、この名前で保存されます' } }, required: ['host', 'port', 'user'] } }, this.connectDatabase.bind(this)); // connect_by_profile ツール this.registerTool({ name: 'connect_by_profile', description: '保存済み接続プロファイルを使用してデータベースに接続します', inputSchema: { type: 'object', properties: { profileName: { type: 'string', description: '接続プロファイル名' } }, required: ['profileName'] } }, this.connectByProfile.bind(this)); // disconnect_database ツール this.registerTool({ name: 'disconnect_database', description: '現在のデータベース接続を切断します', inputSchema: { type: 'object', properties: {} } }, this.disconnectDatabase.bind(this)); // get_connection_status ツール this.registerTool({ name: 'get_connection_status', description: 'データベース接続の状態を取得します', inputSchema: { type: 'object', properties: {} } }, this.getConnectionStatus.bind(this)); // list_profiles ツール this.registerTool({ name: 'list_profiles', description: '保存済みの接続プロファイル一覧を取得します', inputSchema: { type: 'object', properties: {} } }, this.listProfiles.bind(this)); // get_profile ツール this.registerTool({ name: 'get_profile', description: '指定した名前の接続プロファイルの詳細を取得します', inputSchema: { type: 'object', properties: { profileName: { type: 'string', description: 'プロファイル名' } }, required: ['profileName'] } }, this.getProfile.bind(this)); // add_profile ツール this.registerTool({ name: 'add_profile', description: '新しい接続プロファイルを追加または更新します', inputSchema: { type: 'object', properties: { profileName: { type: 'string', description: 'プロファイル名' }, host: { type: 'string', description: 'MySQLホスト' }, port: { type: 'number', description: 'MySQLポート' }, user: { type: 'string', description: 'MySQLユーザー' }, password: { type: 'string', description: 'MySQLパスワード' }, database: { type: 'string', description: 'データベース名(オプション)' } }, required: ['profileName', 'host', 'port', 'user'] } }, this.addProfile.bind(this)); // remove_profile ツール this.registerTool({ name: 'remove_profile', description: '指定した名前の接続プロファイルを削除します', inputSchema: { type: 'object', properties: { profileName: { type: 'string', description: 'プロファイル名' } }, required: ['profileName'] } }, this.removeProfile.bind(this)); // -- SQLクエリ関連ツール -- // execute_query ツール this.registerTool({ name: 'execute_query', description: 'MySQLクエリを実行します', inputSchema: { type: 'object', properties: { query: { type: 'string', description: '実行するSQLクエリ' }, params: { type: 'array', items: { type: 'any' }, description: 'クエリパラメータ(オプション)' } }, required: ['query'] } }, this.executeQuery.bind(this)); // get_databases ツール this.registerTool({ name: 'get_databases', description: '利用可能なデータベースの一覧を取得します', inputSchema: { type: 'object', properties: {} } }, this.getDatabases.bind(this)); // get_tables ツール this.registerTool({ name: 'get_tables', description: '指定したデータベース内のテーブル一覧を取得します', inputSchema: { type: 'object', properties: { database: { type: 'string', description: 'データベース名(指定しない場合は現在選択されているデータベース)' } } } }, this.getTables.bind(this)); // describe_table ツール this.registerTool({ name: 'describe_table', description: '指定したテーブルの構造を取得します', inputSchema: { type: 'object', properties: { table: { type: 'string', description: 'テーブル名' }, database: { type: 'string', description: 'データベース名(指定しない場合は現在選択されているデータベース)' } }, required: ['table'] } }, this.describeTable.bind(this)); } /** * ツールを登録 * @param definition ツール定義 * @param handler ツールハンドラー関数 */ private registerTool( definition: MCPToolDefinition, handler: (args: Record<string, any>) => Promise<any> ): void { this.serverDefinition.tools.push(definition); this.tools.set(definition.name, handler); } /** * サーバー定義を取得 */ public getServerDefinition(): MCPServerDefinition { return this.serverDefinition; } /** * ツールを実行 * @param request ツールリクエスト */ public async executeTool(request: MCPToolRequest): Promise<MCPToolResponse> { try { const { tool, arguments: args } = request; const handler = this.tools.get(tool); if (!handler) { return { result: null, error: `Unknown tool: ${tool}` }; } const result = await handler(args); return { result }; } catch (error) { console.error(`Error executing tool:`, error); return { result: null, error: (error as Error).message }; } } /** * SQLクエリ実行ツール * @param args クエリ引数 */ private async executeQuery(args: Record<string, any>): Promise<any> { try { // 接続状態のチェック if (!this.mysqlConnection.isConnectedToDatabase()) { throw new Error('Database not connected. Please use connect_database tool first.'); } const { query, params = [] } = args as { query: string; params?: any[] }; if (!query || query.trim() === '') { throw new Error('Empty query'); } // クエリ実行前のログ if (this.configManager.isDebugMode()) { console.log(`Executing query: ${query}`); if (params.length > 0) { console.log(`With params:`, params); } } const result = await this.mysqlConnection.executeQuery(query, params); // 結果の行数制限 const maxResults = this.configManager.getMaxResultSize(); if (Array.isArray(result.data) && result.data.length > maxResults) { result.data = result.data.slice(0, maxResults); result.truncated = true; result.totalRows = result.data.length; } return result; } catch (error) { throw new Error(`Query execution failed: ${(error as Error).message}`); } } /** * データベース一覧取得ツール */ private async getDatabases(): Promise<any> { try { // 接続状態のチェック if (!this.mysqlConnection.isConnectedToDatabase()) { throw new Error('Database not connected. Please use connect_database tool first.'); } const result = await this.mysqlConnection.executeQuery('SHOW DATABASES'); return { databases: result.data.map((row: any) => row.Database) }; } catch (error) { throw new Error(`Failed to get databases: ${(error as Error).message}`); } } /** * テーブル一覧取得ツール * @param args 引数 */ private async getTables(args: Record<string, any>): Promise<any> { try { // 接続状態のチェック if (!this.mysqlConnection.isConnectedToDatabase()) { throw new Error('Database not connected. Please use connect_database tool first.'); } const { database } = args as { database?: string }; let query = 'SHOW TABLES'; if (database) { query = `SHOW TABLES FROM \`${database}\``; } const result = await this.mysqlConnection.executeQuery(query); // 結果の形式がデータベースによって異なるため、一律の形式に変換 const tables = result.data.map((row: any) => { // 最初のカラム名を取得 const firstCol = Object.keys(row)[0]; return row[firstCol]; }); return { tables }; } catch (error) { throw new Error(`Failed to get tables: ${(error as Error).message}`); } } /** * テーブル構造取得ツール * @param args 引数 */ private async describeTable(args: Record<string, any>): Promise<any> { try { // 接続状態のチェック if (!this.mysqlConnection.isConnectedToDatabase()) { throw new Error('Database not connected. Please use connect_database tool first.'); } const { table, database } = args as { table: string; database?: string }; let tableReference = table; if (database) { tableReference = `\`${database}\`.\`${table}\``; } else { tableReference = `\`${table}\``; } // テーブルの構造を取得 const describeResult = await this.mysqlConnection.executeQuery(`DESCRIBE ${tableReference}`); // インデックス情報を取得 const indexResult = await this.mysqlConnection.executeQuery(`SHOW INDEX FROM ${tableReference}`); // 外部キー情報を取得 (information_schemaから取得) let currentDb = database; if (!currentDb) { // 現在のデータベースを取得 const dbResult = await this.mysqlConnection.executeQuery('SELECT DATABASE() as db'); currentDb = dbResult.data[0]?.db; } const fkQuery = ` SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND REFERENCED_TABLE_NAME IS NOT NULL`; const fkResult = currentDb ? await this.mysqlConnection.executeQuery(fkQuery, [currentDb, table]) : { data: [] }; return { columns: describeResult.data, indexes: indexResult.data, foreignKeys: fkResult.data }; } catch (error) { throw new Error(`Failed to describe table: ${(error as Error).message}`); } } /** * データベース接続ツール * @param args 接続引数 */ private async connectDatabase(args: Record<string, any>): Promise<any> { try { const { host, port, user, password, database, profileName } = args as { host: string; port: number; user: string; password?: string; database?: string; profileName?: string; }; // 現在の接続があれば切断 if (this.mysqlConnection.isConnectedToDatabase()) { await this.mysqlConnection.close(); } // 新しい接続設定を適用 const newConfig: MySQLConfig = { host, port, user, password: password || '', database, }; this.mysqlConnection.updateConfig(newConfig); this.configManager.updateMySQLConfig(newConfig); // 接続実行 await this.mysqlConnection.connect(); // 接続情報を保存(プロファイル名付き) const savedProfileName = this.configManager.saveCurrentConnection(profileName); return { success: true, connection: this.mysqlConnection.getConnectionInfo(), profileName: savedProfileName }; } catch (error) { console.error('Failed to connect to database:', error); throw new Error(`Database connection failed: ${(error as Error).message}`); } } /** * プロファイル名による接続ツール * @param args 引数 */ private async connectByProfile(args: Record<string, any>): Promise<any> { try { const { profileName } = args as { profileName: string }; // プロファイルの取得 const profile = this.configManager.getProfile(profileName); if (!profile) { throw new Error(`プロファイル "${profileName}" が見つかりません`); } // 現在の接続があれば切断 if (this.mysqlConnection.isConnectedToDatabase()) { await this.mysqlConnection.close(); } // プロファイルの接続設定を適用 const newConfig: MySQLConfig = { host: profile.host, port: profile.port, user: profile.user, password: profile.password, database: profile.database, }; this.mysqlConnection.updateConfig(newConfig); this.configManager.updateMySQLConfig(newConfig); // 接続実行 await this.mysqlConnection.connect(); return { success: true, connection: this.mysqlConnection.getConnectionInfo(), profileName: profile.name }; } catch (error) { console.error('Failed to connect by profile:', error); throw new Error(`プロファイル接続失敗: ${(error as Error).message}`); } } /** * データベース切断ツール */ private async disconnectDatabase(): Promise<any> { await this.mysqlConnection.close(); return { success: true, message: 'Database disconnected' }; } /** * 接続状態取得ツール */ private async getConnectionStatus(): Promise<any> { // 現在の接続情報を取得 const connectionInfo = this.mysqlConnection.isConnectedToDatabase() ? this.mysqlConnection.getConnectionInfo() : null; // 現在使用中のプロファイル名を特定(あれば) let currentProfileName: string | null = null; if (connectionInfo) { const profiles = this.configManager.getStoredConnections(); const currentProfile = profiles.find(p => p.host === connectionInfo.host && p.port === connectionInfo.port && p.user === connectionInfo.user && p.database === connectionInfo.database ); if (currentProfile) { currentProfileName = currentProfile.name; } } return { isConnected: this.mysqlConnection.isConnectedToDatabase(), connectionInfo: connectionInfo, currentProfileName: currentProfileName }; } /** * プロファイル一覧取得ツール */ private async listProfiles(): Promise<any> { const profiles = this.configManager.getStoredConnections(); return { profiles: profiles.map(p => ({ name: p.name, host: p.host, port: p.port, user: p.user, database: p.database || null })) }; } /** * プロファイル取得ツール */ private async getProfile(args: Record<string, any>): Promise<any> { const { profileName } = args as { profileName: string }; const profile = this.configManager.getProfile(profileName); if (!profile) { throw new Error(`プロファイル "${profileName}" が見つかりません`); } return { profile: { name: profile.name, host: profile.host, port: profile.port, user: profile.user, database: profile.database || null } }; } /** * プロファイル追加ツール */ private async addProfile(args: Record<string, any>): Promise<any> { const { profileName, host, port, user, password, database } = args as { profileName: string; host: string; port: number; user: string; password?: string; database?: string; }; const newProfile: NamedStoredConnection = { name: profileName, host, port, user, password: password || '', database }; // 一時的に設定を更新して保存 const originalConfig = { ...this.configManager.getMySQLConfig() }; this.configManager.updateMySQLConfig(newProfile); const savedName = this.configManager.saveCurrentConnection(profileName); // 元の設定に戻す(現在の接続には影響させない) this.configManager.updateMySQLConfig(originalConfig); return { success: true, profileName: savedName }; } /** * プロファイル削除ツール */ private async removeProfile(args: Record<string, any>): Promise<any> { const { profileName } = args as { profileName: string }; const result = this.configManager.removeProfile(profileName); return { success: result, message: result ? `プロファイル "${profileName}" を削除しました` : `プロファイル "${profileName}" は存在しません` }; } /** * 接続を閉じる */ public async close(): Promise<void> { await this.mysqlConnection.close(); console.log('MySQL MCP Server closed'); } }

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/yuki777/mysql-mcp-server'

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