Merge pull request #5 from executeautomation/Added-Support-for-MySql-Database

Add MySQL support to MCP Database Server
This commit is contained in:
ExecuteAutomation
2025-05-31 08:38:16 +12:00
committed by GitHub
9 changed files with 367 additions and 5 deletions

View File

@@ -68,6 +68,25 @@ node dist/src/index.js --postgresql --host localhost --database sample_db --user
node dist/src/index.js --postgresql --host dbserver.example.com --database sample_db --user appuser --password Secure123! --port 5433 --ssl true
```
## MySQL Connection Options
| Option | Description | Default | Required |
|--------|-------------|---------|----------|
| `--mysql` | Specifies MySQL mode | - | Yes |
| `--host` | MySQL hostname or IP | - | Yes |
| `--database` | Database name | - | Yes |
| `--user` | MySQL username | - | No |
| `--password` | MySQL password | - | No |
| `--port` | MySQL port | 3306 | No |
| `--ssl` | Use SSL connection (true/false or object) | false | No |
| `--connection-timeout` | Connection timeout in ms | 30000 | No |
### Example
```bash
node dist/src/index.js --mysql --host localhost --database sample_db --port 3306 --user root --password secret
```
## Environment Variables
Instead of specifying sensitive credentials on the command line, you can use environment variables:

View File

@@ -53,4 +53,12 @@
- Table management (CREATE, ALTER, DROP)
- Schema introspection
- MCP integration for Claude Desktop
- Node.js-based implementation for cross-platform support
- Node.js-based implementation for cross-platform support
## 1.1.0 (2024-05-30)
### Features
- Added MySQL database support (read/write/query, schema, etc.)
- Support for passing MySQL port via CLI and config
- Improved port validation and debug logging for MySQL
- Updated documentation and examples for MySQL and port usage

View File

@@ -14,7 +14,7 @@ import sqlite3 from "sqlite3";
const server = new Server(
{
name: "executeautomation/database-server",
version: "1.0.0",
version: "1.1.0",
},
{
capabilities: {

100
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"dependencies": {
"@modelcontextprotocol/sdk": "1.9.0",
"mssql": "11.0.1",
"mysql2": "^3.14.1",
"pg": "^8.11.3",
"sqlite3": "5.1.7"
},
@@ -639,6 +640,14 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1124,6 +1133,14 @@
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"optional": true
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -1645,6 +1662,14 @@
"node": ">=8"
}
},
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -2032,6 +2057,11 @@
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
},
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
},
"node_modules/is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
@@ -2180,12 +2210,31 @@
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="
},
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true
},
"node_modules/lru.min": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
"integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/make-fetch-happen": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz",
@@ -2545,6 +2594,44 @@
"node": ">=18"
}
},
"node_modules/mysql2": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.1.tgz",
"integrity": "sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==",
"dependencies": {
"aws-ssl-profiles": "^1.1.1",
"denque": "^2.1.0",
"generate-function": "^2.3.1",
"iconv-lite": "^0.6.3",
"long": "^5.2.1",
"lru.min": "^1.0.0",
"named-placeholders": "^1.1.3",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/named-placeholders": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
"dependencies": {
"lru-cache": "^7.14.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/named-placeholders/node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"engines": {
"node": ">=12"
}
},
"node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
@@ -3413,6 +3500,11 @@
"node": ">= 18"
}
},
"node_modules/seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
},
"node_modules/serve-static": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
@@ -3688,6 +3780,14 @@
}
}
},
"node_modules/sqlstring": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@executeautomation/database-server",
"version": "1.0.2",
"version": "1.1.0",
"description": "MCP server for interacting with SQLite and SQL Server databases by ExecuteAutomation",
"license": "MIT",
"author": "ExecuteAutomation, Ltd (https://executeautomation.com)",
@@ -25,6 +25,7 @@
"dependencies": {
"@modelcontextprotocol/sdk": "1.9.0",
"mssql": "11.0.1",
"mysql2": "^3.14.1",
"pg": "^8.11.3",
"sqlite3": "5.1.7"
},

View File

@@ -2,7 +2,7 @@
# MCP Database Server
This MCP (Model Context Protocol) server provides database access capabilities to Claude, supporting SQLite, SQL Server, and PostgreSQL databases.
This MCP (Model Context Protocol) server provides database access capabilities to Claude, supporting SQLite, SQL Server, PostgreSQL, and MySQL databases.
## Installation
@@ -92,6 +92,25 @@ Optional parameters:
- `--ssl`: Enable SSL connection (true/false)
- `--connection-timeout`: Connection timeout in milliseconds (default: 30000)
### MySQL Database
To use with a MySQL database:
```
node dist/src/index.js --mysql --host <host-name> --database <database-name> --port <port> [--user <username> --password <password>]
```
Required parameters:
- `--host`: MySQL host name or IP address
- `--database`: Name of the database
- `--port`: Port number (default: 3306)
Optional parameters:
- `--user`: Username for MySQL authentication
- `--password`: Password for MySQL authentication
- `--ssl`: Enable SSL connection (true/false or object)
- `--connection-timeout`: Connection timeout in milliseconds (default: 30000)
## Configuring Claude Desktop
### Direct Usage Configuration
@@ -132,6 +151,19 @@ If you installed the package globally, configure Claude Desktop with:
"--user", "your-username",
"--password", "your-password"
]
},
"mysql": {
"command": "npx",
"args": [
"-y",
"@executeautomation/database-server",
"--mysql",
"--host", "your-host-name",
"--database", "your-database-name",
"--port", "3306",
"--user", "your-username",
"--password", "your-password"
]
}
}
}
@@ -172,6 +204,18 @@ For local development, configure Claude Desktop to use your locally built versio
"--user", "your-username",
"--password", "your-password"
]
},
"mysql": {
"command": "node",
"args": [
"/absolute/path/to/mcp-database-server/dist/src/index.js",
"--mysql",
"--host", "your-host-name",
"--database", "your-database-name",
"--port", "3306",
"--user", "your-username",
"--password", "your-password"
]
}
}
}

View File

@@ -54,6 +54,7 @@ export interface DbAdapter {
import { SqliteAdapter } from './sqlite-adapter.js';
import { SqlServerAdapter } from './sqlserver-adapter.js';
import { PostgresqlAdapter } from './postgresql-adapter.js';
import { MysqlAdapter } from './mysql-adapter.js';
/**
* Factory function to create the appropriate database adapter
@@ -72,6 +73,8 @@ export function createDbAdapter(type: string, connectionInfo: any): DbAdapter {
case 'postgresql':
case 'postgres':
return new PostgresqlAdapter(connectionInfo);
case 'mysql':
return new MysqlAdapter(connectionInfo);
default:
throw new Error(`Unsupported database type: ${type}`);
}

145
src/db/mysql-adapter.ts Normal file
View File

@@ -0,0 +1,145 @@
import { DbAdapter } from "./adapter.js";
import mysql from "mysql2/promise";
/**
* MySQL database adapter implementation
*/
export class MysqlAdapter implements DbAdapter {
private connection: mysql.Connection | null = null;
private config: mysql.ConnectionOptions;
private host: string;
private database: string;
constructor(connectionInfo: {
host: string;
database: string;
user?: string;
password?: string;
port?: number;
ssl?: boolean | object;
connectionTimeout?: number;
}) {
this.host = connectionInfo.host;
this.database = connectionInfo.database;
this.config = {
host: connectionInfo.host,
database: connectionInfo.database,
port: connectionInfo.port || 3306,
user: connectionInfo.user,
password: connectionInfo.password,
connectTimeout: connectionInfo.connectionTimeout || 30000,
multipleStatements: true,
};
if (typeof connectionInfo.ssl === 'object' || typeof connectionInfo.ssl === 'string') {
this.config.ssl = connectionInfo.ssl;
} else if (connectionInfo.ssl === true) {
this.config.ssl = {};
}
// Validate port
if (connectionInfo.port && typeof connectionInfo.port !== 'number') {
const parsedPort = parseInt(connectionInfo.port as any, 10);
if (isNaN(parsedPort)) {
throw new Error(`Invalid port value for MySQL: ${connectionInfo.port}`);
}
this.config.port = parsedPort;
}
// Log the port for debugging
console.error(`[DEBUG] MySQL connection will use port: ${this.config.port}`);
}
/**
* Initialize MySQL connection
*/
async init(): Promise<void> {
try {
console.error(`[INFO] Connecting to MySQL: ${this.host}, Database: ${this.database}`);
this.connection = await mysql.createConnection(this.config);
console.error(`[INFO] MySQL connection established successfully`);
} catch (err) {
console.error(`[ERROR] MySQL connection error: ${(err as Error).message}`);
throw new Error(`Failed to connect to MySQL: ${(err as Error).message}`);
}
}
/**
* Execute a SQL query and get all results
*/
async all(query: string, params: any[] = []): Promise<any[]> {
if (!this.connection) {
throw new Error("Database not initialized");
}
try {
const [rows] = await this.connection.execute(query, params);
return Array.isArray(rows) ? rows : [];
} catch (err) {
throw new Error(`MySQL query error: ${(err as Error).message}`);
}
}
/**
* Execute a SQL query that modifies data
*/
async run(query: string, params: any[] = []): Promise<{ changes: number, lastID: number }> {
if (!this.connection) {
throw new Error("Database not initialized");
}
try {
const [result]: any = await this.connection.execute(query, params);
const changes = result.affectedRows || 0;
const lastID = result.insertId || 0;
return { changes, lastID };
} catch (err) {
throw new Error(`MySQL query error: ${(err as Error).message}`);
}
}
/**
* Execute multiple SQL statements
*/
async exec(query: string): Promise<void> {
if (!this.connection) {
throw new Error("Database not initialized");
}
try {
await this.connection.query(query);
} catch (err) {
throw new Error(`MySQL batch error: ${(err as Error).message}`);
}
}
/**
* Close the database connection
*/
async close(): Promise<void> {
if (this.connection) {
await this.connection.end();
this.connection = null;
}
}
/**
* Get database metadata
*/
getMetadata(): { name: string; type: string; server: string; database: string } {
return {
name: "MySQL",
type: "mysql",
server: this.host,
database: this.database,
};
}
/**
* Get database-specific query for listing tables
*/
getListTablesQuery(): string {
return "SHOW TABLES";
}
/**
* Get database-specific query for describing a table
*/
getDescribeTableQuery(tableName: string): string {
return `DESCRIBE \`${tableName}\``;
}
}

