# Directory Structure ``` ├── .env.example ├── .gitignore ├── .npmignore ├── Dockerfile ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── smithery.yaml ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | [](https://smithery.ai/server/@sussa3007/mysql-mcp) 2 | 3 | 4 | <a href="https://glama.ai/mcp/servers/@sussa3007/mysql-mcp"> 5 | <img width="380" height="200" src="https://glama.ai/mcp/servers/@sussa3007/mysql-mcp/badge" /> 6 | </a> 7 | 8 | # MySQL MCP Server 9 | 10 | [](https://opensource.org/licenses/MIT) 11 | 12 | A Model Context Protocol (MCP) server for MySQL databases that enables AI models to interact with MySQL databases through a structured interface. 13 | 14 | ## Overview 15 | 16 | The MySQL MCP Server provides a bridge between AI models and MySQL databases, allowing AI agents to query and analyze MySQL data. This implementation follows the Model Context Protocol specification and offers both web server and CLI modes of operation. 17 | 18 | ## Features 19 | 20 | - MySQL database connection management 21 | - SQL query execution 22 | - Table listing and structure inspection 23 | - Database listing and selection 24 | - Real-time status monitoring via SSE (Server-Sent Events) 25 | - Web interface for testing MCP tools 26 | - Support for both stdio and SSE transport methods 27 | - Docker deployment ready 28 | 29 | ## Installation 30 | 31 | ```bash 32 | # Global installation 33 | npm install -g mysql-mcp 34 | 35 | # Local installation 36 | npm install mysql-mcp 37 | ``` 38 | 39 | ## Using with AI Assistants 40 | 41 | ### Using the Published Server on Smithery.ai 42 | 43 | The MySQL MCP Server is published on Smithery.ai and can be easily used with various AI assistants: 44 | 45 | 1. **Access the server**: Visit [https://smithery.ai/server/@sussa3007/mysql-mcp](https://smithery.ai/server/@sussa3007/mysql-mcp) 46 | 47 | 2. **Configure the server**: 48 | 49 | - Set your MySQL database connection details: 50 | - MYSQL_HOST 51 | - MYSQL_PORT 52 | - MYSQL_USER 53 | - MYSQL_PASSWORD 54 | - MYSQL_DATABASE 55 | - MYSQL_READONLY (optional, set to True for read-only access) 56 | 57 | 3. **Connect with supported AI platforms**: 58 | 59 | - Anthropic Claude 60 | - Cursor AI 61 | - Windsurf 62 | - Cline 63 | - Witsy 64 | - Enconvo 65 | - Goose 66 | 67 | 4. **Authentication**: Login to Smithery.ai to save your configuration and generate authentication tokens. 68 | 69 | 5. **Use in AI prompts**: Once connected, you can utilize MySQL tools in your AI conversations by asking the assistant to perform database operations. 70 | 71 | ### Using After Local Installation 72 | 73 | To use a locally developed version: 74 | 75 | 1. Run `npm link` in your project directory 76 | 2. Configure your settings file as follows: 77 | 78 | ```json 79 | { 80 | "mcpServers": { 81 | "mysql": { 82 | "command": "node", 83 | "args": ["mysql-mcp"], 84 | "env": { 85 | "MYSQL_HOST": "localhost", 86 | "MYSQL_PORT": "3306", 87 | "MYSQL_USER": "root", 88 | "MYSQL_PASSWORD": "your_password", 89 | "MYSQL_DATABASE": "your_database", 90 | "MYSQL_READONLY": "true" 91 | } 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | ## Tools 98 | 99 | ### status 100 | 101 | Check the current database connection status. 102 | 103 | - **Inputs**: No parameters required 104 | - **Returns**: Connection status information, including host, port, database, and username if connected. 105 | 106 | ### connect 107 | 108 | Connect to a MySQL database. 109 | 110 | - **Inputs**: 111 | - host (optional string): Database server hostname or IP address 112 | - port (optional string): Database server port 113 | - user (optional string): Database username 114 | - password (optional string): Database password 115 | - database (optional string): Database name to connect to 116 | - **Returns**: Connection success message or error details. 117 | 118 | ### disconnect 119 | 120 | Close the current MySQL database connection. 121 | 122 | - **Inputs**: No parameters required 123 | - **Returns**: Disconnection success message or error details. 124 | 125 | ### query 126 | 127 | Execute an SQL query on the connected database. 128 | 129 | - **Inputs**: 130 | - sql (string): SQL query to execute 131 | - params (optional array): Parameters for prepared statements 132 | - **Returns**: Query results as JSON or error message. 133 | 134 | ### list_tables 135 | 136 | Get a list of tables in the current database. 137 | 138 | - **Inputs**: No parameters required 139 | - **Returns**: List of table names in the current database. 140 | 141 | ### describe_table 142 | 143 | Get the structure of a specific table. 144 | 145 | - **Inputs**: 146 | - table (string): Name of the table to describe 147 | - **Returns**: Table structure details including columns, types, keys, and other attributes. 148 | 149 | ### list_databases 150 | 151 | Get a list of all accessible databases on the server. 152 | 153 | - **Inputs**: No parameters required 154 | - **Returns**: List of database names available on the server. 155 | 156 | ### use_database 157 | 158 | Switch to a different database. 159 | 160 | - **Inputs**: 161 | - database (string): Name of the database to switch to 162 | - **Returns**: Confirmation message or error details. 163 | 164 | ## Keywords 165 | 166 | mysql, mcp, database, ai, model context protocol 167 | 168 | ## License 169 | 170 | MIT 171 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "outDir": "dist", 11 | "rootDir": "src", 12 | "sourceMap": true, 13 | "declaration": true 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Smithery.ai configuration 2 | startCommand: 3 | type: stdio 4 | configSchema: 5 | # JSON Schema defining the configuration options for the MCP. 6 | type: object 7 | properties: 8 | MYSQL_HOST: 9 | type: string 10 | description: MySQL server address 11 | MYSQL_PORT: 12 | type: string 13 | description: MySQL server port 14 | MYSQL_USER: 15 | type: string 16 | description: MySQL username 17 | MYSQL_PASSWORD: 18 | type: string 19 | description: MySQL password 20 | MYSQL_DATABASE: 21 | type: string 22 | description: Initial database 23 | MYSQL_READONLY: 24 | type: boolean 25 | description: Read-only mode, set true to enable 26 | commandFunction: 27 | # A function that produces the CLI command to start the MCP on stdio. 28 | |- 29 | (config) => ({ 30 | "command": "node", 31 | "args": [ 32 | "dist/index.js" 33 | ], 34 | "env": { 35 | MYSQL_HOST: config.MYSQL_HOST, 36 | MYSQL_PORT: config.MYSQL_PORT, 37 | MYSQL_USER: config.MYSQL_USER, 38 | MYSQL_PASSWORD: config.MYSQL_PASSWORD, 39 | MYSQL_DATABASE: config.MYSQL_DATABASE, 40 | MYSQL_READONLY: config.MYSQL_READONLY.toString() 41 | } 42 | }) 43 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "mysql-mcp-connect", 3 | "version": "0.1.1", 4 | "description": "MySQL MCP 서버 - Model Context Protocol로 MySQL 데이터베이스에 접근하는 도구", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "bin": { 8 | "mysql-mcp": "./dist/index.js" 9 | }, 10 | "files": [ 11 | "dist", 12 | "bin", 13 | "public" 14 | ], 15 | "scripts": { 16 | "start": "node dist/index.js", 17 | "dev": "ts-node src/index.ts", 18 | "build": "tsc", 19 | "stdio": "node dist/index.js --stdio", 20 | "stdio:dev": "ts-node src/index.ts --stdio", 21 | "prepublishOnly": "npm run build" 22 | }, 23 | "keywords": [ 24 | "mcp", 25 | "mysql", 26 | "cursor", 27 | "model-context-protocol", 28 | "database", 29 | "ai", 30 | "tool" 31 | ], 32 | "author": "sussa3007", 33 | "license": "MIT", 34 | "dependencies": { 35 | "@modelcontextprotocol/sdk": "^1.7.0", 36 | "cors": "^2.8.5", 37 | "dotenv": "^16.4.7", 38 | "express": "^4.18.2", 39 | "express-sse": "^0.5.3", 40 | "mysql2": "^3.14.0", 41 | "zod": "^3.24.2" 42 | }, 43 | "devDependencies": { 44 | "@types/content-type": "^1.1.8", 45 | "@types/cors": "^2.8.13", 46 | "@types/express": "^4.17.17", 47 | "@types/node": "^20.3.1", 48 | "ts-node": "^10.9.1", 49 | "typescript": "^5.1.3" 50 | }, 51 | "engines": { 52 | "node": ">=14.0.0" 53 | } 54 | } 55 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import { 4 | CallToolRequestSchema, 5 | ListToolsRequestSchema 6 | } from "@modelcontextprotocol/sdk/types.js"; 7 | import { createConnection, Connection, RowDataPacket } from "mysql2/promise"; 8 | 9 | // Set up logging 10 | const logger = { 11 | info: (message: string) => console.log(`[INFO] ${message}`), 12 | error: (message: string) => console.error(`[ERROR] ${message}`), 13 | debug: (message: string) => console.debug(`[DEBUG] ${message}`) 14 | }; 15 | 16 | // Query type enum for categorizing SQL operations 17 | enum QueryType { 18 | SELECT = "SELECT", 19 | INSERT = "INSERT", 20 | UPDATE = "UPDATE", 21 | DELETE = "DELETE", 22 | CREATE = "CREATE", 23 | DROP = "DROP", 24 | ALTER = "ALTER", 25 | TRUNCATE = "TRUNCATE", 26 | USE = "USE", 27 | SHOW = "SHOW", 28 | DESCRIBE = "DESCRIBE", 29 | UNKNOWN = "UNKNOWN" 30 | } 31 | 32 | // Determine if query type is a write operation 33 | function isWriteOperation(queryType: QueryType): boolean { 34 | const writeOperations = [ 35 | QueryType.INSERT, 36 | QueryType.UPDATE, 37 | QueryType.DELETE, 38 | QueryType.CREATE, 39 | QueryType.DROP, 40 | QueryType.ALTER, 41 | QueryType.TRUNCATE 42 | ]; 43 | return writeOperations.includes(queryType); 44 | } 45 | 46 | // Get query type from SQL query 47 | function getQueryType(query: string): QueryType { 48 | const firstWord = query.trim().split(/\s+/)[0].toUpperCase(); 49 | return Object.values(QueryType).includes(firstWord as QueryType) 50 | ? (firstWord as QueryType) 51 | : QueryType.UNKNOWN; 52 | } 53 | 54 | // Global connection state 55 | let connection: Connection | null = null; 56 | let connectionConfig = { 57 | host: process.env.MYSQL_HOST || "localhost", 58 | port: parseInt(process.env.MYSQL_PORT || "3306", 10), 59 | user: process.env.MYSQL_USER || "root", 60 | password: process.env.MYSQL_PASSWORD || "", 61 | database: process.env.MYSQL_DATABASE || "", 62 | readonly: process.env.MYSQL_READONLY === "true" || false 63 | }; 64 | 65 | // Initialize MCP server 66 | const server = new Server( 67 | { 68 | name: "mysql-mcp", 69 | version: "0.1.0" 70 | }, 71 | { 72 | capabilities: { 73 | tools: {} 74 | } 75 | } 76 | ); 77 | 78 | // Helper function to get current connection or throw error if not connected 79 | async function getConnection(): Promise<Connection> { 80 | if (!connection) { 81 | throw new Error( 82 | "Database not connected. Please use the 'connect' tool first." 83 | ); 84 | } 85 | return connection; 86 | } 87 | 88 | // Connect to MySQL with current config 89 | async function connectToDatabase( 90 | config = connectionConfig 91 | ): Promise<Connection> { 92 | try { 93 | if (connection) { 94 | logger.info("Closing existing connection before creating a new one"); 95 | await connection.end(); 96 | } 97 | 98 | connection = await createConnection({ 99 | host: config.host, 100 | port: config.port, 101 | user: config.user, 102 | password: config.password, 103 | database: config.database, 104 | multipleStatements: true // Allow multiple statements in one query 105 | }); 106 | 107 | connectionConfig = config; 108 | logger.info(`Connected to MySQL database at ${config.host}:${config.port}`); 109 | return connection; 110 | } catch (error) { 111 | logger.error(`Failed to connect to database: ${error}`); 112 | throw error; 113 | } 114 | } 115 | 116 | // Validate query against readonly mode 117 | async function executeQuery(sql: string, params: any[] = []): Promise<any> { 118 | const conn = await getConnection(); 119 | 120 | // Check if in readonly mode and validate query type 121 | if (connectionConfig.readonly) { 122 | const queryType = getQueryType(sql); 123 | if (isWriteOperation(queryType)) { 124 | throw new Error( 125 | "Server is in read-only mode. Write operations are not allowed." 126 | ); 127 | } 128 | } 129 | 130 | // Execute the query 131 | const [rows] = await conn.query(sql, params); 132 | return rows; 133 | } 134 | 135 | // Define available tools 136 | server.setRequestHandler(ListToolsRequestSchema, async () => { 137 | return { 138 | tools: [ 139 | { 140 | name: "status", 141 | description: "Check the current database connection status.", 142 | inputSchema: { 143 | type: "object", 144 | properties: { 145 | random_string: { 146 | type: "string", 147 | description: "Dummy parameter for no-parameter tools" 148 | } 149 | }, 150 | required: ["random_string"] 151 | } 152 | }, 153 | { 154 | name: "connect", 155 | description: "Connect to a MySQL database.", 156 | inputSchema: { 157 | type: "object", 158 | properties: { 159 | host: { 160 | type: "string", 161 | description: "Database server hostname or IP address" 162 | }, 163 | port: { type: "string", description: "Database server port" }, 164 | user: { type: "string", description: "Database username" }, 165 | password: { type: "string", description: "Database password" }, 166 | database: { 167 | type: "string", 168 | description: "Database name to connect to" 169 | } 170 | } 171 | } 172 | }, 173 | { 174 | name: "disconnect", 175 | description: "Close the current MySQL database connection.", 176 | inputSchema: { 177 | type: "object", 178 | properties: { 179 | random_string: { 180 | type: "string", 181 | description: "Dummy parameter for no-parameter tools" 182 | } 183 | }, 184 | required: ["random_string"] 185 | } 186 | }, 187 | { 188 | name: "query", 189 | description: "Execute an SQL query on the connected database.", 190 | inputSchema: { 191 | type: "object", 192 | properties: { 193 | sql: { type: "string", description: "SQL query to execute" }, 194 | params: { 195 | type: "array", 196 | description: "Parameters for prepared statements", 197 | items: { type: "string" } 198 | } 199 | }, 200 | required: ["sql"] 201 | } 202 | }, 203 | { 204 | name: "list_tables", 205 | description: "Get a list of tables in the current database.", 206 | inputSchema: { 207 | type: "object", 208 | properties: { 209 | random_string: { 210 | type: "string", 211 | description: "Dummy parameter for no-parameter tools" 212 | } 213 | }, 214 | required: ["random_string"] 215 | } 216 | }, 217 | { 218 | name: "describe_table", 219 | description: "Get the structure of a specific table.", 220 | inputSchema: { 221 | type: "object", 222 | properties: { 223 | table: { 224 | type: "string", 225 | description: "Name of the table to describe" 226 | } 227 | }, 228 | required: ["table"] 229 | } 230 | }, 231 | { 232 | name: "list_databases", 233 | description: "Get a list of all accessible databases on the server.", 234 | inputSchema: { 235 | type: "object", 236 | properties: { 237 | random_string: { 238 | type: "string", 239 | description: "Dummy parameter for no-parameter tools" 240 | } 241 | }, 242 | required: ["random_string"] 243 | } 244 | }, 245 | { 246 | name: "use_database", 247 | description: "Switch to a different database.", 248 | inputSchema: { 249 | type: "object", 250 | properties: { 251 | database: { 252 | type: "string", 253 | description: "Name of the database to switch to" 254 | } 255 | }, 256 | required: ["database"] 257 | } 258 | }, 259 | { 260 | name: "set_readonly", 261 | description: "Enable or disable read-only mode", 262 | inputSchema: { 263 | type: "object", 264 | properties: { 265 | readonly: { 266 | type: "boolean", 267 | description: 268 | "Set to true to enable read-only mode, false to disable" 269 | } 270 | }, 271 | required: ["readonly"] 272 | } 273 | } 274 | ] 275 | }; 276 | }); 277 | 278 | // Handle tool calls 279 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 280 | try { 281 | switch (request.params.name) { 282 | case "status": { 283 | if (!connection) { 284 | return { 285 | content: [ 286 | { 287 | type: "text", 288 | text: JSON.stringify( 289 | { 290 | connected: false, 291 | message: 292 | "Database not connected. Need to use 'connect' tool after checking current environment variable information. The password is sensitive information and is stored in an environment variable, so it is not required in the request parameters.", 293 | host: connectionConfig.host, 294 | port: connectionConfig.port, 295 | user: connectionConfig.user, 296 | database: connectionConfig.database, 297 | readonly: connectionConfig.readonly 298 | }, 299 | null, 300 | 2 301 | ) 302 | } 303 | ], 304 | isError: false 305 | }; 306 | } 307 | 308 | return { 309 | content: [ 310 | { 311 | type: "text", 312 | text: JSON.stringify( 313 | { 314 | connected: true, 315 | host: connectionConfig.host, 316 | port: connectionConfig.port, 317 | user: connectionConfig.user, 318 | database: connectionConfig.database, 319 | threadId: connection.threadId, 320 | readonly: connectionConfig.readonly 321 | }, 322 | null, 323 | 2 324 | ) 325 | } 326 | ], 327 | isError: false 328 | }; 329 | } 330 | 331 | case "connect": { 332 | const args = request.params.arguments || {}; 333 | const newConfig = { 334 | host: (args.host as string) || connectionConfig.host, 335 | port: parseInt( 336 | (args.port as string) || connectionConfig.port.toString() 337 | ), 338 | user: (args.user as string) || connectionConfig.user, 339 | password: (args.password as string) || connectionConfig.password, 340 | database: (args.database as string) || connectionConfig.database, 341 | readonly: connectionConfig.readonly // Maintain existing readonly setting 342 | }; 343 | 344 | try { 345 | await connectToDatabase(newConfig); 346 | } catch (error) { 347 | return { 348 | content: [ 349 | { 350 | type: "text", 351 | text: JSON.stringify( 352 | { 353 | connected: false, 354 | message: 355 | "Database connection failed, please request with new information or check the environment variable information." 356 | }, 357 | null, 358 | 2 359 | ) 360 | } 361 | ], 362 | isError: true 363 | }; 364 | } 365 | 366 | return { 367 | content: [ 368 | { 369 | type: "text", 370 | text: `Successfully connected to MySQL at ${newConfig.host}:${ 371 | newConfig.port 372 | }, database: ${newConfig.database}, read-only mode: ${ 373 | newConfig.readonly ? "enabled" : "disabled" 374 | }` 375 | } 376 | ], 377 | isError: false 378 | }; 379 | } 380 | 381 | case "disconnect": { 382 | if (!connection) { 383 | return { 384 | content: [{ type: "text", text: "Not connected to any database" }], 385 | isError: false 386 | }; 387 | } 388 | 389 | await connection.end(); 390 | connection = null; 391 | 392 | return { 393 | content: [ 394 | { 395 | type: "text", 396 | text: "Successfully disconnected from database" 397 | } 398 | ], 399 | isError: false 400 | }; 401 | } 402 | 403 | case "query": { 404 | try { 405 | const sql = request.params.arguments?.sql as string; 406 | const params = (request.params.arguments?.params as any[]) || []; 407 | 408 | // Execute query with validation 409 | const rows = await executeQuery(sql, params); 410 | 411 | return { 412 | content: [{ type: "text", text: JSON.stringify(rows, null, 2) }], 413 | isError: false 414 | }; 415 | } catch (error) { 416 | return { 417 | content: [ 418 | { 419 | type: "text", 420 | text: 421 | error instanceof Error 422 | ? error.message 423 | : "Unknown error occurred" 424 | } 425 | ], 426 | isError: true 427 | }; 428 | } 429 | } 430 | 431 | case "list_tables": { 432 | try { 433 | const rows = await executeQuery("SHOW TABLES"); 434 | return { 435 | content: [{ type: "text", text: JSON.stringify(rows, null, 2) }], 436 | isError: false 437 | }; 438 | } catch (error) { 439 | return { 440 | content: [ 441 | { 442 | type: "text", 443 | text: 444 | error instanceof Error 445 | ? error.message 446 | : "Unknown error occurred" 447 | } 448 | ], 449 | isError: true 450 | }; 451 | } 452 | } 453 | 454 | case "describe_table": { 455 | try { 456 | const tableName = request.params.arguments?.table as string; 457 | 458 | if (!tableName) { 459 | throw new Error("Table name is required"); 460 | } 461 | 462 | const rows = await executeQuery("DESCRIBE ??", [tableName]); 463 | 464 | return { 465 | content: [{ type: "text", text: JSON.stringify(rows, null, 2) }], 466 | isError: false 467 | }; 468 | } catch (error) { 469 | return { 470 | content: [ 471 | { 472 | type: "text", 473 | text: 474 | error instanceof Error 475 | ? error.message 476 | : "Unknown error occurred" 477 | } 478 | ], 479 | isError: true 480 | }; 481 | } 482 | } 483 | 484 | case "list_databases": { 485 | try { 486 | const rows = await executeQuery("SHOW DATABASES"); 487 | return { 488 | content: [{ type: "text", text: JSON.stringify(rows, null, 2) }], 489 | isError: false 490 | }; 491 | } catch (error) { 492 | return { 493 | content: [ 494 | { 495 | type: "text", 496 | text: 497 | error instanceof Error 498 | ? error.message 499 | : "Unknown error occurred" 500 | } 501 | ], 502 | isError: true 503 | }; 504 | } 505 | } 506 | 507 | case "use_database": { 508 | try { 509 | const dbName = request.params.arguments?.database as string; 510 | 511 | if (!dbName) { 512 | throw new Error("Database name is required"); 513 | } 514 | 515 | await executeQuery("USE ??", [dbName]); 516 | connectionConfig.database = dbName; 517 | 518 | return { 519 | content: [ 520 | { 521 | type: "text", 522 | text: `Successfully switched to database: ${dbName}` 523 | } 524 | ], 525 | isError: false 526 | }; 527 | } catch (error) { 528 | return { 529 | content: [ 530 | { 531 | type: "text", 532 | text: 533 | error instanceof Error 534 | ? error.message 535 | : "Unknown error occurred" 536 | } 537 | ], 538 | isError: true 539 | }; 540 | } 541 | } 542 | 543 | case "set_readonly": { 544 | try { 545 | const readonly = request.params.arguments?.readonly as boolean; 546 | 547 | if (readonly === undefined) { 548 | throw new Error("readonly parameter is required"); 549 | } 550 | 551 | connectionConfig.readonly = readonly; 552 | 553 | return { 554 | content: [ 555 | { 556 | type: "text", 557 | text: `Read-only mode ${readonly ? "enabled" : "disabled"}` 558 | } 559 | ], 560 | isError: false 561 | }; 562 | } catch (error) { 563 | return { 564 | content: [ 565 | { 566 | type: "text", 567 | text: 568 | error instanceof Error 569 | ? error.message 570 | : "Unknown error occurred" 571 | } 572 | ], 573 | isError: true 574 | }; 575 | } 576 | } 577 | 578 | default: 579 | throw new Error(`Unknown tool: ${request.params.name}`); 580 | } 581 | } catch (error) { 582 | logger.error(`Error handling request: ${error}`); 583 | return { 584 | content: [ 585 | { 586 | type: "text", 587 | text: 588 | error instanceof Error ? error.message : "Unknown error occurred" 589 | } 590 | ], 591 | isError: true 592 | }; 593 | } 594 | }); 595 | 596 | async function main() { 597 | logger.info("Starting MySQL MCP server..."); 598 | const transport = new StdioServerTransport(); 599 | await server.connect(transport); 600 | logger.info("Server connected to transport"); 601 | } 602 | 603 | // Handle termination signals 604 | process.once("SIGTERM", () => { 605 | logger.info("SIGTERM received, closing server"); 606 | 607 | // Close database connection if open 608 | if (connection) { 609 | connection.end().catch((err) => { 610 | logger.error(`Error closing database connection: ${err}`); 611 | }); 612 | } 613 | 614 | server.close().then(() => { 615 | logger.info("Server closed, exiting"); 616 | process.exit(0); 617 | }); 618 | }); 619 | 620 | process.once("SIGINT", () => { 621 | logger.info("SIGINT received, closing server"); 622 | 623 | // Close database connection if open 624 | if (connection) { 625 | connection.end().catch((err) => { 626 | logger.error(`Error closing database connection: ${err}`); 627 | }); 628 | } 629 | 630 | server.close().then(() => { 631 | logger.info("Server closed, exiting"); 632 | process.exit(0); 633 | }); 634 | }); 635 | 636 | main().catch((error) => { 637 | logger.error( 638 | error instanceof Error ? error.message : "Unknown error occurred" 639 | ); 640 | process.exit(1); 641 | }); 642 | ```