@cano721/mysql-mcp-server 0.3.0 → 0.4.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 +49 -7
- package/build/connection.js +1 -1
- package/build/index.js +145 -61
- package/build/validators.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
## 보안 기능
|
|
24
24
|
|
|
25
25
|
- **읽기 전용 접근**: SELECT, SHOW, DESCRIBE 문 항상 허용
|
|
26
|
-
-
|
|
26
|
+
- **분석 도구**: EXPLAIN, ANALYZE 기본 허용 (읽기 전용 분석)
|
|
27
27
|
- **쿼리 검증**: SQL 인젝션 방지 및 데이터 수정 시도 차단
|
|
28
28
|
- **쿼리 타임아웃**: 장시간 실행되는 쿼리로부터 리소스 보호
|
|
29
29
|
- **행 제한**: 과도한 데이터 반환 방지 (최대 1000행)
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
- `SELECT` - 데이터 조회 및 분석 (항상 허용)
|
|
34
34
|
- `SHOW` - 데이터베이스/테이블/인덱스 정보 조회 (항상 허용)
|
|
35
35
|
- `DESCRIBE` / `DESC` - 테이블 구조 및 컬럼 정보 (항상 허용)
|
|
36
|
-
- `EXPLAIN` - 쿼리 실행 계획 및 성능 분석 (
|
|
37
|
-
- `ANALYZE` - 테이블 통계 분석
|
|
36
|
+
- `EXPLAIN` - 쿼리 실행 계획 및 성능 분석 (기본 허용, 읽기 전용)
|
|
37
|
+
- `ANALYZE` - 테이블 통계 분석 (기본 허용, 읽기 전용)
|
|
38
38
|
|
|
39
39
|
## 요구사항
|
|
40
40
|
|
|
@@ -105,7 +105,7 @@ npx -y @smithery/cli install @cano721/mysql-mcp-server --client claude
|
|
|
105
105
|
|
|
106
106
|
**보안 설정:**
|
|
107
107
|
- `MYSQL_ALLOW_EXPLAIN`: EXPLAIN 쿼리 허용 여부 (기본값: true, 비활성화: false)
|
|
108
|
-
- `MYSQL_ALLOW_ANALYZE`: ANALYZE 쿼리 허용 여부 (기본값:
|
|
108
|
+
- `MYSQL_ALLOW_ANALYZE`: ANALYZE 쿼리 허용 여부 (기본값: true, 비활성화: false)
|
|
109
109
|
|
|
110
110
|
### 3. MCP 설정에 추가
|
|
111
111
|
|
|
@@ -267,7 +267,7 @@ MySQL 서버에서 접근 가능한 모든 데이터베이스를 나열합니다
|
|
|
267
267
|
}
|
|
268
268
|
```
|
|
269
269
|
|
|
270
|
-
**ANALYZE 사용
|
|
270
|
+
**ANALYZE 사용 예제**:
|
|
271
271
|
```json
|
|
272
272
|
{
|
|
273
273
|
"server_name": "mysql",
|
|
@@ -279,6 +279,48 @@ MySQL 서버에서 접근 가능한 모든 데이터베이스를 나열합니다
|
|
|
279
279
|
}
|
|
280
280
|
```
|
|
281
281
|
|
|
282
|
+
### explain_query
|
|
283
|
+
|
|
284
|
+
쿼리 실행 계획을 분석합니다 (MYSQL_ALLOW_EXPLAIN=true 필요).
|
|
285
|
+
|
|
286
|
+
**매개변수**:
|
|
287
|
+
- `query` (필수): 분석할 SQL 쿼리
|
|
288
|
+
- `database` (선택사항): 데이터베이스명
|
|
289
|
+
- `format` (선택사항): 출력 형식 (TRADITIONAL, JSON, TREE)
|
|
290
|
+
|
|
291
|
+
**예제**:
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"server_name": "mysql",
|
|
295
|
+
"tool_name": "explain_query",
|
|
296
|
+
"arguments": {
|
|
297
|
+
"database": "my_database",
|
|
298
|
+
"query": "SELECT * FROM users WHERE id = 1",
|
|
299
|
+
"format": "JSON"
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### analyze_table
|
|
305
|
+
|
|
306
|
+
테이블 통계를 분석합니다 (MYSQL_ALLOW_ANALYZE=true 필요).
|
|
307
|
+
|
|
308
|
+
**매개변수**:
|
|
309
|
+
- `table` (필수): 분석할 테이블명
|
|
310
|
+
- `database` (선택사항): 데이터베이스명
|
|
311
|
+
|
|
312
|
+
**예제**:
|
|
313
|
+
```json
|
|
314
|
+
{
|
|
315
|
+
"server_name": "mysql",
|
|
316
|
+
"tool_name": "analyze_table",
|
|
317
|
+
"arguments": {
|
|
318
|
+
"database": "my_database",
|
|
319
|
+
"table": "users"
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
282
324
|
**SHOW 명령어 예제**:
|
|
283
325
|
```json
|
|
284
326
|
{
|
|
@@ -314,7 +356,7 @@ MySQL 연결 풀 동작을 더 세밀하게 제어하려면 추가 매개변수
|
|
|
314
356
|
"MYSQL_IDLE_TIMEOUT": "60000",
|
|
315
357
|
"MYSQL_MAX_IDLE": "10",
|
|
316
358
|
"MYSQL_ALLOW_EXPLAIN": "true",
|
|
317
|
-
"MYSQL_ALLOW_ANALYZE": "
|
|
359
|
+
"MYSQL_ALLOW_ANALYZE": "true"
|
|
318
360
|
},
|
|
319
361
|
"disabled": false,
|
|
320
362
|
"autoApprove": []
|
|
@@ -331,7 +373,7 @@ MySQL 연결 풀 동작을 더 세밀하게 제어하려면 추가 매개변수
|
|
|
331
373
|
- `MYSQL_IDLE_TIMEOUT`: 연결이 해제되기 전까지 유휴 상태로 있을 수 있는 시간 (밀리초 단위)
|
|
332
374
|
- `MYSQL_MAX_IDLE`: 풀에 유지할 최대 유휴 연결 수 설정
|
|
333
375
|
- `MYSQL_ALLOW_EXPLAIN`: EXPLAIN 쿼리 허용 여부 (기본값: true)
|
|
334
|
-
- `MYSQL_ALLOW_ANALYZE`: ANALYZE 쿼리 허용 여부 (기본값:
|
|
376
|
+
- `MYSQL_ALLOW_ANALYZE`: ANALYZE 쿼리 허용 여부 (기본값: true)
|
|
335
377
|
|
|
336
378
|
## 테스트
|
|
337
379
|
|
package/build/connection.js
CHANGED
|
@@ -104,7 +104,7 @@ export function getConfigFromEnv() {
|
|
|
104
104
|
const maxIdleStr = process.env.MYSQL_MAX_IDLE;
|
|
105
105
|
// Security options
|
|
106
106
|
const allowExplain = process.env.MYSQL_ALLOW_EXPLAIN !== 'false'; // Default: true
|
|
107
|
-
const allowAnalyze = process.env.MYSQL_ALLOW_ANALYZE
|
|
107
|
+
const allowAnalyze = process.env.MYSQL_ALLOW_ANALYZE !== 'false'; // Default: true
|
|
108
108
|
if (!host)
|
|
109
109
|
throw new Error('MYSQL_HOST environment variable is required');
|
|
110
110
|
if (!user)
|
package/build/index.js
CHANGED
|
@@ -48,8 +48,8 @@ const server = new Server({
|
|
|
48
48
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
49
49
|
// Check if optional commands are enabled
|
|
50
50
|
const allowExplain = process.env.MYSQL_ALLOW_EXPLAIN !== 'false';
|
|
51
|
-
const allowAnalyze = process.env.MYSQL_ALLOW_ANALYZE
|
|
52
|
-
// Build allowed commands description
|
|
51
|
+
const allowAnalyze = process.env.MYSQL_ALLOW_ANALYZE !== 'false';
|
|
52
|
+
// Build allowed commands description for execute_query
|
|
53
53
|
const allowedCommands = ['SELECT', 'SHOW', 'DESCRIBE'];
|
|
54
54
|
if (allowExplain) {
|
|
55
55
|
allowedCommands.push('EXPLAIN');
|
|
@@ -58,69 +58,116 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
58
58
|
allowedCommands.push('ANALYZE');
|
|
59
59
|
}
|
|
60
60
|
const commandsDescription = `SQL query (only ${allowedCommands.join(', ')} statements are allowed)`;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
61
|
+
// Build tools array
|
|
62
|
+
const tools = [
|
|
63
|
+
{
|
|
64
|
+
name: "list_databases",
|
|
65
|
+
description: "List all accessible databases on the MySQL server",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: "object",
|
|
68
|
+
properties: {},
|
|
69
|
+
required: []
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "list_tables",
|
|
74
|
+
description: "List all tables in a specified database",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
database: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "Database name (optional, uses default if not specified)"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
required: []
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "describe_table",
|
|
88
|
+
description: "Show the schema for a specific table",
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
database: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "Database name (optional, uses default if not specified)"
|
|
82
95
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
table: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "Table name"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
required: ["table"]
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "execute_query",
|
|
106
|
+
description: "Execute a read-only SQL query",
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {
|
|
110
|
+
query: {
|
|
111
|
+
type: "string",
|
|
112
|
+
description: commandsDescription
|
|
100
113
|
},
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
database: {
|
|
115
|
+
type: "string",
|
|
116
|
+
description: "Database name (optional, uses default if not specified)"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
required: ["query"]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
];
|
|
123
|
+
// Add explain_query tool if enabled
|
|
124
|
+
if (allowExplain) {
|
|
125
|
+
tools.push({
|
|
126
|
+
name: "explain_query",
|
|
127
|
+
description: "Analyze query execution plan using EXPLAIN",
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
query: {
|
|
132
|
+
type: "string",
|
|
133
|
+
description: "SQL query to analyze (SELECT, UPDATE, DELETE, INSERT, REPLACE statements)"
|
|
118
134
|
},
|
|
119
|
-
|
|
120
|
-
|
|
135
|
+
database: {
|
|
136
|
+
type: "string",
|
|
137
|
+
description: "Database name (optional, uses default if not specified)"
|
|
138
|
+
},
|
|
139
|
+
format: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "Output format: TRADITIONAL, JSON, or TREE (optional, default: TRADITIONAL)",
|
|
142
|
+
enum: ["TRADITIONAL", "JSON", "TREE"]
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
required: ["query"]
|
|
121
146
|
}
|
|
122
|
-
|
|
123
|
-
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
// Add analyze_query tool if enabled
|
|
150
|
+
if (allowAnalyze) {
|
|
151
|
+
tools.push({
|
|
152
|
+
name: "analyze_table",
|
|
153
|
+
description: "Analyze table statistics for query optimization",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {
|
|
157
|
+
table: {
|
|
158
|
+
type: "string",
|
|
159
|
+
description: "Table name to analyze"
|
|
160
|
+
},
|
|
161
|
+
database: {
|
|
162
|
+
type: "string",
|
|
163
|
+
description: "Database name (optional, uses default if not specified)"
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
required: ["table"]
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return { tools };
|
|
124
171
|
});
|
|
125
172
|
/**
|
|
126
173
|
* Handler for MySQL database access tools
|
|
@@ -181,6 +228,43 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
181
228
|
}]
|
|
182
229
|
};
|
|
183
230
|
}
|
|
231
|
+
case "explain_query": {
|
|
232
|
+
console.error('[Tool] Executing explain_query');
|
|
233
|
+
const query = request.params.arguments?.query;
|
|
234
|
+
const database = request.params.arguments?.database;
|
|
235
|
+
const format = request.params.arguments?.format;
|
|
236
|
+
if (!query) {
|
|
237
|
+
throw new McpError(ErrorCode.InvalidParams, "Query is required");
|
|
238
|
+
}
|
|
239
|
+
// Build EXPLAIN query
|
|
240
|
+
let explainQuery = 'EXPLAIN';
|
|
241
|
+
if (format && ['JSON', 'TREE'].includes(format.toUpperCase())) {
|
|
242
|
+
explainQuery += ` FORMAT=${format.toUpperCase()}`;
|
|
243
|
+
}
|
|
244
|
+
explainQuery += ` ${query}`;
|
|
245
|
+
const { rows } = await executeQuery(pool, explainQuery, [], database);
|
|
246
|
+
return {
|
|
247
|
+
content: [{
|
|
248
|
+
type: "text",
|
|
249
|
+
text: JSON.stringify(rows, null, 2)
|
|
250
|
+
}]
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
case "analyze_table": {
|
|
254
|
+
console.error('[Tool] Executing analyze_table');
|
|
255
|
+
const table = request.params.arguments?.table;
|
|
256
|
+
const database = request.params.arguments?.database;
|
|
257
|
+
if (!table) {
|
|
258
|
+
throw new McpError(ErrorCode.InvalidParams, "Table name is required");
|
|
259
|
+
}
|
|
260
|
+
const { rows } = await executeQuery(pool, `ANALYZE TABLE \`${table}\``, [], database);
|
|
261
|
+
return {
|
|
262
|
+
content: [{
|
|
263
|
+
type: "text",
|
|
264
|
+
text: JSON.stringify(rows, null, 2)
|
|
265
|
+
}]
|
|
266
|
+
};
|
|
267
|
+
}
|
|
184
268
|
default:
|
|
185
269
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
186
270
|
}
|
package/build/validators.js
CHANGED
|
@@ -48,8 +48,8 @@ function getAllowedCommands() {
|
|
|
48
48
|
if (allowExplain) {
|
|
49
49
|
allowedCommands.push('EXPLAIN');
|
|
50
50
|
}
|
|
51
|
-
// Check if ANALYZE is enabled (default:
|
|
52
|
-
const allowAnalyze = process.env.MYSQL_ALLOW_ANALYZE
|
|
51
|
+
// Check if ANALYZE is enabled (default: true)
|
|
52
|
+
const allowAnalyze = process.env.MYSQL_ALLOW_ANALYZE !== 'false';
|
|
53
53
|
if (allowAnalyze) {
|
|
54
54
|
allowedCommands.push('ANALYZE');
|
|
55
55
|
}
|