MySQL MCP Server

by xiangma9712
Verified
  • src
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { createConnection } from 'mysql2/promise'; import * as dotenv from 'dotenv'; dotenv.config(); const server = new Server( { name: "mcp-mysql", version: "0.1.0", }, { capabilities: { tools: {}, }, }, ); async function initializeConnection() { return await createConnection({ host: process.env.MYSQL_HOST ?? 'localhost', port: parseInt(process.env.MYSQL_PORT ?? '3306'), user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DATABASE, }); } server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "query", description: "読み取り専用のSQLクエリを実行します。", inputSchema: { type: "object", properties: { sql: { type: "string" }, }, required: ["sql"], }, }, { name: "test_execute", description: "SQLが実行可能か確認し、最後にロールバックします。", inputSchema: { type: "object", properties: { sql: { type: "string" }, }, required: ["sql"], }, }, { name: "list_tables", description: "データベース内のテーブル一覧を取得します。", inputSchema: { type: "object", }, }, { name: "describe_table", description: "テーブルのカラム情報を取得します。", inputSchema: { type: "object", properties: { tableName: { type: "string" }, }, required: ["tableName"], }, } ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const connection = await initializeConnection(); try { switch (request.params.name) { case "query": { const sql = request.params.arguments?.sql as string; await connection.query('START TRANSACTION READ ONLY'); const [rows] = await connection.query(sql); return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }], isError: false, }; } case "test_execute": { const sql = request.params.arguments?.sql as string; await connection.query('START TRANSACTION'); try { await connection.query(sql); } catch (error) { return { content: [{ type: "text", text: `SQLの実行に失敗しました。error: ${error}` }], isError: true, }; } finally { await connection.query('ROLLBACK'); } return { content: [{ type: "text", text: "更新SQLクエリが実行可能です。" }], isError: false, }; } case "list_tables": { const [rows] = await connection.query( "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?", [process.env.MYSQL_DATABASE] ); return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }], isError: false, }; } case "describe_table": { const tableName = request.params.arguments?.tableName as string; const [rows] = await connection.query( "SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?", [process.env.MYSQL_DATABASE, tableName] ); return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }], isError: false, }; } default: throw new Error(`Unknown tool: ${request.params.name}`); } } catch (error) { return { content: [{ type: "text", text: error instanceof Error ? error.message : "不明なエラーが発生しました" }], isError: true, }; } finally { try { await connection.end(); } catch (error) { return { content: [{ type: "text", text: `connection close failed, error: ${error}` }], isError: true, } } } }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } process.once("SIGTERM", () => { console.log(`SIGTERM received, closing server`); server.close().then(() => { console.log(`server closed, exiting`); process.exit(0); }); }); process.once("SIGINT", () => { console.log(`SIGINT received, closing server`); server.close().then(() => { console.log(`server closed, exiting`); process.exit(0); }); }); main().catch(error => { console.error(error instanceof Error ? error.message : "不明なエラーが発生しました"); process.exit(1); });