Skip to content

Commit 5839464

Browse files
committed
feat: add MySQL Unix socket support and fix build errors
- Add support for Unix socket connections via MYSQL_SOCKET_PATH env var - Automatically detect and use socket connection when MYSQL_SOCKET_PATH is set - Fix TypeScript build errors caused by duplicate variable declarations in evals.ts - Update documentation with Unix socket example - Add integration tests for Unix socket connections - Update Smithery configuration to support socket connections - Maintain backward compatibility with existing TCP/IP connections Handles designcomputer#37, Fixes designcomputer#46
1 parent be7bb65 commit 5839464

File tree

10 files changed

+160
-28
lines changed

10 files changed

+160
-28
lines changed

.env.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Basic MySQL connection settings
22
MYSQL_HOST=127.0.0.1
3+
MYSQL_SOCKET_PATH=/tmp/mysql.sock
34
MYSQL_PORT=3306
45
MYSQL_USER=root
56
MYSQL_PASS=your_password

.env.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
MYSQL_HOST=127.0.0.1
2+
MYSQL_SOCKET_PATH=/tmp/mysql.sock
23
MYSQL_PORT=3306
34
MYSQL_USER=root
45
MYSQL_PASS=root

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,9 @@ For more control over the MCP server's behavior, you can use these advanced conf
347347
## Environment Variables
348348

349349
### Basic Connection
350-
- `MYSQL_HOST`: MySQL server host (default: "127.0.0.1")
351-
- `MYSQL_PORT`: MySQL server port (default: "3306")
350+
- `MYSQL_SOCKET_PATH`: Unix socket path for local connections (e.g., "/tmp/mysql.sock")
351+
- `MYSQL_HOST`: MySQL server host (default: "127.0.0.1") - ignored if MYSQL_SOCKET_PATH is set
352+
- `MYSQL_PORT`: MySQL server port (default: "3306") - ignored if MYSQL_SOCKET_PATH is set
352353
- `MYSQL_USER`: MySQL username (default: "root")
353354
- `MYSQL_PASS`: MySQL password
354355
- `MYSQL_DB`: Target database name (leave empty for multi-DB mode)

evals.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { EvalConfig } from 'mcp-evals';
44
import { openai } from "@ai-sdk/openai";
55
import { grade, EvalFunction } from "mcp-evals";
66

