@cano721/mysql-mcp-server 0.1.14 → 0.2.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 +18 -9
- package/build/connection.js +5 -0
- package/build/index.js +9 -1
- package/build/validators.js +20 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,16 +22,17 @@
|
|
|
22
22
|
|
|
23
23
|
## 보안 기능
|
|
24
24
|
|
|
25
|
-
- **읽기 전용 접근**: SELECT, SHOW, DESCRIBE
|
|
25
|
+
- **읽기 전용 접근**: SELECT, SHOW, DESCRIBE 문 항상 허용, EXPLAIN 선택적 허용
|
|
26
26
|
- **쿼리 검증**: SQL 인젝션 방지 및 데이터 수정 시도 차단
|
|
27
27
|
- **쿼리 타임아웃**: 장시간 실행되는 쿼리로부터 리소스 보호
|
|
28
28
|
- **행 제한**: 과도한 데이터 반환 방지 (최대 1000행)
|
|
29
|
+
- **선택적 기능 제어**: EXPLAIN 쿼리 허용/차단 설정 가능
|
|
29
30
|
|
|
30
31
|
**지원되는 SQL 명령어**:
|
|
31
|
-
- `SELECT` - 데이터 조회 및 분석
|
|
32
|
-
- `SHOW` - 데이터베이스/테이블/인덱스 정보 조회
|
|
33
|
-
- `DESCRIBE` / `DESC` - 테이블 구조 및 컬럼 정보
|
|
34
|
-
- `EXPLAIN` - 쿼리 실행 계획 및 성능 분석
|
|
32
|
+
- `SELECT` - 데이터 조회 및 분석 (항상 허용)
|
|
33
|
+
- `SHOW` - 데이터베이스/테이블/인덱스 정보 조회 (항상 허용)
|
|
34
|
+
- `DESCRIBE` / `DESC` - 테이블 구조 및 컬럼 정보 (항상 허용)
|
|
35
|
+
- `EXPLAIN` - 쿼리 실행 계획 및 성능 분석 (선택적 허용, 기본값: 허용)
|
|
35
36
|
|
|
36
37
|
## 요구사항
|
|
37
38
|
|
|
@@ -91,11 +92,17 @@ npx -y @smithery/cli install @cano721/mysql-mcp-server --client claude
|
|
|
91
92
|
|
|
92
93
|
서버는 다음 환경 변수가 필요합니다:
|
|
93
94
|
|
|
95
|
+
**필수 환경 변수:**
|
|
94
96
|
- `MYSQL_HOST`: 데이터베이스 서버 호스트명
|
|
95
|
-
- `MYSQL_PORT`: 데이터베이스 서버 포트 (기본값: 3306)
|
|
96
97
|
- `MYSQL_USER`: 데이터베이스 사용자명
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
|
|
99
|
+
**선택적 환경 변수:**
|
|
100
|
+
- `MYSQL_PORT`: 데이터베이스 서버 포트 (기본값: 3306)
|
|
101
|
+
- `MYSQL_PASSWORD`: 데이터베이스 비밀번호 (보안 연결에 권장)
|
|
102
|
+
- `MYSQL_DATABASE`: 기본 데이터베이스명
|
|
103
|
+
|
|
104
|
+
**보안 설정:**
|
|
105
|
+
- `MYSQL_ALLOW_EXPLAIN`: EXPLAIN 쿼리 허용 여부 (기본값: true, 비활성화: false)
|
|
99
106
|
|
|
100
107
|
### 3. MCP 설정에 추가
|
|
101
108
|
|
|
@@ -290,7 +297,8 @@ MySQL 연결 풀 동작을 더 세밀하게 제어하려면 추가 매개변수
|
|
|
290
297
|
"MYSQL_QUEUE_LIMIT": "0",
|
|
291
298
|
"MYSQL_CONNECT_TIMEOUT": "10000",
|
|
292
299
|
"MYSQL_IDLE_TIMEOUT": "60000",
|
|
293
|
-
"MYSQL_MAX_IDLE": "10"
|
|
300
|
+
"MYSQL_MAX_IDLE": "10",
|
|
301
|
+
"MYSQL_ALLOW_EXPLAIN": "true"
|
|
294
302
|
},
|
|
295
303
|
"disabled": false,
|
|
296
304
|
"autoApprove": []
|
|
@@ -306,6 +314,7 @@ MySQL 연결 풀 동작을 더 세밀하게 제어하려면 추가 매개변수
|
|
|
306
314
|
- `MYSQL_CONNECT_TIMEOUT`: 연결 타임아웃을 밀리초 단위로 조정 (기본값: 10000)
|
|
307
315
|
- `MYSQL_IDLE_TIMEOUT`: 연결이 해제되기 전까지 유휴 상태로 있을 수 있는 시간 (밀리초 단위)
|
|
308
316
|
- `MYSQL_MAX_IDLE`: 풀에 유지할 최대 유휴 연결 수 설정
|
|
317
|
+
- `MYSQL_ALLOW_EXPLAIN`: EXPLAIN 쿼리 허용 여부 (기본값: true)
|
|
309
318
|
|
|
310
319
|
## 테스트
|
|
311
320
|
|
package/build/connection.js
CHANGED
|
@@ -102,6 +102,8 @@ export function getConfigFromEnv() {
|
|
|
102
102
|
const connectTimeoutStr = process.env.MYSQL_CONNECT_TIMEOUT;
|
|
103
103
|
const idleTimeoutStr = process.env.MYSQL_IDLE_TIMEOUT;
|
|
104
104
|
const maxIdleStr = process.env.MYSQL_MAX_IDLE;
|
|
105
|
+
// Security options
|
|
106
|
+
const allowExplain = process.env.MYSQL_ALLOW_EXPLAIN !== 'false'; // Default: true
|
|
105
107
|
if (!host)
|
|
106
108
|
throw new Error('MYSQL_HOST environment variable is required');
|
|
107
109
|
if (!user)
|
|
@@ -113,6 +115,9 @@ export function getConfigFromEnv() {
|
|
|
113
115
|
const connectTimeout = connectTimeoutStr ? parseInt(connectTimeoutStr, 10) : undefined;
|
|
114
116
|
const idleTimeout = idleTimeoutStr ? parseInt(idleTimeoutStr, 10) : undefined;
|
|
115
117
|
const maxIdle = maxIdleStr ? parseInt(maxIdleStr, 10) : undefined;
|
|
118
|
+
console.error('[Setup] Security settings:', {
|
|
119
|
+
allowExplain
|
|
120
|
+
});
|
|
116
121
|
return {
|
|
117
122
|
host,
|
|
118
123
|
port,
|
package/build/index.js
CHANGED
|
@@ -46,6 +46,14 @@ const server = new Server({
|
|
|
46
46
|
* Handler that lists available tools for MySQL database access
|
|
47
47
|
*/
|
|
48
48
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
49
|
+
// Check if EXPLAIN is enabled
|
|
50
|
+
const allowExplain = process.env.MYSQL_ALLOW_EXPLAIN !== 'false';
|
|
51
|
+
// Build allowed commands description
|
|
52
|
+
const allowedCommands = ['SELECT', 'SHOW', 'DESCRIBE'];
|
|
53
|
+
if (allowExplain) {
|
|
54
|
+
allowedCommands.push('EXPLAIN');
|
|
55
|
+
}
|
|
56
|
+
const commandsDescription = `SQL query (only ${allowedCommands.join(', ')} statements are allowed)`;
|
|
49
57
|
return {
|
|
50
58
|
tools: [
|
|
51
59
|
{
|
|
@@ -97,7 +105,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
97
105
|
properties: {
|
|
98
106
|
query: {
|
|
99
107
|
type: "string",
|
|
100
|
-
description:
|
|
108
|
+
description: commandsDescription
|
|
101
109
|
},
|
|
102
110
|
database: {
|
|
103
111
|
type: "string",
|
package/build/validators.js
CHANGED
|
@@ -8,6 +8,9 @@ const ALLOWED_COMMANDS = [
|
|
|
8
8
|
'SHOW',
|
|
9
9
|
'DESCRIBE',
|
|
10
10
|
'DESC',
|
|
11
|
+
];
|
|
12
|
+
// Optional commands that can be enabled/disabled
|
|
13
|
+
const OPTIONAL_COMMANDS = [
|
|
11
14
|
'EXPLAIN',
|
|
12
15
|
];
|
|
13
16
|
// List of disallowed SQL commands (write operations)
|
|
@@ -34,6 +37,18 @@ const DISALLOWED_COMMANDS = [
|
|
|
34
37
|
'COMMIT',
|
|
35
38
|
'ROLLBACK',
|
|
36
39
|
];
|
|
40
|
+
/**
|
|
41
|
+
* Get allowed commands based on environment configuration
|
|
42
|
+
*/
|
|
43
|
+
function getAllowedCommands() {
|
|
44
|
+
const allowedCommands = [...ALLOWED_COMMANDS];
|
|
45
|
+
// Check if EXPLAIN is enabled (default: true)
|
|
46
|
+
const allowExplain = process.env.MYSQL_ALLOW_EXPLAIN !== 'false';
|
|
47
|
+
if (allowExplain) {
|
|
48
|
+
allowedCommands.push(...OPTIONAL_COMMANDS);
|
|
49
|
+
}
|
|
50
|
+
return allowedCommands;
|
|
51
|
+
}
|
|
37
52
|
/**
|
|
38
53
|
* Validates if a SQL query is read-only
|
|
39
54
|
* @param query SQL query to validate
|
|
@@ -47,8 +62,10 @@ export function isReadOnlyQuery(query) {
|
|
|
47
62
|
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
48
63
|
.trim()
|
|
49
64
|
.toUpperCase();
|
|
65
|
+
// Get currently allowed commands
|
|
66
|
+
const allowedCommands = getAllowedCommands();
|
|
50
67
|
// Check if query starts with an allowed command
|
|
51
|
-
const startsWithAllowed =
|
|
68
|
+
const startsWithAllowed = allowedCommands.some(cmd => normalizedQuery.startsWith(cmd + ' ') || normalizedQuery === cmd);
|
|
52
69
|
// Check if query contains any disallowed commands
|
|
53
70
|
const containsDisallowed = DISALLOWED_COMMANDS.some(cmd => {
|
|
54
71
|
const regex = new RegExp(`(^|\\s)${cmd}(\\s|$)`);
|
|
@@ -73,7 +90,8 @@ export function validateQuery(query) {
|
|
|
73
90
|
}
|
|
74
91
|
if (!isReadOnlyQuery(query)) {
|
|
75
92
|
console.error('[Validator] Query rejected: not read-only');
|
|
76
|
-
|
|
93
|
+
const allowedCommands = getAllowedCommands();
|
|
94
|
+
throw new Error(`Only read-only queries are allowed (${allowedCommands.join(', ')})`);
|
|
77
95
|
}
|
|
78
96
|
console.error('[Validator] Query validated as read-only');
|
|
79
97
|
}
|