Skip to content

Commit 16e4563

Browse files
committed
fix: respect custom mysql db port
1 parent 214387a commit 16e4563

7 files changed

Lines changed: 181 additions & 123 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
316316

317317
---
318318

319-
For more information or support, please [open an issue](https://github.com/devakone/mysql-query-mcp-server/issues) on the GitHub repository.
319+
For more information or support, please [open an issue](https://github.com/devakone/mysql-query-mcp-server/issues) on the GitHub repository.

docs/TROUBLESHOOTING.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,15 @@ PRODUCTION_DB_HOST=prod.example.com # Correct: "PRODUCTION" is recognized
8080

8181
3. **Network/firewall restrictions**
8282
- Check if your database allows remote connections
83-
- Verify firewall settings allow connections on the MySQL port (usually 3306)
83+
- Verify firewall settings allow connections on the configured MySQL port (`[ENV]_DB_PORT`, default `3306`)
8484

8585
4. **Missing environment variables**
8686
- Ensure all required variables for your environment are set
8787
- Run with `DEBUG=true` to see loaded configuration
88+
89+
5. **Incorrect custom port**
90+
- If your MySQL server is not on `3306`, set `[ENV]_DB_PORT` explicitly
91+
- Ensure the value is a valid integer such as `3307`
8892

8993
5. **Incorrect environment name**
9094
- Verify you're using one of the supported environment names: local, development, staging, production
@@ -225,4 +229,4 @@ If you can't resolve your issue with this guide:
225229
- Error messages
226230
- Steps to reproduce the issue
227231
- Your environment details (OS, Node.js version)
228-
- Debug logs (with sensitive information removed)
232+
- Debug logs (with sensitive information removed)

src/db/pools.ts

Lines changed: 79 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,8 @@ function debug(message: string, ...args: any[]) {
55
process.stderr.write(`DEBUG [Pools]: ${message} ${args.map(arg => JSON.stringify(arg)).join(' ')}\n`);
66
}
77

8-
// Connection pools for each environment
9-
export const pools = new Map<string, Pool>();
10-
11-
debug('Initializing database pools...');
12-
debug('Environment enum options:', Object.values(Environment.enum));
13-
debug('Environment enum type:', typeof Environment);
14-
debug('Environment enum:', Environment);
8+
export const pools = new Map<Environment, Pool>();
9+
let poolsInitialized = false;
1510

1611
// Map of environment to env var prefix
1712
const ENV_PREFIX_MAP = {
@@ -21,59 +16,85 @@ const ENV_PREFIX_MAP = {
2116
production: 'PRODUCTION'
2217
} as const;
2318

24-
// Initialize pools
25-
Object.values(Environment.enum).forEach((env) => {
26-
const envPrefix = ENV_PREFIX_MAP[env];
27-
28-
debug(`=== Initializing pool for ${env} environment ===`);
29-
debug(`Using prefix: ${envPrefix}`);
30-
debug('Environment variables:');
31-
debug(`HOST: ${process.env[`${envPrefix}_DB_HOST`]}`);
32-
debug(`USER: ${process.env[`${envPrefix}_DB_USER`]}`);
33-
debug(`DB: ${process.env[`${envPrefix}_DB_NAME`]}`);
34-
debug(`PASS: ${process.env[`${envPrefix}_DB_PASS`] ? 'set' : 'not set'}`);
35-
debug(`SSL: ${process.env.MCP_MYSQL_SSL}`);
36-
37-
const config: PoolOptions = {
38-
host: process.env[`${envPrefix}_DB_HOST`],
39-
user: process.env[`${envPrefix}_DB_USER`],
40-
password: process.env[`${envPrefix}_DB_PASS`],
41-
database: process.env[`${envPrefix}_DB_NAME`],
42-
ssl: process.env.MCP_MYSQL_SSL === "true" ? {} : undefined,
43-
connectionLimit: 5,
44-
enableKeepAlive: true,
45-
keepAliveInitialDelay: 10000,
46-
};
19+
export function initializePools() {
20+
if (poolsInitialized) {
21+
debug("Pools already initialized");
22+
return;
23+
}
24+
25+
debug("Initializing database pools...");
26+
debug("Environment enum options:", Object.values(Environment.enum));
27+
28+
Object.values(Environment.enum).forEach((env) => {
29+
const envPrefix = ENV_PREFIX_MAP[env];
30+
const portEnv = process.env[`${envPrefix}_DB_PORT`];
31+
const parsedPort = portEnv ? Number.parseInt(portEnv, 10) : undefined;
32+
const sslEnv = process.env[`${envPrefix}_DB_SSL`] ?? process.env.MCP_MYSQL_SSL;
33+
34+
debug(`=== Initializing pool for ${env} environment ===`);
35+
debug(`Using prefix: ${envPrefix}`);
36+
debug(`HOST: ${process.env[`${envPrefix}_DB_HOST`]}`);
37+
debug(`USER: ${process.env[`${envPrefix}_DB_USER`]}`);
38+
debug(`DB: ${process.env[`${envPrefix}_DB_NAME`]}`);
39+
debug(`PASS: ${process.env[`${envPrefix}_DB_PASS`] ? "set" : "not set"}`);
40+
debug(`PORT: ${portEnv ?? "default"}`);
41+
debug(`SSL: ${sslEnv}`);
42+
43+
const config: PoolOptions = {
44+
host: process.env[`${envPrefix}_DB_HOST`],
45+
user: process.env[`${envPrefix}_DB_USER`],
46+
password: process.env[`${envPrefix}_DB_PASS`],
47+
database: process.env[`${envPrefix}_DB_NAME`],
48+
ssl: sslEnv === "true" ? {} : undefined,
49+
connectionLimit: 5,
50+
enableKeepAlive: true,
51+
keepAliveInitialDelay: 10000,
52+
};
53+
54+
if (portEnv && Number.isNaN(parsedPort)) {
55+
debug(`Invalid port for ${env}, skipping configured value:`, portEnv);
56+
} else if (parsedPort !== undefined) {
57+
config.port = parsedPort;
58+
}
4759

48-
if (config.host && config.user && config.password && config.database) {
49-
debug(`Creating pool for ${env} with config:`, {
50-
host: config.host,
51-
user: config.user,
52-
database: config.database,
53-
ssl: config.ssl,
54-
hasPassword: !!config.password
55-
});
56-
60+
if (config.host && config.user && config.password && config.database) {
61+
debug(`Creating pool for ${env} with config:`, {
62+
host: config.host,
63+
user: config.user,
64+
database: config.database,
65+
port: config.port ?? 3306,
66+
ssl: config.ssl,
67+
hasPassword: !!config.password,
68+
});
69+
70+
try {
71+
pools.set(env, createPool(config));
72+
debug(`Pool created successfully for ${env}`);
73+
} catch (error) {
74+
debug(`Error creating pool for ${env}:`, error);
75+
}
76+
} else {
77+
debug(`Missing configuration for ${env}:`, {
78+
hasHost: !!config.host,
79+
hasUser: !!config.user,
80+
hasPass: !!config.password,
81+
hasDB: !!config.database,
82+
});
83+
}
84+
});
85+
86+
poolsInitialized = true;
87+
debug("Pools map keys:", Array.from(pools.keys()));
88+
}
89+
90+
export async function closePools() {
91+
for (const [env, pool] of pools.entries()) {
5792
try {
58-
const pool = createPool(config);
59-
pools.set(env, pool);
60-
debug(`Pool created successfully for ${env}`);
61-
debug(`Pool type for ${env}:`, typeof pool);
62-
debug(`Pool methods for ${env}:`, Object.keys(pool));
93+
debug(`Closing pool for ${env}...`);
94+
await pool.end();
95+
debug(`Pool for ${env} closed successfully`);
6396
} catch (error) {
64-
debug(`Error creating pool for ${env}:`, error);
97+
debug(`Error closing pool for ${env}:`, error);
6598
}
66-
} else {
67-
debug(`Missing configuration for ${env}:`, {
68-
hasHost: !!config.host,
69-
hasUser: !!config.user,
70-
hasPass: !!config.password,
71-
hasDB: !!config.database
72-
});
7399
}
74-
});
75-
76-
debug('Final pools state:');
77-
debug('Pools map keys:', Array.from(pools.keys()));
78-
debug('Pools map size:', pools.size);
79-
debug('Pools map entries:', Array.from(pools.entries()).map(([env]) => env));
100+
}

src/help.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Environment Variables:
2121
LOCAL_DB_USER Local database username
2222
LOCAL_DB_PASS Local database password
2323
LOCAL_DB_NAME Local database name
24+
LOCAL_DB_PORT Local database port (default: 3306)
2425
LOCAL_DB_SSL Set to 'true' to enable SSL for local database
2526
2627
DEVELOPMENT_DB_* Development environment database settings
@@ -62,4 +63,4 @@ export function processCommandLineArgs(): void {
6263
if (process.argv.includes('--version') || process.argv.includes('-v')) {
6364
showVersion();
6465
}
65-
}
66+
}

src/index.ts

Lines changed: 29 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,31 @@ processCommandLineArgs();
88
import { config } from "dotenv";
99
import { resolve, dirname } from "path";
1010
import { fileURLToPath } from "url";
11-
import fs from "fs";
11+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
12+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13+
import {
14+
CallToolRequestSchema,
15+
ListToolsRequestSchema,
16+
} from "@modelcontextprotocol/sdk/types.js";
17+
import { initializePools, closePools } from "./db/pools.js";
18+
import {
19+
queryToolName,
20+
queryToolDescription,
21+
QueryToolSchema,
22+
runQueryTool,
23+
} from "./tools/query.js";
24+
import {
25+
infoToolName,
26+
infoToolDescription,
27+
InfoToolSchema,
28+
runInfoTool,
29+
} from "./tools/info.js";
30+
import {
31+
environmentsToolName,
32+
environmentsToolDescription,
33+
EnvironmentsToolSchema,
34+
runEnvironmentsTool,
35+
} from "./tools/environments.js";
1236

1337
// Get the directory path of the current module
1438
const __filename = fileURLToPath(import.meta.url);
@@ -39,48 +63,9 @@ debug('Environment variables loaded:', {
3963
LOCAL_DB_HOST: process.env.LOCAL_DB_HOST,
4064
});
4165

42-
// Then import pools and MCP server components
43-
debug('Initializing database pools...');
44-
import { pools } from "./db/pools.js";
66+
initializePools();
4567

46-
debug('Importing MCP SDK components...');
47-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
48-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
49-
import {
50-
CallToolRequestSchema,
51-
ListToolsRequestSchema,
52-
} from "@modelcontextprotocol/sdk/types.js";
53-
54-
debug('Importing tools...');
55-
56-
debug('Importing query tool...');
57-
import {
58-
queryToolName,
59-
queryToolDescription,
60-
QueryToolSchema,
61-
runQueryTool,
62-
} from "./tools/query.js";
63-
debug('Query tool imported:', { queryToolName });
64-
65-
debug('Importing info tool...');
66-
import {
67-
infoToolName,
68-
infoToolDescription,
69-
InfoToolSchema,
70-
runInfoTool,
71-
} from "./tools/info.js";
72-
debug('Info tool imported:', { infoToolName });
73-
74-
debug('Importing environments tool...');
75-
import {
76-
environmentsToolName,
77-
environmentsToolDescription,
78-
EnvironmentsToolSchema,
79-
runEnvironmentsTool,
80-
} from "./tools/environments.js";
81-
debug('Environments tool imported:', { environmentsToolName });
82-
83-
debug('All tools imported successfully');
68+
debug('Tools imported successfully');
8469

8570
/**
8671
* MCP server providing MySQL database tools:
@@ -256,19 +241,8 @@ debug('CallTool handler registered');
256241
// Handle process termination
257242
async function cleanup() {
258243
debug('Starting cleanup...');
259-
260-
for (const [env, pool] of pools.entries()) {
261-
try {
262-
debug(`Closing pool for ${env}...`);
263-
await pool.end();
264-
debug(`Pool for ${env} closed successfully`);
265-
} catch (error) {
266-
const message = error instanceof Error ? error.message : "Unknown error";
267-
debug(`Error closing pool for ${env}:`, { error, message });
268-
}
269-
}
270-
271-
debug('Cleanup completed');
244+
await closePools();
245+
debug('Server cleanup completed');
272246
}
273247

274248
// Clean server startup function matching the PostgreSQL example

src/tools/info.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import { z } from "zod";
2-
import { Pool } from "mysql2/promise";
3-
import { Environment, InfoParams, DatabaseInfo } from "../types/index.js";
4-
import { config } from "dotenv";
2+
import { InfoParams, DatabaseInfo } from "../types/index.js";
53
import { pools } from "../db/pools.js";
64

7-
config();
8-
95
export const infoToolName = "info";
106
export const infoToolDescription = "Get information about MySQL databases";
117
export const InfoToolSchema = InfoParams;
@@ -70,4 +66,4 @@ export async function runInfoTool(params: z.infer<typeof InfoToolSchema>): Promi
7066
const message = error instanceof Error ? error.message : "Unknown error occurred";
7167
throw new Error(`Failed to get database info: ${message}`);
7268
}
73-
}
69+
}

tests/db/pools.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
const createPool = vi.fn((config) => ({ config }));
4+
5+
vi.mock('mysql2/promise', () => ({
6+
createPool,
7+
}));
8+
9+
describe('db pools', () => {
10+
const originalEnv = process.env;
11+
12+
beforeEach(() => {
13+
vi.resetModules();
14+
vi.clearAllMocks();
15+
process.env = { ...originalEnv };
16+
17+
Object.keys(process.env).forEach((key) => {
18+
if (key.includes('_DB_') || key.startsWith('MCP_MYSQL_')) {
19+
delete process.env[key];
20+
}
21+
});
22+
23+
vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
24+
});
25+
26+
it('passes a configured custom port to mysql2', async () => {
27+
process.env.LOCAL_DB_HOST = 'localhost';
28+
process.env.LOCAL_DB_USER = 'root';
29+
process.env.LOCAL_DB_PASS = 'password';
30+
process.env.LOCAL_DB_NAME = 'testdb';
31+
process.env.LOCAL_DB_PORT = '3307';
32+
33+
const { initializePools, pools } = await import('../../src/db/pools.js');
34+
35+
initializePools();
36+
37+
expect(createPool).toHaveBeenCalledTimes(1);
38+
expect(createPool).toHaveBeenCalledWith(
39+
expect.objectContaining({
40+
host: 'localhost',
41+
user: 'root',
42+
database: 'testdb',
43+
port: 3307,
44+
}),
45+
);
46+
expect(pools.has('local')).toBe(true);
47+
});
48+
49+
it('does not initialize pools more than once', async () => {
50+
process.env.LOCAL_DB_HOST = 'localhost';
51+
process.env.LOCAL_DB_USER = 'root';
52+
process.env.LOCAL_DB_PASS = 'password';
53+
process.env.LOCAL_DB_NAME = 'testdb';
54+
55+
const { initializePools } = await import('../../src/db/pools.js');
56+
57+
initializePools();
58+
initializePools();
59+
60+
expect(createPool).toHaveBeenCalledTimes(1);
61+
});
62+
});

0 commit comments

Comments
 (0)