View File

@@ -28,7 +28,7 @@ const logger = {
const server = new Server(
{
name: "executeautomation/database-server",
version: "1.0.1",
version: "1.1.0",
},
{
capabilities: {
@@ -45,6 +45,7 @@ if (args.length === 0) {
logger.error("Usage for SQLite: node index.js <database_file_path>");
logger.error("Usage for SQL Server: node index.js --sqlserver --server <server> --database <database> [--user <user> --password <password>]");
logger.error("Usage for PostgreSQL: node index.js --postgresql --host <host> --database <database> [--user <user> --password <password> --port <port>]");
logger.error("Usage for MySQL: node index.js --mysql --host <host> --database <database> [--user <user> --password <password> --port <port>]");
process.exit(1);
}
@@ -120,6 +121,45 @@ else if (args.includes('--postgresql') || args.includes('--postgres')) {
logger.error("Error: PostgreSQL requires --host and --database parameters");
process.exit(1);
}
}
// Check if using MySQL
else if (args.includes('--mysql')) {
dbType = 'mysql';
connectionInfo = {
host: '',
database: '',
user: undefined,
password: undefined,
port: undefined,
ssl: undefined,
connectionTimeout: undefined
};
// Parse MySQL connection parameters
for (let i = 0; i < args.length; i++) {
if (args[i] === '--host' && i + 1 < args.length) {
connectionInfo.host = args[i + 1];
} else if (args[i] === '--database' && i + 1 < args.length) {
connectionInfo.database = args[i + 1];
} else if (args[i] === '--user' && i + 1 < args.length) {
connectionInfo.user = args[i + 1];
} else if (args[i] === '--password' && i + 1 < args.length) {
connectionInfo.password = args[i + 1];
} else if (args[i] === '--port' && i + 1 < args.length) {
connectionInfo.port = parseInt(args[i + 1], 10);
} else if (args[i] === '--ssl' && i + 1 < args.length) {
const sslVal = args[i + 1];
if (sslVal === 'true') connectionInfo.ssl = true;
else if (sslVal === 'false') connectionInfo.ssl = false;
else connectionInfo.ssl = sslVal;
} else if (args[i] === '--connection-timeout' && i + 1 < args.length) {
connectionInfo.connectionTimeout = parseInt(args[i + 1], 10);
}
}
// Validate MySQL connection info
if (!connectionInfo.host || !connectionInfo.database) {
logger.error("Error: MySQL requires --host and --database parameters");
process.exit(1);
}
} else {
// SQLite mode (default)
dbType = 'sqlite';
@@ -178,6 +218,8 @@ async function runServer() {
logger.info(`Server: ${connectionInfo.server}, Database: ${connectionInfo.database}`);
} else if (dbType === 'postgresql') {
logger.info(`Host: ${connectionInfo.host}, Database: ${connectionInfo.database}`);
} else if (dbType === 'mysql') {
logger.info(`Host: ${connectionInfo.host}, Database: ${connectionInfo.database}`);
}
// Initialize the database