@cano721/mysql-mcp-server 0.1.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,285 @@
1
+ [![npm version](https://img.shields.io/npm/v/@cano721/mysql-mcp-server?color=blue)](https://www.npmjs.com/package/@cano721/mysql-mcp-server)
2
+
3
+
4
+ # MySQL Database Access MCP Server (@cano721/mysql-mcp-server)
5
+
6
+ This MCP server provides read-only access to MySQL databases. It allows you to:
7
+
8
+ - List available databases
9
+ - List tables in a database
10
+ - Describe table schemas
11
+ - Execute read-only SQL queries
12
+
13
+ ## Security Features
14
+
15
+ - **Read-only access**: Only SELECT, SHOW, DESCRIBE, and EXPLAIN statements are allowed
16
+ - **Query validation**: Prevents SQL injection and blocks any data modification attempts
17
+ - **Query timeout**: Prevents long-running queries from consuming resources
18
+ - **Row limit**: Prevents excessive data return
19
+
20
+ ## Installation
21
+
22
+ ### 1. Install using one of these methods:
23
+
24
+ #### Install from NPM
25
+
26
+ ```bash
27
+ # Install globally
28
+ npm install -g @cano721/mysql-mcp-server
29
+
30
+ # Or install locally in your project
31
+ npm install @cano721/mysql-mcp-server
32
+ ```
33
+
34
+ #### Build from Source
35
+
36
+ ```bash
37
+ # Clone the repository
38
+ git clone https://github.com/cano721/mysql-mcp-server.git
39
+ cd mysql-mcp-server
40
+
41
+ # Install dependencies and build
42
+ npm install
43
+ npm run build
44
+ ```
45
+
46
+ #### Install via Smithery
47
+
48
+ To install MySQL Database Access MCP Server for Claude AI automatically via Smithery:
49
+
50
+ ```bash
51
+ npx -y @smithery/cli install @cano721/mysql-mcp-server --client claude
52
+ ```
53
+
54
+ ### 2. Configure environment variables
55
+
56
+ The server requires the following environment variables:
57
+
58
+ - `MYSQL_HOST`: Database server hostname
59
+ - `MYSQL_PORT`: Database server port (default: 3306)
60
+ - `MYSQL_USER`: Database username
61
+ - `MYSQL_PASSWORD`: Database password (optional, but recommended for secure connections)
62
+ - `MYSQL_DATABASE`: Default database name (optional)
63
+
64
+ ### 3. Add to MCP settings
65
+
66
+ Add the following configuration to your MCP settings file:
67
+
68
+ If you installed via npm (Option 1):
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "mysql": {
73
+ "command": "npx",
74
+ "args": ["@cano721/mysql-mcp-server"],
75
+ "env": {
76
+ "MYSQL_HOST": "your-mysql-host",
77
+ "MYSQL_PORT": "3306",
78
+ "MYSQL_USER": "your-mysql-user",
79
+ "MYSQL_PASSWORD": "your-mysql-password",
80
+ "MYSQL_DATABASE": "your-default-database"
81
+ },
82
+ "disabled": false,
83
+ "autoApprove": []
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ If you built from source (Option 2):
90
+ ```json
91
+ {
92
+ "mcpServers": {
93
+ "mysql": {
94
+ "command": "node",
95
+ "args": ["/path/to/mysql-mcp-server/build/index.js"],
96
+ "env": {
97
+ "MYSQL_HOST": "your-mysql-host",
98
+ "MYSQL_PORT": "3306",
99
+ "MYSQL_USER": "your-mysql-user",
100
+ "MYSQL_PASSWORD": "your-mysql-password",
101
+ "MYSQL_DATABASE": "your-default-database"
102
+ },
103
+ "disabled": false,
104
+ "autoApprove": []
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ ## Available Tools
111
+
112
+ ### list_databases
113
+
114
+ Lists all accessible databases on the MySQL server.
115
+
116
+ **Parameters**: None
117
+
118
+ **Example**:
119
+ ```json
120
+ {
121
+ "server_name": "mysql",
122
+ "tool_name": "list_databases",
123
+ "arguments": {}
124
+ }
125
+ ```
126
+
127
+ ### list_tables
128
+
129
+ Lists all tables in a specified database.
130
+
131
+ **Parameters**:
132
+ - `database` (optional): Database name (uses default if not specified)
133
+
134
+ **Example**:
135
+ ```json
136
+ {
137
+ "server_name": "mysql",
138
+ "tool_name": "list_tables",
139
+ "arguments": {
140
+ "database": "my_database"
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### describe_table
146
+
147
+ Shows the schema for a specific table.
148
+
149
+ **Parameters**:
150
+ - `database` (optional): Database name (uses default if not specified)
151
+ - `table` (required): Table name
152
+
153
+ **Example**:
154
+ ```json
155
+ {
156
+ "server_name": "mysql",
157
+ "tool_name": "describe_table",
158
+ "arguments": {
159
+ "database": "my_database",
160
+ "table": "my_table"
161
+ }
162
+ }
163
+ ```
164
+
165
+ ### execute_query
166
+
167
+ Executes a read-only SQL query.
168
+
169
+ **Parameters**:
170
+ - `query` (required): SQL query (only SELECT, SHOW, DESCRIBE, and EXPLAIN statements are allowed)
171
+ - `database` (optional): Database name (uses default if not specified)
172
+
173
+ **Example**:
174
+ ```json
175
+ {
176
+ "server_name": "mysql",
177
+ "tool_name": "execute_query",
178
+ "arguments": {
179
+ "database": "my_database",
180
+ "query": "SELECT * FROM my_table LIMIT 10"
181
+ }
182
+ }
183
+ ```
184
+
185
+ ## Advanced Connection Pool Configuration
186
+
187
+ For more control over the MySQL connection pool behavior, you can configure additional parameters:
188
+
189
+ ```json
190
+ {
191
+ "mcpServers": {
192
+ "mysql": {
193
+ "command": "npx",
194
+ "args": ["@cano721/mysql-mcp-server"],
195
+ "env": {
196
+ "MYSQL_HOST": "your-mysql-host",
197
+ "MYSQL_PORT": "3306",
198
+ "MYSQL_USER": "your-mysql-user",
199
+ "MYSQL_PASSWORD": "your-mysql-password",
200
+ "MYSQL_DATABASE": "your-default-database",
201
+
202
+ "MYSQL_CONNECTION_LIMIT": "10",
203
+ "MYSQL_QUEUE_LIMIT": "0",
204
+ "MYSQL_CONNECT_TIMEOUT": "10000",
205
+ "MYSQL_IDLE_TIMEOUT": "60000",
206
+ "MYSQL_MAX_IDLE": "10"
207
+ },
208
+ "disabled": false,
209
+ "autoApprove": []
210
+ }
211
+ }
212
+ }
213
+ ```
214
+
215
+ These advanced options allow you to:
216
+
217
+ - `MYSQL_CONNECTION_LIMIT`: Control the maximum number of connections in the pool (default: 10)
218
+ - `MYSQL_QUEUE_LIMIT`: Set the maximum number of connection requests to queue (default: 0, unlimited)
219
+ - `MYSQL_CONNECT_TIMEOUT`: Adjust the connection timeout in milliseconds (default: 10000)
220
+ - `MYSQL_IDLE_TIMEOUT`: Configure how long a connection can be idle before being released (in milliseconds)
221
+ - `MYSQL_MAX_IDLE`: Set the maximum number of idle connections to keep in the pool
222
+
223
+
224
+ ## Testing
225
+
226
+ The server includes test scripts to verify functionality with your MySQL setup:
227
+
228
+ ### 1. Setup Test Database
229
+
230
+ This script creates a test database, table, and sample data:
231
+
232
+ ```bash
233
+ # Set your MySQL credentials as environment variables
234
+ export MYSQL_HOST=localhost
235
+ export MYSQL_PORT=3306
236
+ export MYSQL_USER=your_username
237
+ export MYSQL_PASSWORD=your_password
238
+
239
+ # Run the setup script
240
+ npm run test:setup
241
+ ```
242
+
243
+ ### 2. Test MCP Tools
244
+
245
+ This script tests each of the MCP tools against the test database:
246
+
247
+ ```bash
248
+ # Set your MySQL credentials as environment variables
249
+ export MYSQL_HOST=localhost
250
+ export MYSQL_PORT=3306
251
+ export MYSQL_USER=your_username
252
+ export MYSQL_PASSWORD=your_password
253
+ export MYSQL_DATABASE=mcp_test_db
254
+
255
+ # Run the tools test script
256
+ npm run test:tools
257
+ ```
258
+
259
+ ### 3. Run All Tests
260
+
261
+ To run both setup and tool tests:
262
+
263
+ ```bash
264
+ # Set your MySQL credentials as environment variables
265
+ export MYSQL_HOST=localhost
266
+ export MYSQL_PORT=3306
267
+ export MYSQL_USER=your_username
268
+ export MYSQL_PASSWORD=your_password
269
+
270
+ # Run all tests
271
+ npm test
272
+ ```
273
+
274
+ ## Troubleshooting
275
+
276
+ If you encounter issues:
277
+
278
+ 1. Check the server logs for error messages
279
+ 2. Verify your MySQL credentials and connection details
280
+ 3. Ensure your MySQL user has appropriate permissions
281
+ 4. Check that your query is read-only and properly formatted
282
+
283
+ ## License
284
+
285
+ This project is licensed under the MIT License - see the [LICENSE](../LICENSE) file for details.
@@ -0,0 +1,128 @@
1
+ /**
2
+ * MySQL connection management for MCP server
3
+ */
4
+ import mysql from 'mysql2/promise';
5
+ // Default connection pool configuration
6
+ const DEFAULT_PORT = 3306; // Default MySQL port
7
+ const DEFAULT_TIMEOUT = 10000; // Default connection timeout in milliseconds
8
+ const DEFAULT_CONNECTION_LIMIT = 10; // Default maximum number of connections in the pool
9
+ const DEFAULT_QUEUE_LIMIT = 0; // Default maximum number of connection requests to queue (0 = unlimited)
10
+ const DEFAULT_ROW_LIMIT = 1000; // Default row limit for query results
11
+ /**
12
+ * Create a MySQL connection pool
13
+ */
14
+ export function createConnectionPool(config) {
15
+ console.error('[Setup] Creating MySQL connection pool');
16
+ try {
17
+ // Create connection options with defaults
18
+ const poolConfig = {
19
+ host: config.host,
20
+ port: config.port,
21
+ user: config.user,
22
+ waitForConnections: true,
23
+ connectionLimit: config.connectionLimit ?? DEFAULT_CONNECTION_LIMIT,
24
+ queueLimit: config.queueLimit ?? DEFAULT_QUEUE_LIMIT,
25
+ connectTimeout: config.connectTimeout ?? DEFAULT_TIMEOUT,
26
+ };
27
+ // Add password if provided
28
+ if (config.password !== undefined) {
29
+ poolConfig.password = config.password;
30
+ }
31
+ // Add database if provided
32
+ if (config.database) {
33
+ poolConfig.database = config.database;
34
+ }
35
+ // Add idleTimeout if provided
36
+ if (config.idleTimeout !== undefined) {
37
+ poolConfig.idleTimeout = config.idleTimeout;
38
+ }
39
+ // Add maxIdle if provided
40
+ if (config.maxIdle !== undefined) {
41
+ poolConfig.maxIdle = config.maxIdle;
42
+ }
43
+ return mysql.createPool(poolConfig);
44
+ }
45
+ catch (error) {
46
+ console.error('[Error] Failed to create connection pool:', error);
47
+ throw error;
48
+ }
49
+ }
50
+ /**
51
+ * Execute a query with error handling and logging
52
+ */
53
+ export async function executeQuery(pool, sql, params = [], database) {
54
+ console.error(`[Query] Executing: ${sql}`);
55
+ let connection = null;
56
+ try {
57
+ // Get connection from pool
58
+ connection = await pool.getConnection();
59
+ // Use specific database if provided
60
+ if (database) {
61
+ console.error(`[Query] Using database: ${database}`);
62
+ await connection.query(`USE \`${database}\``);
63
+ }
64
+ // Execute query with timeout
65
+ const [rows, fields] = await Promise.race([
66
+ connection.query(sql, params),
67
+ new Promise((_, reject) => {
68
+ setTimeout(() => reject(new Error('Query timeout')), DEFAULT_TIMEOUT);
69
+ }),
70
+ ]);
71
+ // Apply row limit if result is an array
72
+ const limitedRows = Array.isArray(rows) && rows.length > DEFAULT_ROW_LIMIT
73
+ ? rows.slice(0, DEFAULT_ROW_LIMIT)
74
+ : rows;
75
+ // Log result summary
76
+ console.error(`[Query] Success: ${Array.isArray(rows) ? rows.length : 1} rows returned`);
77
+ return { rows: limitedRows, fields };
78
+ }
79
+ catch (error) {
80
+ console.error('[Error] Query execution failed:', error);
81
+ throw error;
82
+ }
83
+ finally {
84
+ // Release connection back to pool
85
+ if (connection) {
86
+ connection.release();
87
+ }
88
+ }
89
+ }
90
+ /**
91
+ * Get MySQL connection configuration from environment variables
92
+ */
93
+ export function getConfigFromEnv() {
94
+ const host = process.env.MYSQL_HOST;
95
+ const portStr = process.env.MYSQL_PORT;
96
+ const user = process.env.MYSQL_USER;
97
+ const password = process.env.MYSQL_PASSWORD;
98
+ const database = process.env.MYSQL_DATABASE;
99
+ // Connection pool options
100
+ const connectionLimitStr = process.env.MYSQL_CONNECTION_LIMIT;
101
+ const queueLimitStr = process.env.MYSQL_QUEUE_LIMIT;
102
+ const connectTimeoutStr = process.env.MYSQL_CONNECT_TIMEOUT;
103
+ const idleTimeoutStr = process.env.MYSQL_IDLE_TIMEOUT;
104
+ const maxIdleStr = process.env.MYSQL_MAX_IDLE;
105
+ if (!host)
106
+ throw new Error('MYSQL_HOST environment variable is required');
107
+ if (!user)
108
+ throw new Error('MYSQL_USER environment variable is required');
109
+ const port = portStr ? parseInt(portStr, 10) : DEFAULT_PORT;
110
+ // Parse connection pool options (all optional)
111
+ const connectionLimit = connectionLimitStr ? parseInt(connectionLimitStr, 10) : undefined;
112
+ const queueLimit = queueLimitStr ? parseInt(queueLimitStr, 10) : undefined;
113
+ const connectTimeout = connectTimeoutStr ? parseInt(connectTimeoutStr, 10) : undefined;
114
+ const idleTimeout = idleTimeoutStr ? parseInt(idleTimeoutStr, 10) : undefined;
115
+ const maxIdle = maxIdleStr ? parseInt(maxIdleStr, 10) : undefined;
116
+ return {
117
+ host,
118
+ port,
119
+ user,
120
+ password,
121
+ database,
122
+ connectionLimit,
123
+ queueLimit,
124
+ connectTimeout,
125
+ idleTimeout,
126
+ maxIdle
127
+ };
128
+ }
package/build/index.js ADDED
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MySQL Database Access MCP Server
4
+ *
5
+ * This MCP server provides read-only access to MySQL databases.
6
+ * It allows:
7
+ * - Listing available databases
8
+ * - Listing tables in a database
9
+ * - Describing table schemas
10
+ * - Executing read-only SQL queries
11
+ */
12
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
15
+ import { createConnectionPool, executeQuery, getConfigFromEnv } from './connection.js';
16
+ import { validateQuery } from './validators.js';
17
+ // Create MySQL connection pool
18
+ let pool;
19
+ try {
20
+ const config = getConfigFromEnv();
21
+ console.error('[Setup] MySQL configuration:', {
22
+ host: config.host,
23
+ port: config.port,
24
+ user: config.user,
25
+ password: config.password ? '********' : '(not provided)',
26
+ database: config.database || '(default not set)'
27
+ });
28
+ pool = createConnectionPool(config);
29
+ }
30
+ catch (error) {
31
+ console.error('[Fatal] Failed to initialize MySQL connection:', error);
32
+ process.exit(1);
33
+ }
34
+ /**
35
+ * Create an MCP server with tools for MySQL database access
36
+ */
37
+ const server = new Server({
38
+ name: "mysql-mcp-server",
39
+ version: "0.1.0",
40
+ }, {
41
+ capabilities: {
42
+ tools: {},
43
+ },
44
+ });
45
+ /**
46
+ * Handler that lists available tools for MySQL database access
47
+ */
48
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
49
+ return {
50
+ tools: [
51
+ {
52
+ name: "list_databases",
53
+ description: "List all accessible databases on the MySQL server",
54
+ inputSchema: {
55
+ type: "object",
56
+ properties: {},
57
+ required: []
58
+ }
59
+ },
60
+ {
61
+ name: "list_tables",
62
+ description: "List all tables in a specified database",
63
+ inputSchema: {
64
+ type: "object",
65
+ properties: {
66
+ database: {
67
+ type: "string",
68
+ description: "Database name (optional, uses default if not specified)"
69
+ }
70
+ },
71
+ required: []
72
+ }
73
+ },
74
+ {
75
+ name: "describe_table",
76
+ description: "Show the schema for a specific table",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: {
80
+ database: {
81
+ type: "string",
82
+ description: "Database name (optional, uses default if not specified)"
83
+ },
84
+ table: {
85
+ type: "string",
86
+ description: "Table name"
87
+ }
88
+ },
89
+ required: ["table"]
90
+ }
91
+ },
92
+ {
93
+ name: "execute_query",
94
+ description: "Execute a read-only SQL query",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: {
98
+ query: {
99
+ type: "string",
100
+ description: "SQL query (only SELECT, SHOW, DESCRIBE, and EXPLAIN statements are allowed)"
101
+ },
102
+ database: {
103
+ type: "string",
104
+ description: "Database name (optional, uses default if not specified)"
105
+ }
106
+ },
107
+ required: ["query"]
108
+ }
109
+ }
110
+ ]
111
+ };
112
+ });
113
+ /**
114
+ * Handler for MySQL database access tools
115
+ */
116
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
117
+ try {
118
+ switch (request.params.name) {
119
+ case "list_databases": {
120
+ console.error('[Tool] Executing list_databases');
121
+ const { rows } = await executeQuery(pool, 'SHOW DATABASES');
122
+ return {
123
+ content: [{
124
+ type: "text",
125
+ text: JSON.stringify(rows, null, 2)
126
+ }]
127
+ };
128
+ }
129
+ case "list_tables": {
130
+ console.error('[Tool] Executing list_tables');
131
+ const database = request.params.arguments?.database;
132
+ const { rows } = await executeQuery(pool, 'SHOW FULL TABLES', [], database);
133
+ return {
134
+ content: [{
135
+ type: "text",
136
+ text: JSON.stringify(rows, null, 2)
137
+ }]
138
+ };
139
+ }
140
+ case "describe_table": {
141
+ console.error('[Tool] Executing describe_table');
142
+ const database = request.params.arguments?.database;
143
+ const table = request.params.arguments?.table;
144
+ if (!table) {
145
+ throw new McpError(ErrorCode.InvalidParams, "Table name is required");
146
+ }
147
+ const { rows } = await executeQuery(pool, `DESCRIBE \`${table}\``, [], database);
148
+ return {
149
+ content: [{
150
+ type: "text",
151
+ text: JSON.stringify(rows, null, 2)
152
+ }]
153
+ };
154
+ }
155
+ case "execute_query": {
156
+ console.error('[Tool] Executing execute_query');
157
+ const query = request.params.arguments?.query;
158
+ const database = request.params.arguments?.database;
159
+ if (!query) {
160
+ throw new McpError(ErrorCode.InvalidParams, "Query is required");
161
+ }
162
+ // Validate that the query is read-only
163
+ validateQuery(query);
164
+ const { rows } = await executeQuery(pool, query, [], database);
165
+ return {
166
+ content: [{
167
+ type: "text",
168
+ text: JSON.stringify(rows, null, 2)
169
+ }]
170
+ };
171
+ }
172
+ default:
173
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
174
+ }
175
+ }
176
+ catch (error) {
177
+ console.error('[Error] Tool execution failed:', error);
178
+ // Format error message for client
179
+ return {
180
+ content: [{
181
+ type: "text",
182
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
183
+ }],
184
+ isError: true
185
+ };
186
+ }
187
+ });
188
+ /**
189
+ * Start the server using stdio transport
190
+ */
191
+ async function main() {
192
+ console.error('[Setup] Starting MySQL MCP server');
193
+ try {
194
+ const transport = new StdioServerTransport();
195
+ await server.connect(transport);
196
+ console.error('[Setup] MySQL MCP server running on stdio');
197
+ }
198
+ catch (error) {
199
+ console.error('[Fatal] Failed to start server:', error);
200
+ process.exit(1);
201
+ }
202
+ }
203
+ // Handle process termination
204
+ process.on('SIGINT', async () => {
205
+ console.error('[Shutdown] Closing MySQL connection pool');
206
+ await pool.end();
207
+ process.exit(0);
208
+ });
209
+ // Start the server
210
+ main().catch((error) => {
211
+ console.error('[Fatal] Unhandled error:', error);
212
+ process.exit(1);
213
+ });
package/build/types.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Type definitions for MySQL MCP server
3
+ */
4
+ export {};
@@ -0,0 +1,79 @@
1
+ /**
2
+ * SQL query validators for MySQL MCP server
3
+ * Ensures that only read-only queries are allowed
4
+ */
5
+ // List of allowed SQL commands (read-only operations)
6
+ const ALLOWED_COMMANDS = [
7
+ 'SELECT',
8
+ 'SHOW',
9
+ 'DESCRIBE',
10
+ 'DESC',
11
+ 'EXPLAIN',
12
+ ];
13
+ // List of disallowed SQL commands (write operations)
14
+ const DISALLOWED_COMMANDS = [
15
+ 'INSERT',
16
+ 'UPDATE',
17
+ 'DELETE',
18
+ 'DROP',
19
+ 'CREATE',
20
+ 'ALTER',
21
+ 'TRUNCATE',
22
+ 'RENAME',
23
+ 'REPLACE',
24
+ 'GRANT',
25
+ 'REVOKE',
26
+ 'LOCK',
27
+ 'UNLOCK',
28
+ 'CALL',
29
+ 'EXEC',
30
+ 'EXECUTE',
31
+ 'SET',
32
+ 'START',
33
+ 'BEGIN',
34
+ 'COMMIT',
35
+ 'ROLLBACK',
36
+ ];
37
+ /**
38
+ * Validates if a SQL query is read-only
39
+ * @param query SQL query to validate
40
+ * @returns true if the query is read-only, false otherwise
41
+ */
42
+ export function isReadOnlyQuery(query) {
43
+ // Normalize query by removing comments and extra whitespace
44
+ const normalizedQuery = query
45
+ .replace(/--.*$/gm, '') // Remove single-line comments
46
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
47
+ .replace(/\s+/g, ' ') // Normalize whitespace
48
+ .trim()
49
+ .toUpperCase();
50
+ // Check if query starts with an allowed command
51
+ const startsWithAllowed = ALLOWED_COMMANDS.some(cmd => normalizedQuery.startsWith(cmd + ' ') || normalizedQuery === cmd);
52
+ // Check if query contains any disallowed commands
53
+ const containsDisallowed = DISALLOWED_COMMANDS.some(cmd => {
54
+ const regex = new RegExp(`(^|\\s)${cmd}(\\s|$)`);
55
+ return regex.test(normalizedQuery);
56
+ });
57
+ // Check for multiple statements (;)
58
+ const hasMultipleStatements = normalizedQuery.includes(';') &&
59
+ !normalizedQuery.endsWith(';');
60
+ // Query is read-only if it starts with an allowed command,
61
+ // doesn't contain any disallowed commands, and doesn't have multiple statements
62
+ return startsWithAllowed && !containsDisallowed && !hasMultipleStatements;
63
+ }
64
+ /**
65
+ * Validates if a SQL query is safe to execute
66
+ * @param query SQL query to validate
67
+ * @throws Error if the query is not safe
68
+ */
69
+ export function validateQuery(query) {
70
+ console.error('[Validator] Validating query:', query);
71
+ if (!query || typeof query !== 'string') {
72
+ throw new Error('Query must be a non-empty string');
73
+ }
74
+ if (!isReadOnlyQuery(query)) {
75
+ console.error('[Validator] Query rejected: not read-only');
76
+ throw new Error('Only read-only queries are allowed (SELECT, SHOW, DESCRIBE, EXPLAIN)');
77
+ }
78
+ console.error('[Validator] Query validated as read-only');
79
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@cano721/mysql-mcp-server",
3
+ "version": "0.1.3",
4
+ "description": "An MCP server that provides read-only access to MySQL databases.",
5
+ "type": "module",
6
+ "bin": {
7
+ "mysql-mcp-server": "build/index.js"
8
+ },
9
+ "files": [
10
+ "build"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
14
+ "prepare": "npm run build",
15
+ "watch": "tsc --watch",
16
+ "inspector": "npx @modelcontextprotocol/inspector build/index.js",
17
+ "test:setup": "node test-setup.js",
18
+ "test:tools": "node test-tools.js",
19
+ "test": "npm run test:setup && npm run test:tools"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "mysql",
24
+ "database",
25
+ "model-context-protocol",
26
+ "ai",
27
+ "llm"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/cano721/mysql-mcp-server.git"
32
+ },
33
+ "author": "cano721",
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "0.6.0",
37
+ "mysql2": "^3.12.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^20.11.24",
41
+ "dotenv": "^16.4.7",
42
+ "typescript": "^5.3.3"
43
+ }
44
+ }