easy-mysql-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # easy-mysql-mcp
2
+
3
+ A lightweight [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that lets AI assistants inspect and query a MySQL database through a safe, structured tool interface.
4
+
5
+ This project uses Node.js, TypeScript, the official MCP SDK, and `mysql2/promise`. It runs over stdio, so it can be used directly by MCP clients such as Claude Desktop.
6
+
7
+ ## Features
8
+
9
+ - MySQL connection pooling powered by `mysql2/promise`
10
+ - Read-only query tool for data retrieval
11
+ - Execute tool for data modification statements
12
+ - Schema discovery tools for tables, views, indexes, and triggers
13
+ - Query plan inspection with `EXPLAIN`
14
+ - Current user and privilege inspection
15
+ - stdout protection to prevent non-MCP logs from polluting the stdio protocol
16
+
17
+ ## Requirements
18
+
19
+ - Node.js 18 or newer
20
+ - npm
21
+ - A reachable MySQL-compatible database
22
+
23
+ ## Installation
24
+
25
+ Run the server directly with `npx`:
26
+
27
+ ```bash
28
+ npx -y easy-mysql-mcp
29
+ ```
30
+
31
+ For local development after cloning the repository:
32
+
33
+ ```bash
34
+ cd easy-mysql-mcp
35
+ npm install
36
+ npm run build
37
+ ```
38
+
39
+ ## Configuration
40
+
41
+ Configure the server with environment variables. You can provide them through your MCP client configuration or by creating a local `.env` file.
42
+
43
+ | Variable | Required | Default | Description |
44
+ | --- | --- | --- | --- |
45
+ | `MYSQL_HOST` | Yes | - | MySQL host name or IP address |
46
+ | `MYSQL_PORT` | No | `3306` | MySQL port |
47
+ | `MYSQL_USER` | Yes | - | MySQL user name |
48
+ | `MYSQL_PASSWORD` | Yes | - | MySQL password |
49
+ | `MYSQL_DATABASE` | Yes | - | Default database/schema |
50
+ | `MYSQL_CONNECTION_LIMIT` | No | `10` | Maximum number of active pool connections |
51
+ | `MYSQL_MAX_IDLE` | No | `10` | Maximum number of idle pool connections |
52
+ | `MYSQL_IDLE_TIMEOUT` | No | `60000` | Idle connection timeout in milliseconds |
53
+ | `MYSQL_QUEUE_LIMIT` | No | `0` | Maximum queued connection requests, where `0` means unlimited |
54
+ | `MYSQL_WAIT_FOR_CONNECTIONS` | No | `true` | Whether the pool waits when all connections are busy |
55
+ | `MYSQL_ENABLE_KEEP_ALIVE` | No | `true` | Whether TCP keep-alive is enabled |
56
+ | `MYSQL_KEEP_ALIVE_INITIAL_DELAY` | No | `0` | Initial TCP keep-alive delay in milliseconds |
57
+
58
+ Example `.env`:
59
+
60
+ ```env
61
+ MYSQL_HOST=localhost
62
+ MYSQL_PORT=3306
63
+ MYSQL_USER=root
64
+ MYSQL_PASSWORD=your_password
65
+ MYSQL_DATABASE=your_database
66
+ ```
67
+
68
+ ## Usage
69
+
70
+ Configure your MCP client to launch the package through `npx`.
71
+
72
+ For local development, build the TypeScript source first:
73
+
74
+ ```bash
75
+ npm run build
76
+ ```
77
+
78
+ Start the MCP server:
79
+
80
+ ```bash
81
+ npm start
82
+ ```
83
+
84
+ The server communicates over stdio and is normally launched by an MCP client rather than run manually.
85
+
86
+ ## Claude Desktop Example
87
+
88
+ Add the server to your `claude_desktop_config.json`:
89
+
90
+ ```json
91
+ {
92
+ "mcpServers": {
93
+ "easy-mysql-mcp": {
94
+ "command": "npx",
95
+ "args": ["-y", "easy-mysql-mcp"],
96
+ "env": {
97
+ "MYSQL_HOST": "localhost",
98
+ "MYSQL_PORT": "3306",
99
+ "MYSQL_USER": "root",
100
+ "MYSQL_PASSWORD": "your_password",
101
+ "MYSQL_DATABASE": "your_database"
102
+ }
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ Restart Claude Desktop after updating the configuration.
109
+
110
+ ## Available Tools
111
+
112
+ | Tool | Description |
113
+ | --- | --- |
114
+ | `mysql_query` | Execute a SQL query intended for data retrieval, such as `SELECT` |
115
+ | `mysql_execute` | Execute a data modification statement, such as `INSERT`, `UPDATE`, or `DELETE` |
116
+ | `explain_query` | Run `EXPLAIN` for a SQL query and return the execution plan |
117
+ | `list_tables` | List base tables in the current database, including approximate row counts and comments |
118
+ | `list_views` | List views in the current database |
119
+ | `describe_table` | Show column information for one or more tables |
120
+ | `describe_index` | Show indexes for a table |
121
+ | `list_triggers` | List triggers in the current database |
122
+ | `get_current_privileges` | Show the current MySQL user and grants |
123
+
124
+ ## Security Notes
125
+
126
+ - Use a dedicated MySQL user with the minimum permissions your assistant needs.
127
+ - Prefer read-only database credentials if you only need inspection and reporting.
128
+ - Be careful with `mysql_execute`, because it can modify data.
129
+ - Do not commit `.env` files or real database credentials to GitHub.
130
+ - Review generated SQL before running it against production data.
131
+
132
+ ## Development
133
+
134
+ ```bash
135
+ npm run dev
136
+ ```
137
+
138
+ This runs TypeScript in watch mode.
139
+
140
+ To create a production build:
141
+
142
+ ```bash
143
+ npm run build
144
+ ```
145
+
146
+ ## Project Structure
147
+
148
+ ```text
149
+ src/
150
+ db.ts MySQL pool and query helpers
151
+ index.ts MCP server and tool registration
152
+ proxy.ts stdout protection for stdio-based MCP transport
153
+ ```
154
+
155
+ ## License
156
+
157
+ ISC
package/build/db.js ADDED
@@ -0,0 +1,30 @@
1
+ import mysql from 'mysql2/promise';
2
+ import dotenv from 'dotenv';
3
+ dotenv.config();
4
+ const { MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, MYSQL_CONNECTION_LIMIT, MYSQL_MAX_IDLE, MYSQL_IDLE_TIMEOUT, MYSQL_QUEUE_LIMIT, MYSQL_WAIT_FOR_CONNECTIONS, MYSQL_ENABLE_KEEP_ALIVE, MYSQL_KEEP_ALIVE_INITIAL_DELAY, } = process.env;
5
+ if (!MYSQL_HOST || !MYSQL_USER || !MYSQL_PASSWORD || !MYSQL_DATABASE) {
6
+ console.error('Missing required environment variables for MySQL connection.');
7
+ process.exit(1);
8
+ }
9
+ export const pool = mysql.createPool({
10
+ host: MYSQL_HOST,
11
+ port: MYSQL_PORT ? parseInt(MYSQL_PORT) : 3306,
12
+ user: MYSQL_USER,
13
+ password: MYSQL_PASSWORD,
14
+ database: MYSQL_DATABASE,
15
+ waitForConnections: MYSQL_WAIT_FOR_CONNECTIONS !== 'false',
16
+ connectionLimit: MYSQL_CONNECTION_LIMIT ? parseInt(MYSQL_CONNECTION_LIMIT) : 10,
17
+ maxIdle: MYSQL_MAX_IDLE ? parseInt(MYSQL_MAX_IDLE) : 10,
18
+ idleTimeout: MYSQL_IDLE_TIMEOUT ? parseInt(MYSQL_IDLE_TIMEOUT) : 60000,
19
+ queueLimit: MYSQL_QUEUE_LIMIT ? parseInt(MYSQL_QUEUE_LIMIT) : 0,
20
+ enableKeepAlive: MYSQL_ENABLE_KEEP_ALIVE !== 'false',
21
+ keepAliveInitialDelay: MYSQL_KEEP_ALIVE_INITIAL_DELAY ? parseInt(MYSQL_KEEP_ALIVE_INITIAL_DELAY) : 0,
22
+ });
23
+ export async function query(sql, params) {
24
+ const [rows] = await pool.query(sql, params);
25
+ return rows;
26
+ }
27
+ export async function execute(sql, params) {
28
+ const [result] = await pool.execute(sql, params);
29
+ return result;
30
+ }
package/build/index.js ADDED
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ import './proxy.js';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { z } from 'zod';
6
+ import * as db from './db.js';
7
+ // Initialize MCP Server
8
+ const server = new McpServer({
9
+ name: 'easy-mysql-mcp',
10
+ version: '1.0.0',
11
+ });
12
+ // --- Register Tools ---
13
+ server.registerTool('mysql_query', {
14
+ description: 'Execute a read-only SQL query (e.g., SELECT). Use this for data retrieval.',
15
+ inputSchema: z.object({
16
+ sql: z.string().describe('The SQL query to execute.'),
17
+ }),
18
+ }, async ({ sql }) => {
19
+ const results = await db.query(sql);
20
+ return {
21
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
22
+ };
23
+ });
24
+ server.registerTool('mysql_execute', {
25
+ description: 'Execute a data modification SQL statement (e.g., INSERT, UPDATE, DELETE).',
26
+ inputSchema: z.object({
27
+ sql: z.string().describe('The SQL statement to execute.'),
28
+ params: z.array(z.any()).optional().describe('Optional parameters for the statement.'),
29
+ }),
30
+ }, async ({ sql, params }) => {
31
+ const result = await db.execute(sql, params);
32
+ return {
33
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
34
+ };
35
+ });
36
+ server.registerTool('explain_query', {
37
+ description: 'Run EXPLAIN on a SQL query to analyze its execution plan and performance.',
38
+ inputSchema: z.object({
39
+ sql: z.string().describe('The SQL query to explain.'),
40
+ }),
41
+ }, async ({ sql }) => {
42
+ const results = await db.query(`EXPLAIN ${sql}`);
43
+ return {
44
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
45
+ };
46
+ });
47
+ server.registerTool('list_tables', {
48
+ description: 'List all base tables in the current database with row counts and comments.',
49
+ inputSchema: z.object({}),
50
+ }, async () => {
51
+ const results = await db.query(`
52
+ SELECT TABLE_NAME, TABLE_ROWS, TABLE_COMMENT
53
+ FROM information_schema.TABLES
54
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE'
55
+ `);
56
+ return {
57
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
58
+ };
59
+ });
60
+ server.registerTool('list_views', {
61
+ description: 'List all views in the current database.',
62
+ inputSchema: z.object({}),
63
+ }, async () => {
64
+ const results = await db.query(`
65
+ SELECT TABLE_NAME, VIEW_DEFINITION
66
+ FROM information_schema.VIEWS
67
+ WHERE TABLE_SCHEMA = DATABASE()
68
+ `);
69
+ return {
70
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
71
+ };
72
+ });
73
+ server.registerTool('describe_table', {
74
+ description: 'Show the schema/structure of one or more specific tables.',
75
+ inputSchema: z.object({
76
+ tables: z.array(z.string()).describe('The names of the tables to describe.'),
77
+ }),
78
+ }, async ({ tables }) => {
79
+ const results = {};
80
+ for (const table of tables) {
81
+ results[table] = await db.query(`DESCRIBE ${table}`);
82
+ }
83
+ return {
84
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
85
+ };
86
+ });
87
+ server.registerTool('describe_index', {
88
+ description: 'Show indexes for a specific table.',
89
+ inputSchema: z.object({
90
+ table: z.string().describe('The name of the table to show indexes for.'),
91
+ }),
92
+ }, async ({ table }) => {
93
+ const results = await db.query(`SHOW INDEX FROM ${table}`);
94
+ return {
95
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
96
+ };
97
+ });
98
+ server.registerTool('list_triggers', {
99
+ description: 'List all triggers in the current database.',
100
+ inputSchema: z.object({}),
101
+ }, async () => {
102
+ const results = await db.query('SHOW TRIGGERS');
103
+ return {
104
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
105
+ };
106
+ });
107
+ server.registerTool('get_current_privileges', {
108
+ description: 'Check the permissions and grants of the current database user. Useful for debugging access issues.',
109
+ inputSchema: z.object({}),
110
+ }, async () => {
111
+ const user = await db.query('SELECT CURRENT_USER() as user');
112
+ const grants = await db.query('SHOW GRANTS');
113
+ return {
114
+ content: [{
115
+ type: 'text',
116
+ text: JSON.stringify({ currentUser: user, grants }, null, 2)
117
+ }],
118
+ };
119
+ });
120
+ // Start server
121
+ async function main() {
122
+ const transport = new StdioServerTransport();
123
+ await server.connect(transport);
124
+ console.error('MySQL MCP Server running on stdio');
125
+ }
126
+ main().catch((error) => {
127
+ console.error('Fatal error in main():', error);
128
+ process.exit(1);
129
+ });
package/build/proxy.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Stdout Protection Proxy
3
+ * This module MUST be imported as the very first import in the application.
4
+ * It intercepts all writes to stdout and redirects non-MCP JSON output to stderr.
5
+ * This prevents protocol pollution from libraries that log to stdout (like dotenvx).
6
+ */
7
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
8
+ // @ts-ignore
9
+ process.stdout.write = (chunk, encoding, callback) => {
10
+ if (typeof chunk === 'string' || Buffer.isBuffer(chunk)) {
11
+ const str = chunk.toString();
12
+ // Allow MCP JSON-RPC messages and Content-Length headers (used in some transports)
13
+ if (str.includes('"jsonrpc":"2.0"') || str.includes('Content-Length:')) {
14
+ return originalStdoutWrite(chunk, encoding, callback);
15
+ }
16
+ }
17
+ // Redirect everything else to stderr
18
+ return process.stderr.write(chunk, encoding, callback);
19
+ };
20
+ // Also patch console.log to use stderr, just in case
21
+ const originalConsoleLog = console.log;
22
+ console.log = (...args) => {
23
+ console.error(...args);
24
+ };
25
+ export {};
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "easy-mysql-mcp",
3
+ "version": "1.0.0",
4
+ "description": "High performance MySQL MCP Server using mysql2",
5
+ "main": "build/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "easy-mysql-mcp": "build/index.js"
9
+ },
10
+ "files": [
11
+ "build",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "mysql",
21
+ "mysql2",
22
+ "claude"
23
+ ],
24
+ "license": "ISC",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "scripts": {
29
+ "build": "tsc",
30
+ "start": "node build/index.js",
31
+ "dev": "tsc --watch",
32
+ "prepack": "npm run build"
33
+ },
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.29.0",
36
+ "dotenv": "^17.4.2",
37
+ "mysql2": "^3.22.2",
38
+ "zod": "^4.3.6"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^25.6.0",
42
+ "typescript": "^6.0.3"
43
+ }
44
+
45
+ }