7-
const mysql_queryEval: EvalFunction = {
7+
const mysqlQueryToolEval: EvalFunction = {
88
name: 'mysql_query Tool Evaluation',
99
description: 'Evaluates the MySQL query execution functionality',
1010
run: async () => {
@@ -13,18 +13,18 @@ const mysql_queryEval: EvalFunction = {
1313
}
1414
};
1515

16-
const mysql_queryEval: EvalFunction = {
17-
name: 'mysql_query Tool Evaluation',
16+
const mysqlQueryGenerationEval: EvalFunction = {
17+
name: 'mysql_query Tool Generation Evaluation',
1818
description: 'Evaluates the MySQL query tool for correct SQL generation and execution',
1919
run: async () => {
2020
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.");
2121
return JSON.parse(result);
2222
}
2323
};
2424

25-
const mysql_queryEval: EvalFunction = {
26-
name: 'mysql_queryEval',
27-
description: 'Evaluates the mysql_query tool',
25+
const mysqlQueryColumnsEval: EvalFunction = {
26+
name: 'mysql_query Columns Evaluation',
27+
description: 'Evaluates the mysql_query tool for column selection',
2828
run: async () => {
2929
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.");
3030
return JSON.parse(result);
@@ -33,9 +33,9 @@ const mysql_queryEval: EvalFunction = {
3333

3434
const config: EvalConfig = {
3535
model: openai("gpt-4"),
36-
evals: [mysql_queryEval, mysql_queryEval, mysql_queryEval]
36+
evals: [mysqlQueryToolEval, mysqlQueryGenerationEval, mysqlQueryColumnsEval]
3737
};
3838

3939
export default config;
4040

41-
export const evals = [mysql_queryEval, mysql_queryEval, mysql_queryEval];
41+
export const evals = [mysqlQueryToolEval, mysqlQueryGenerationEval, mysqlQueryColumnsEval];

index.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -197,16 +197,24 @@ if (ALLOW_INSERT_OPERATION || ALLOW_UPDATE_OPERATION || ALLOW_DELETE_OPERATION |
197197
toolDescription += ' (READ-ONLY)'
198198
}
199199

200-
// Update MySQL config to handle blank database name
200+
// Update MySQL config to handle blank database name and Unix socket connections
201201
const config = {
202202
server: {
203203
name: '@benborla29/mcp-server-mysql',
204204
version: '0.1.18',
205205
connectionTypes: ['stdio'],
206206
},
207207
mysql: {
208-
host: process.env.MYSQL_HOST || '127.0.0.1',
209-
port: Number(process.env.MYSQL_PORT || '3306'),
208+
// Use Unix socket if provided, otherwise use host/port
209+
...(process.env.MYSQL_SOCKET_PATH
210+
? {
211+
socketPath: process.env.MYSQL_SOCKET_PATH,
212+
}
213+
: {
214+
host: process.env.MYSQL_HOST || '127.0.0.1',
215+
port: Number(process.env.MYSQL_PORT || '3306'),
216+
}
217+
),
210218
user: process.env.MYSQL_USER || 'root',
211219
password: process.env.MYSQL_PASS || 'root',
212220
database: process.env.MYSQL_DB || undefined, // Allow undefined database for multi-DB mode
@@ -232,8 +240,17 @@ const config = {
232240
// @INFO: Add debug logging for configuration
233241
log('info', 'MySQL Configuration:', JSON.stringify(
234242
{
235-
host: config.mysql.host,
236-
port: config.mysql.port,
243+
...(process.env.MYSQL_SOCKET_PATH
244+
? {
245+
socketPath: process.env.MYSQL_SOCKET_PATH,
246+
connectionType: 'Unix Socket',
247+
}
248+
: {
249+
host: process.env.MYSQL_HOST || '127.0.0.1',
250+
port: process.env.MYSQL_PORT || '3306',
251+
connectionType: 'TCP/IP',
252+
}
253+
),
237254
user: config.mysql.user,
238255
password: config.mysql.password ? '******' : 'not set',
239256
database: config.mysql.database || 'MULTI_DB_MODE',
@@ -294,6 +311,11 @@ const getServer = (): Promise<Server> => {
294311
async () => {
295312
try {
296313
log('error', 'Handling ListResourcesRequest')
314+
315+
// Determine connection info for URI
316+
const connectionInfo = process.env.MYSQL_SOCKET_PATH
317+
? `socket:${process.env.MYSQL_SOCKET_PATH}`
318+
: `${process.env.MYSQL_HOST || '127.0.0.1'}:${process.env.MYSQL_PORT || '3306'}`;
297319

298320
// If we're in multi-DB mode, list all databases first
299321
if (isMultiDbMode) {
@@ -317,7 +339,7 @@ const getServer = (): Promise<Server> => {
317339
allResources.push(...tables.map((row: TableRow) => ({
318340
uri: new URL(
319341
`${db.Database}/${row.table_name}/${config.paths.schema}`,
320-
`${config.mysql.host}:${config.mysql.port}`,
342+
connectionInfo,
321343
).href,
322344
mimeType: 'application/json',
323345
name: `"${db.Database}.${row.table_name}" database schema`,
@@ -337,7 +359,7 @@ const getServer = (): Promise<Server> => {
337359
resources: results.map((row: TableRow) => ({
338360
uri: new URL(
339361
`${row.table_name}/${config.paths.schema}`,
340-
`${config.mysql.host}:${config.mysql.port}`,
362+
connectionInfo,
341363
).href,
342364
mimeType: 'application/json',
343365
name: `"${row.table_name}" database schema`,

package-lock.json

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
"build": "tsc && shx chmod +x dist/*.js",
2222
"prepare": "npm run build",
2323
"watch": "tsc --watch",
24-
"setup:test:db": "node --loader ts-node/esm scripts/setup-test-db.ts",
24+
"setup:test:db": "tsx scripts/setup-test-db.ts",
2525
"pretest": "pnpm run setup:test:db",
2626
"test": "pnpm run setup:test:db && vitest run",
27+
"test:socket": "pnpm run setup:test:db && vitest run tests/integration/socket-connection.test.ts",
2728
"test:watch": "pnpm run setup:test:db && vitest",
2829
"test:coverage": "vitest run --coverage",
2930
"test:unit": "vitest run --config vitest.unit.config.ts",
@@ -46,6 +47,7 @@
4647
"shx": "^0.3.4",
4748
"ts-node": "^10.9.2",
4849
"tslib": "^2.8.1",
50+
"tsx": "^4.19.4",
4951
"typescript": "^5.8.2",
5052
"vitest": "^1.6.1"
5153
},

scripts/setup-test-db.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,16 @@ function log(type: LogType = 'info', ...args: any[]): void {
3232
async function setupTestDatabase() {
3333
// Create connection config, omitting password if empty
3434
const config: any = {
35-
host: process.env.MYSQL_HOST || '127.0.0.1',
36-
port: Number(process.env.MYSQL_PORT) || 3306,
35+
// Use Unix socket if provided, otherwise use host/port
36+
...(process.env.MYSQL_SOCKET_PATH
37+
? {
38+
socketPath: process.env.MYSQL_SOCKET_PATH,
39+
}
40+
: {
41+
host: process.env.MYSQL_HOST || '127.0.0.1',
42+
port: Number(process.env.MYSQL_PORT) || 3306,
43+
}
44+
),
3745
user: process.env.MYSQL_USER || 'root',
3846
password: process.env.MYSQL_PASS || 'root', // Default to 'root' if not specified
3947
multipleStatements: true

smithery.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@ startCommand:
44
configSchema:
55
# JSON Schema defining the configuration options for the MCP.
66
type: object
7-
required:
8-
- mysqlHost
9-
- mysqlPort
10-
- mysqlUser
7+
required: []
118
properties:
9+
mysqlSocketPath:
10+
type: string
11+
description: Unix socket path for local MySQL connections. If provided, host and port are ignored.
1212
mysqlHost:
1313
type: string
14-
description: The host address of the MySQL database.
14+
description: The host address of the MySQL database. Ignored if socket path is provided.
1515
default: "127.0.0.1"
1616
mysqlPort:
1717
type: string
18-
description: The port number for connecting to MySQL.
18+
description: The port number for connecting to MySQL. Ignored if socket path is provided.
1919
default: "3306"
2020
mysqlUser:
2121
type: string
@@ -50,4 +50,4 @@ startCommand:
5050
description: If set to true, DELETE operations will be allowed.
5151
commandFunction:
5252
|-
53-
(config) => ({ "command": "node", "args": ["dist/index.js"], "env": { "MYSQL_HOST": config.mysqlHost, "MYSQL_PORT": config.mysqlPort, "MYSQL_USER": config.mysqlUser, "MYSQL_PASS": config.mysqlPass, "MYSQL_DB": config.mysqlDb, "MYSQL_SSL": config.ssl, "MYSQL_SSL_REJECT_UNAUTHORIZED": config.rejectUnauthorizedSSL, "ALLOW_INSERT_OPERATION": config.allowInsertOperation, "ALLOW_UPDATE_OPERATION": config.allowUpdateOperation, "ALLOW_DELETE_OPERATION": config.allowDeleteOperation } })
53+
(config) => ({ "command": "node", "args": ["dist/index.js"], "env": { "MYSQL_SOCKET_PATH": config.mysqlSocketPath, "MYSQL_HOST": config.mysqlHost, "MYSQL_PORT": config.mysqlPort, "MYSQL_USER": config.mysqlUser, "MYSQL_PASS": config.mysqlPass, "MYSQL_DB": config.mysqlDb, "MYSQL_SSL": config.ssl, "MYSQL_SSL_REJECT_UNAUTHORIZED": config.rejectUnauthorizedSSL, "ALLOW_INSERT_OPERATION": config.allowInsertOperation, "ALLOW_UPDATE_OPERATION": config.allowUpdateOperation, "ALLOW_DELETE_OPERATION": config.allowDeleteOperation } })
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import * as mysql2 from 'mysql2/promise';
2+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
3+
import * as dotenv from 'dotenv';
4+
import * as path from 'path';
5+
import { fileURLToPath } from 'url';
6+
7+
// Set test directory path
8+
const __filename = fileURLToPath(import.meta.url);
9+
const __dirname = path.dirname(__filename);
10+
11+
// Load test environment variables
12+
dotenv.config({ path: path.resolve(__dirname, '../../.env.test') });
13+
14+
describe('Unix Socket Connection', () => {
15+
// Skip these tests if no socket path is provided
16+
const skipTests = !process.env.MYSQL_SOCKET_PATH;
17+
18+
it.skipIf(skipTests)('should connect via Unix socket when MYSQL_SOCKET_PATH is set', async () => {
19+
const originalHost = process.env.MYSQL_HOST;
20+
const originalPort = process.env.MYSQL_PORT;
21+
const originalSocketPath = process.env.MYSQL_SOCKET_PATH;
22+
23+
try {
24+
// Set socket path (use the actual socket path from environment or a test path)
25+
process.env.MYSQL_SOCKET_PATH = originalSocketPath || '/tmp/mysql.sock';
26+
delete process.env.MYSQL_HOST;
27+
delete process.env.MYSQL_PORT;
28+
29+
// Create a connection pool using socket
30+
const config: any = {
31+
socketPath: process.env.MYSQL_SOCKET_PATH,
32+
user: process.env.MYSQL_USER || 'root',
33+
database: process.env.MYSQL_DB || 'mcp_test',
34+
connectionLimit: 5,
35+
};
36+
37+
// Only add password if it's set
38+
if (process.env.MYSQL_PASS) {
39+
config.password = process.env.MYSQL_PASS;
40+
}
41+
42+
const pool = mysql2.createPool(config);
43+
44+
// Test the connection
45+
const connection = await pool.getConnection();
46+
expect(connection).toBeDefined();
47+
48+
// Execute a simple query
49+
const [rows] = await connection.query('SELECT 1 as test') as [any[], any];
50+
expect(rows[0].test).toBe(1);
51+
52+
connection.release();
53+
await pool.end();
54+
} finally {
55+
// Restore original values
56+
if (originalHost) process.env.MYSQL_HOST = originalHost;
57+
if (originalPort) process.env.MYSQL_PORT = originalPort;
58+
if (originalSocketPath) process.env.MYSQL_SOCKET_PATH = originalSocketPath;
59+
else delete process.env.MYSQL_SOCKET_PATH;
60+
}
61+
});
62+
63+
it('should prefer socket path over host/port when both are provided', async () => {
64+
// This test verifies the configuration logic
65+
const mockConfig = {
66+
...(process.env.MYSQL_SOCKET_PATH
67+
? {
68+
socketPath: process.env.MYSQL_SOCKET_PATH,
69+
}
70+
: {
71+
host: process.env.MYSQL_HOST || '127.0.0.1',
72+
port: Number(process.env.MYSQL_PORT || '3306'),
73+
}
74+
),
75+
};
76+
77+
// If socket path is set, config should not have host/port
78+
if (process.env.MYSQL_SOCKET_PATH) {
79+
expect(mockConfig).toHaveProperty('socketPath');
80+
expect(mockConfig).not.toHaveProperty('host');
81+
expect(mockConfig).not.toHaveProperty('port');
82+
} else {
83+
expect(mockConfig).not.toHaveProperty('socketPath');
84+
expect(mockConfig).toHaveProperty('host');
85+
expect(mockConfig).toHaveProperty('port');
86+
}
87+
});
88+
});

0 commit comments

Comments
 (0)