@zilosoft/mcp-server-mysql 2.1.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/dist/evals.js ADDED
@@ -0,0 +1,32 @@
1
+ import { openai } from "@ai-sdk/openai";
2
+ import { grade } from "mcp-evals";
3
+ const mysqlQueryToolEval = {
4
+ name: 'mysql_query Tool Evaluation',
5
+ description: 'Evaluates the MySQL query execution functionality',
6
+ run: async () => {
7
+ const result = await grade(openai("gpt-4"), "Please execute the following SQL query and return the results: SELECT * FROM employees WHERE status='ACTIVE';");
8
+ return JSON.parse(result);
9
+ }
10
+ };
11
+ const mysqlQueryGenerationEval = {
12
+ name: 'mysql_query Tool Generation Evaluation',
13
+ description: 'Evaluates the MySQL query tool for correct SQL generation and execution',
14
+ run: async () => {
15
+ const result = await grade(openai("gpt-4"), "Use the mysql_query tool to select all rows from the 'users' table where isActive = 1. Provide the SQL query in the correct format.");
16
+ return JSON.parse(result);
17
+ }
18
+ };
19
+ const mysqlQueryColumnsEval = {
20
+ name: 'mysql_query Columns Evaluation',
21
+ description: 'Evaluates the mysql_query tool for column selection',
22
+ run: async () => {
23
+ const result = await grade(openai("gpt-4"), "Please provide a SQL query to retrieve the id, name, and email columns for all records in the users table.");
24
+ return JSON.parse(result);
25
+ }
26
+ };
27
+ const config = {
28
+ model: openai("gpt-4"),
29
+ evals: [mysqlQueryToolEval, mysqlQueryGenerationEval, mysqlQueryColumnsEval]
30
+ };
31
+ export default config;
32
+ export const evals = [mysqlQueryToolEval, mysqlQueryGenerationEval, mysqlQueryColumnsEval];
package/dist/index.js ADDED
@@ -0,0 +1,417 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
6
+ import { z } from "zod";
7
+ import { log } from "./src/utils/index.js";
8
+ import { ALLOW_DELETE_OPERATION, ALLOW_DDL_OPERATION, ALLOW_INSERT_OPERATION, ALLOW_UPDATE_OPERATION, SCHEMA_DELETE_PERMISSIONS, SCHEMA_DDL_PERMISSIONS, SCHEMA_INSERT_PERMISSIONS, SCHEMA_UPDATE_PERMISSIONS, isMultiDbMode, mcpConfig as config, MCP_VERSION as version, IS_REMOTE_MCP, REMOTE_SECRET_KEY, PORT, IS_SSL_ENABLED, IS_SSL_AUTO_DETECTED, IS_SSL_SKIP_VERIFY, } from "./src/config/index.js";
9
+ import { safeExit, getPool, executeQuery, executeReadOnlyQuery, poolPromise, } from "./src/db/index.js";
10
+ import express from "express";
11
+ import { fileURLToPath } from 'url';
12
+ import { realpathSync } from 'fs';
13
+ import { timingSafeEqual } from 'crypto';
14
+ log("info", `Starting MySQL MCP server v${version}...`);
15
+ if (IS_SSL_AUTO_DETECTED) {
16
+ log("info", "SSL auto-detected: remote host, enabling encrypted connection. " +
17
+ "Set MYSQL_SSL=false to disable.");
18
+ }
19
+ if (IS_SSL_SKIP_VERIFY) {
20
+ log("error", "WARNING: SSL certificate verification is DISABLED (MYSQL_SSL_SKIP_VERIFY=true). " +
21
+ "Connection is vulnerable to MITM attacks. Provide MYSQL_SSL_CA for verified SSL.");
22
+ }
23
+ const toolVersion = `MySQL MCP Server [v${process.env.npm_package_version}]`;
24
+ let toolDescription = `[${toolVersion}] Run SQL queries against MySQL database`;
25
+ if (isMultiDbMode) {
26
+ toolDescription += " (Multi-DB mode enabled)";
27
+ }
28
+ if (ALLOW_INSERT_OPERATION ||
29
+ ALLOW_UPDATE_OPERATION ||
30
+ ALLOW_DELETE_OPERATION ||
31
+ ALLOW_DDL_OPERATION) {
32
+ toolDescription += " with support for:";
33
+ if (ALLOW_INSERT_OPERATION) {
34
+ toolDescription += " INSERT,";
35
+ }
36
+ if (ALLOW_UPDATE_OPERATION) {
37
+ toolDescription += " UPDATE,";
38
+ }
39
+ if (ALLOW_DELETE_OPERATION) {
40
+ toolDescription += " DELETE,";
41
+ }
42
+ if (ALLOW_DDL_OPERATION) {
43
+ toolDescription += " DDL,";
44
+ }
45
+ toolDescription = toolDescription.replace(/,$/, "") + " and READ operations";
46
+ if (Object.keys(SCHEMA_INSERT_PERMISSIONS).length > 0 ||
47
+ Object.keys(SCHEMA_UPDATE_PERMISSIONS).length > 0 ||
48
+ Object.keys(SCHEMA_DELETE_PERMISSIONS).length > 0 ||
49
+ Object.keys(SCHEMA_DDL_PERMISSIONS).length > 0) {
50
+ toolDescription += " (Schema-specific permissions enabled)";
51
+ }
52
+ }
53
+ else {
54
+ toolDescription += " (READ-ONLY)";
55
+ }
56
+ const isReadOnly = !(ALLOW_INSERT_OPERATION ||
57
+ ALLOW_UPDATE_OPERATION ||
58
+ ALLOW_DELETE_OPERATION ||
59
+ ALLOW_DDL_OPERATION);
60
+ log("info", "MySQL Configuration:", JSON.stringify({
61
+ ...(process.env.MYSQL_SOCKET_PATH
62
+ ? {
63
+ socketPath: process.env.MYSQL_SOCKET_PATH,
64
+ connectionType: "Unix Socket",
65
+ }
66
+ : {
67
+ host: process.env.MYSQL_HOST || "127.0.0.1",
68
+ port: process.env.MYSQL_PORT || "3306",
69
+ connectionType: "TCP/IP",
70
+ }),
71
+ user: config.mysql.user,
72
+ password: config.mysql.password ? "******" : "not set",
73
+ database: config.mysql.database || "MULTI_DB_MODE",
74
+ ssl: IS_SSL_ENABLED
75
+ ? `enabled${IS_SSL_AUTO_DETECTED ? " (auto-detected: remote host)" : ""}` +
76
+ `, verification: ${IS_SSL_SKIP_VERIFY ? "DISABLED" : "strict"}`
77
+ : "disabled",
78
+ sslCA: process.env.MYSQL_SSL_CA ? "set" : "not set",
79
+ sslCert: process.env.MYSQL_SSL_CERT ? "set" : "not set",
80
+ sslKey: process.env.MYSQL_SSL_KEY ? "set" : "not set",
81
+ multiDbMode: isMultiDbMode ? "enabled" : "disabled",
82
+ }, null, 2));
83
+ export const configSchema = z.object({
84
+ debug: z.boolean().default(false).describe("Enable debug logging"),
85
+ });
86
+ export default function createMcpServer({ sessionId, config, }) {
87
+ const server = new Server({
88
+ name: "MySQL MCP Server",
89
+ version: process.env.npm_package_version || "1.0.0",
90
+ }, {
91
+ capabilities: {
92
+ resources: {},
93
+ tools: {
94
+ mysql_query: {
95
+ description: toolDescription,
96
+ inputSchema: {
97
+ type: "object",
98
+ properties: {
99
+ sql: {
100
+ type: "string",
101
+ description: "The SQL query to execute",
102
+ },
103
+ },
104
+ required: ["sql"],
105
+ },
106
+ annotations: {
107
+ readOnlyHint: isReadOnly,
108
+ idempotentHint: isReadOnly,
109
+ destructiveHint: !isReadOnly,
110
+ openWorldHint: false,
111
+ title: "MySQL Query",
112
+ },
113
+ },
114
+ },
115
+ },
116
+ });
117
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
118
+ try {
119
+ log("info", "Handling ListResourcesRequest");
120
+ const connectionInfo = process.env.MYSQL_SOCKET_PATH
121
+ ? `socket: ${process.env.MYSQL_SOCKET_PATH}`
122
+ : `host: ${process.env.MYSQL_HOST || "localhost"}, port: ${process.env.MYSQL_PORT || 3306}`;
123
+ log("info", `Connection info: ${connectionInfo}`);
124
+ const tablesQuery = `
125
+ SELECT
126
+ table_name as name,
127
+ table_schema as \`database\`,
128
+ table_comment as description,
129
+ table_rows as rowCount,
130
+ data_length as dataSize,
131
+ index_length as indexSize,
132
+ create_time as createTime,
133
+ update_time as updateTime
134
+ FROM
135
+ information_schema.tables
136
+ WHERE
137
+ table_schema NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')
138
+ ORDER BY
139
+ table_schema, table_name
140
+ `;
141
+ const queryResult = (await executeReadOnlyQuery(tablesQuery));
142
+ const tables = JSON.parse(queryResult.content[0].text);
143
+ log("info", `Found ${tables.length} tables`);
144
+ const resources = tables.map((table) => ({
145
+ uri: `mysql://tables/${table.name}`,
146
+ name: table.name,
147
+ title: `${table.database}.${table.name}`,
148
+ description: table.description ||
149
+ `Table ${table.name} in database ${table.database}`,
150
+ mimeType: "application/json",
151
+ }));
152
+ resources.push({
153
+ uri: "mysql://tables",
154
+ name: "Tables",
155
+ title: "MySQL Tables",
156
+ description: "List of all MySQL tables",
157
+ mimeType: "application/json",
158
+ });
159
+ return { resources };
160
+ }
161
+ catch (error) {
162
+ log("error", "Error in ListResourcesRequest handler:", error);
163
+ throw error;
164
+ }
165
+ });
166
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
167
+ try {
168
+ log("info", "Handling ReadResourceRequest:", request.params.uri);
169
+ const uriParts = request.params.uri.split("/");
170
+ const tableName = uriParts.pop();
171
+ const dbName = uriParts.length > 0 ? uriParts.pop() : null;
172
+ if (!tableName) {
173
+ throw new Error(`Invalid resource URI: ${request.params.uri}`);
174
+ }
175
+ let columnsQuery = "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ?";
176
+ let queryParams = [tableName];
177
+ if (dbName) {
178
+ columnsQuery += " AND table_schema = ?";
179
+ queryParams.push(dbName);
180
+ }
181
+ const results = (await executeQuery(columnsQuery, queryParams));
182
+ return {
183
+ contents: [
184
+ {
185
+ uri: request.params.uri,
186
+ mimeType: "application/json",
187
+ text: JSON.stringify(results, null, 2),
188
+ },
189
+ ],
190
+ };
191
+ }
192
+ catch (error) {
193
+ log("error", "Error in ReadResourceRequest handler:", error);
194
+ throw error;
195
+ }
196
+ });
197
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
198
+ try {
199
+ log("info", "Handling CallToolRequest:", request.params.name);
200
+ if (request.params.name !== "mysql_query") {
201
+ throw new Error(`Unknown tool: ${request.params.name}`);
202
+ }
203
+ const sqlValidation = z.string().min(1, "sql must be a non-empty string").safeParse(request.params.arguments?.sql);
204
+ if (!sqlValidation.success) {
205
+ return {
206
+ content: [{ type: "text", text: `Error: ${sqlValidation.error.issues[0].message}` }],
207
+ isError: true,
208
+ };
209
+ }
210
+ const sql = sqlValidation.data;
211
+ return await executeReadOnlyQuery(sql);
212
+ }
213
+ catch (err) {
214
+ const error = err;
215
+ log("error", "Error in CallToolRequest handler:", error);
216
+ return {
217
+ content: [{
218
+ type: "text",
219
+ text: `Error: ${error.message}`
220
+ }],
221
+ isError: true
222
+ };
223
+ }
224
+ });
225
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
226
+ log("info", "Handling ListToolsRequest");
227
+ const toolsResponse = {
228
+ tools: [
229
+ {
230
+ name: "mysql_query",
231
+ description: toolDescription,
232
+ inputSchema: {
233
+ type: "object",
234
+ properties: {
235
+ sql: {
236
+ type: "string",
237
+ description: "The SQL query to execute",
238
+ },
239
+ },
240
+ required: ["sql"],
241
+ },
242
+ annotations: {
243
+ readOnlyHint: isReadOnly,
244
+ idempotentHint: isReadOnly,
245
+ destructiveHint: !isReadOnly,
246
+ openWorldHint: false,
247
+ title: "MySQL Query",
248
+ },
249
+ },
250
+ ],
251
+ };
252
+ log("info", "ListToolsRequest response:", JSON.stringify(toolsResponse, null, 2));
253
+ return toolsResponse;
254
+ });
255
+ (async () => {
256
+ try {
257
+ log("info", "Attempting to test database connection...");
258
+ const pool = await getPool();
259
+ const connection = await pool.getConnection();
260
+ log("info", "Database connection test successful");
261
+ connection.release();
262
+ }
263
+ catch (error) {
264
+ log("error", "Fatal error during server startup:", error);
265
+ safeExit(1);
266
+ }
267
+ })();
268
+ const shutdown = async (signal) => {
269
+ log("error", `Received ${signal}. Shutting down...`);
270
+ try {
271
+ if (poolPromise) {
272
+ const pool = await poolPromise;
273
+ await pool.end();
274
+ }
275
+ }
276
+ catch (err) {
277
+ log("error", "Error closing pool:", err);
278
+ throw err;
279
+ }
280
+ };
281
+ process.on("SIGINT", async () => {
282
+ try {
283
+ await shutdown("SIGINT");
284
+ process.exit(0);
285
+ }
286
+ catch (err) {
287
+ log("error", "Error during SIGINT shutdown:", err);
288
+ safeExit(1);
289
+ }
290
+ });
291
+ process.on("SIGTERM", async () => {
292
+ try {
293
+ await shutdown("SIGTERM");
294
+ process.exit(0);
295
+ }
296
+ catch (err) {
297
+ log("error", "Error during SIGTERM shutdown:", err);
298
+ safeExit(1);
299
+ }
300
+ });
301
+ process.on("uncaughtException", (error) => {
302
+ log("error", "Uncaught exception:", error);
303
+ safeExit(1);
304
+ });
305
+ process.on("unhandledRejection", (reason, promise) => {
306
+ log("error", "Unhandled rejection at:", promise, "reason:", reason);
307
+ safeExit(1);
308
+ });
309
+ return server;
310
+ }
311
+ const isMainModule = () => {
312
+ if (typeof require !== 'undefined' && require.main === module) {
313
+ return true;
314
+ }
315
+ if (typeof import.meta !== 'undefined' && import.meta.url && process.argv[1]) {
316
+ const currentModulePath = fileURLToPath(import.meta.url);
317
+ const mainScriptPath = realpathSync(process.argv[1]);
318
+ return currentModulePath === mainScriptPath;
319
+ }
320
+ return false;
321
+ };
322
+ if (isMainModule()) {
323
+ log("info", "Running in standalone mode");
324
+ (async () => {
325
+ try {
326
+ const mcpServer = createMcpServer({ config: { debug: false } });
327
+ if (IS_REMOTE_MCP && REMOTE_SECRET_KEY?.length) {
328
+ const app = express();
329
+ app.use(express.json());
330
+ app.post("/mcp", async (req, res) => {
331
+ const authHeader = req.get("Authorization") ?? "";
332
+ const expectedHeader = `Bearer ${REMOTE_SECRET_KEY}`;
333
+ const authBuf = Buffer.from(authHeader);
334
+ const expectedBuf = Buffer.from(expectedHeader);
335
+ const authValid = authBuf.length === expectedBuf.length &&
336
+ timingSafeEqual(authBuf, expectedBuf);
337
+ if (!authValid) {
338
+ console.error("Missing or invalid Authorization header");
339
+ res.status(401).json({
340
+ jsonrpc: "2.0",
341
+ error: {
342
+ code: -32603,
343
+ message: "Missing or invalid Authorization header",
344
+ },
345
+ id: null,
346
+ });
347
+ return;
348
+ }
349
+ try {
350
+ const server = mcpServer;
351
+ const transport = new StreamableHTTPServerTransport({
352
+ sessionIdGenerator: undefined,
353
+ });
354
+ res.on("close", () => {
355
+ log("info", "Request closed");
356
+ transport.close();
357
+ server.close();
358
+ });
359
+ await server.connect(transport);
360
+ await transport.handleRequest(req, res, req.body);
361
+ }
362
+ catch (error) {
363
+ log("error", "Error handling MCP request:", error);
364
+ if (!res.headersSent) {
365
+ res.status(500).json({
366
+ jsonrpc: "2.0",
367
+ error: {
368
+ code: -32603,
369
+ message: error.message,
370
+ },
371
+ id: null,
372
+ });
373
+ }
374
+ }
375
+ });
376
+ app.get("/mcp", async (req, res) => {
377
+ console.log("Received GET MCP request");
378
+ res.writeHead(405).end(JSON.stringify({
379
+ jsonrpc: "2.0",
380
+ error: {
381
+ code: -32000,
382
+ message: "Method not allowed.",
383
+ },
384
+ id: null,
385
+ }));
386
+ });
387
+ app.delete("/mcp", async (req, res) => {
388
+ console.log("Received DELETE MCP request");
389
+ res.writeHead(405).end(JSON.stringify({
390
+ jsonrpc: "2.0",
391
+ error: {
392
+ code: -32000,
393
+ message: "Method not allowed.",
394
+ },
395
+ id: null,
396
+ }));
397
+ });
398
+ app.listen(PORT, (error) => {
399
+ if (error) {
400
+ console.error("Failed to start server:", error);
401
+ process.exit(1);
402
+ }
403
+ console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);
404
+ });
405
+ }
406
+ else {
407
+ const transport = new StdioServerTransport();
408
+ await mcpServer.connect(transport);
409
+ log("info", "Server started and listening on stdio");
410
+ }
411
+ }
412
+ catch (error) {
413
+ log("error", "Server error:", error);
414
+ safeExit(1);
415
+ }
416
+ })();
417
+ }
@@ -0,0 +1,151 @@
1
+ import * as dotenv from "dotenv";
2
+ import * as fs from "fs";
3
+ import { parseSchemaPermissions, parseMySQLConnectionString } from "../utils/index.js";
4
+ function readSSLFile(filePath, label) {
5
+ try {
6
+ if (!fs.existsSync(filePath)) {
7
+ throw new Error(`SSL ${label} file not found: ${filePath}`);
8
+ }
9
+ const data = fs.readFileSync(filePath);
10
+ if (data.length === 0) {
11
+ throw new Error(`SSL ${label} file is empty: ${filePath}`);
12
+ }
13
+ return data;
14
+ }
15
+ catch (error) {
16
+ if (error instanceof Error) {
17
+ if (error.message.startsWith('SSL ')) {
18
+ throw error;
19
+ }
20
+ throw new Error(`Failed to read SSL ${label}: ${error.message}`);
21
+ }
22
+ throw error;
23
+ }
24
+ }
25
+ function readCACertificate(filePath) {
26
+ return readSSLFile(filePath, 'CA certificate');
27
+ }
28
+ export const MCP_VERSION = "2.0.2";
29
+ dotenv.config();
30
+ const connectionStringConfig = process.env.MYSQL_CONNECTION_STRING
31
+ ? parseMySQLConnectionString(process.env.MYSQL_CONNECTION_STRING)
32
+ : {};
33
+ if (process.env.NODE_ENV === "test" && !process.env.MYSQL_DB) {
34
+ process.env.MYSQL_DB = "mcp_test_db";
35
+ }
36
+ const dbFromEnvOrConnStringEarly = connectionStringConfig.database || process.env.MYSQL_DB;
37
+ const isMultiDbModeEarly = !dbFromEnvOrConnStringEarly || dbFromEnvOrConnStringEarly.trim() === "";
38
+ const MULTI_DB_WRITE_MODE = process.env.MULTI_DB_WRITE_MODE === "true";
39
+ const multiDbBlocksWrites = isMultiDbModeEarly && !MULTI_DB_WRITE_MODE;
40
+ export const ALLOW_INSERT_OPERATION = !multiDbBlocksWrites && process.env.ALLOW_INSERT_OPERATION === "true";
41
+ export const ALLOW_UPDATE_OPERATION = !multiDbBlocksWrites && process.env.ALLOW_UPDATE_OPERATION === "true";
42
+ export const ALLOW_DELETE_OPERATION = !multiDbBlocksWrites && process.env.ALLOW_DELETE_OPERATION === "true";
43
+ export const ALLOW_DDL_OPERATION = !multiDbBlocksWrites && process.env.ALLOW_DDL_OPERATION === "true";
44
+ export const MYSQL_DISABLE_READ_ONLY_TRANSACTIONS = process.env.MYSQL_DISABLE_READ_ONLY_TRANSACTIONS === "true";
45
+ export const SCHEMA_INSERT_PERMISSIONS = parseSchemaPermissions(process.env.SCHEMA_INSERT_PERMISSIONS);
46
+ export const SCHEMA_UPDATE_PERMISSIONS = parseSchemaPermissions(process.env.SCHEMA_UPDATE_PERMISSIONS);
47
+ export const SCHEMA_DELETE_PERMISSIONS = parseSchemaPermissions(process.env.SCHEMA_DELETE_PERMISSIONS);
48
+ export const SCHEMA_DDL_PERMISSIONS = parseSchemaPermissions(process.env.SCHEMA_DDL_PERMISSIONS);
49
+ export const IS_REMOTE_MCP = process.env.IS_REMOTE_MCP === "true";
50
+ export const REMOTE_SECRET_KEY = process.env.REMOTE_SECRET_KEY || "";
51
+ export const PORT = process.env.PORT || 3000;
52
+ export const isMultiDbMode = isMultiDbModeEarly;
53
+ function shouldAutoEnableSSL() {
54
+ if (connectionStringConfig.ssl !== undefined) {
55
+ return connectionStringConfig.ssl;
56
+ }
57
+ if (process.env.MYSQL_SSL !== undefined) {
58
+ return process.env.MYSQL_SSL === "true";
59
+ }
60
+ if (connectionStringConfig.socketPath || process.env.MYSQL_SOCKET_PATH) {
61
+ return false;
62
+ }
63
+ const host = connectionStringConfig.host || process.env.MYSQL_HOST || "127.0.0.1";
64
+ const localHosts = new Set([
65
+ "localhost",
66
+ "127.0.0.1",
67
+ "::1",
68
+ "0:0:0:0:0:0:0:1",
69
+ "0.0.0.0",
70
+ "[::1]",
71
+ ]);
72
+ return !localHosts.has(host.toLowerCase());
73
+ }
74
+ export const IS_SSL_ENABLED = shouldAutoEnableSSL();
75
+ export const IS_SSL_AUTO_DETECTED = process.env.MYSQL_SSL === undefined &&
76
+ connectionStringConfig.ssl === undefined &&
77
+ IS_SSL_ENABLED;
78
+ const effectiveSSLCA = connectionStringConfig.sslCA || process.env.MYSQL_SSL_CA;
79
+ const SSL_SKIP_VERIFY = process.env.MYSQL_SSL_SKIP_VERIFY === "true" ||
80
+ process.env.MYSQL_SSL_REJECT_UNAUTHORIZED === "false";
81
+ export const IS_SSL_SKIP_VERIFY = IS_SSL_ENABLED && SSL_SKIP_VERIFY;
82
+ if (IS_SSL_ENABLED && !effectiveSSLCA && !SSL_SKIP_VERIFY) {
83
+ throw new Error("SSL is enabled but neither a CA certificate nor a skip-verify flag is set.\n" +
84
+ " - Provide MYSQL_SSL_CA=/path/to/ca.pem for secure verified connections, OR\n" +
85
+ " - Set MYSQL_SSL_SKIP_VERIFY=true to explicitly disable certificate verification\n" +
86
+ " (NOT recommended for production — vulnerable to MITM attacks).\n" +
87
+ (IS_SSL_AUTO_DETECTED
88
+ ? "SSL was auto-enabled because the host is not local. " +
89
+ "Set MYSQL_SSL=false to connect without encryption (also not recommended)."
90
+ : ""));
91
+ }
92
+ export const mcpConfig = {
93
+ server: {
94
+ name: "@benborla29/mcp-server-mysql",
95
+ version: MCP_VERSION,
96
+ connectionTypes: ["stdio", "streamableHttp"],
97
+ },
98
+ mysql: {
99
+ ...(connectionStringConfig.socketPath || process.env.MYSQL_SOCKET_PATH
100
+ ? {
101
+ socketPath: connectionStringConfig.socketPath || process.env.MYSQL_SOCKET_PATH,
102
+ }
103
+ : {
104
+ host: connectionStringConfig.host || process.env.MYSQL_HOST || "127.0.0.1",
105
+ port: connectionStringConfig.port || Number(process.env.MYSQL_PORT || "3306"),
106
+ }),
107
+ user: connectionStringConfig.user || process.env.MYSQL_USER || "root",
108
+ password: connectionStringConfig.password !== undefined
109
+ ? connectionStringConfig.password
110
+ : process.env.MYSQL_PASS === undefined
111
+ ? ""
112
+ : process.env.MYSQL_PASS,
113
+ database: connectionStringConfig.database || process.env.MYSQL_DB || undefined,
114
+ connectionLimit: 10,
115
+ waitForConnections: true,
116
+ queueLimit: process.env.MYSQL_QUEUE_LIMIT ? parseInt(process.env.MYSQL_QUEUE_LIMIT, 10) : 100,
117
+ enableKeepAlive: true,
118
+ keepAliveInitialDelay: 0,
119
+ connectTimeout: process.env.MYSQL_CONNECT_TIMEOUT ? parseInt(process.env.MYSQL_CONNECT_TIMEOUT, 10) : 10000,
120
+ ...(IS_SSL_ENABLED
121
+ ? {
122
+ ssl: {
123
+ rejectUnauthorized: !SSL_SKIP_VERIFY,
124
+ ...(effectiveSSLCA
125
+ ? { ca: readCACertificate(effectiveSSLCA) }
126
+ : {}),
127
+ ...(process.env.MYSQL_SSL_CERT
128
+ ? { cert: readSSLFile(process.env.MYSQL_SSL_CERT, 'client certificate') }
129
+ : {}),
130
+ ...(process.env.MYSQL_SSL_KEY
131
+ ? { key: readSSLFile(process.env.MYSQL_SSL_KEY, 'client private key') }
132
+ : {}),
133
+ },
134
+ }
135
+ : {}),
136
+ ...(process.env.MYSQL_TIMEZONE
137
+ ? {
138
+ timezone: process.env.MYSQL_TIMEZONE,
139
+ }
140
+ : {}),
141
+ ...(process.env.MYSQL_DATE_STRINGS === "true"
142
+ ? {
143
+ dateStrings: true,
144
+ }
145
+ : {}),
146
+ },
147
+ paths: {
148
+ schema: "schema",
149
+ },
150
+ };
151
+ export { readCACertificate, readSSLFile };