@cano721/mysql-mcp-server 0.9.1 → 0.9.3

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.
Files changed (3) hide show
  1. package/README.md +9 -17
  2. package/build/index.js +46 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -24,7 +24,7 @@
24
24
  ## 보안 기능
25
25
 
26
26
  - **읽기 전용 접근**: SELECT, SHOW, DESCRIBE 문 항상 허용
27
- - **분석 도구**: EXPLAIN, ANALYZE 기본 허용 (읽기 전용 분석)
27
+ - **분석 도구**: EXPLAIN, EXPLAIN ANALYZE 기본 허용 (읽기 전용 분석)
28
28
  - **쿼리 검증**: SQL 인젝션 방지 및 데이터 수정 시도 차단
29
29
  - **쿼리 타임아웃**: 장시간 실행되는 쿼리로부터 리소스 보호
30
30
  - **행 제한**: 과도한 데이터 반환 방지 (최대 1000행)
@@ -34,8 +34,8 @@
34
34
  - `SELECT` - 데이터 조회 및 분석 (항상 허용)
35
35
  - `SHOW` - 데이터베이스/테이블/인덱스 정보 조회 (항상 허용)
36
36
  - `DESCRIBE` / `DESC` - 테이블 구조 및 컬럼 정보 (항상 허용)
37
- - `EXPLAIN` - 쿼리 실행 계획 및 성능 분석 (기본 허용, 읽기 전용)
38
- - `ANALYZE` - 테이블 통계 분석 (기본 허용, 읽기 전용)
37
+ - `EXPLAIN` - 쿼리 실행 계획 분석 (기본 허용, 읽기 전용)
38
+ - `EXPLAIN ANALYZE` - 쿼리 실제 실행 및 성능 분석 (기본 허용, 읽기 전용)
39
39
 
40
40
  ## 요구사항
41
41
 
@@ -281,18 +281,6 @@ MySQL 서버에서 접근 가능한 모든 데이터베이스를 나열합니다
281
281
  }
282
282
  ```
283
283
 
284
- **ANALYZE 사용 예제**:
285
- ```json
286
- {
287
- "server_name": "mysql",
288
- "tool_name": "execute_query",
289
- "arguments": {
290
- "database": "my_database",
291
- "query": "ANALYZE TABLE my_table"
292
- }
293
- }
294
- ```
295
-
296
284
  ### explain_query
297
285
 
298
286
  쿼리 실행 계획을 분석합니다 (MYSQL_ALLOW_EXPLAIN=true 필요).
@@ -317,12 +305,16 @@ MySQL 서버에서 접근 가능한 모든 데이터베이스를 나열합니다
317
305
 
318
306
  ### analyze_query
319
307
 
320
- 쿼리 성능 통계를 분석합니다 (MYSQL_ALLOW_ANALYZE=true 필요).
308
+ 쿼리를 실제로 실행하면서 성능 통계를 분석합니다 (MYSQL_ALLOW_ANALYZE=true 필요).
309
+
310
+ `EXPLAIN ANALYZE` 구문을 사용하여 쿼리를 실제로 실행하고, 각 단계별 실행 시간과 행 수 등의 상세한 성능 정보를 제공합니다.
321
311
 
322
312
  **매개변수**:
323
- - `query` (필수): 분석할 SQL 쿼리
313
+ - `query` (필수): 분석할 SQL 쿼리 (SELECT 문)
324
314
  - `database` (선택사항): 데이터베이스명
325
315
 
316
+ **주의**: 이 도구는 쿼리를 실제로 실행하므로, 대용량 데이터 조회 시 시간이 오래 걸릴 수 있습니다.
317
+
326
318
  **예제**:
327
319
  ```json
328
320
  {
package/build/index.js CHANGED
@@ -63,7 +63,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
63
63
  const tools = [
64
64
  {
65
65
  name: "list_databases",
66
- description: "List all accessible databases on the MySQL server",
66
+ description: "List all accessible databases on the MySQL server. 한글: 데이터베이스 목록, DB 목록, 데이터베이스 조회",
67
67
  inputSchema: {
68
68
  type: "object",
69
69
  properties: {},
@@ -72,7 +72,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
72
72
  },
73
73
  {
74
74
  name: "list_tables",
75
- description: "List all tables in a specified database",
75
+ description: "List all tables in a specified database. 한글: 테이블 목록, 테이블 조회, 테이블 리스트",
76
76
  inputSchema: {
77
77
  type: "object",
78
78
  properties: {
@@ -86,7 +86,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
86
86
  },
87
87
  {
88
88
  name: "describe_table",
89
- description: "Show the schema for a specific table",
89
+ description: "Show the schema for a specific table. 한글: 테이블 스키마, 테이블 구조, 컬럼 정보, 테이블 설명",
90
90
  inputSchema: {
91
91
  type: "object",
92
92
  properties: {
@@ -104,7 +104,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
104
104
  },
105
105
  {
106
106
  name: "execute_query",
107
- description: "Execute a read-only SQL query",
107
+ description: "Execute a read-only SQL query. 한글: 쿼리 실행, SQL 실행, 조회 쿼리",
108
108
  inputSchema: {
109
109
  type: "object",
110
110
  properties: {
@@ -122,7 +122,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
122
122
  },
123
123
  {
124
124
  name: "get_related_tables",
125
- description: "Find all related/connected/associated tables linked to a specific table through foreign keys. Discovers table relationships and dependencies with depth traversal. Use this when asked to find related tables, connected tables, associated tables, or table relationships.",
125
+ description: "Find all related/connected/associated tables linked to a specific table through foreign keys. Discovers table relationships and dependencies with depth traversal. Use this when asked to find related tables, connected tables, associated tables, or table relationships. 한글: 연관 테이블, 관련 테이블, 연결된 테이블, 테이블 관계, 참조 테이블, FK 관계",
126
126
  inputSchema: {
127
127
  type: "object",
128
128
  properties: {
@@ -151,7 +151,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
151
151
  if (allowExplain) {
152
152
  tools.push({
153
153
  name: "explain_query",
154
- description: "Analyze query execution plan using EXPLAIN",
154
+ description: "Analyze query execution plan using EXPLAIN. 한글: 쿼리 실행 계획, 쿼리 분석, EXPLAIN",
155
155
  inputSchema: {
156
156
  type: "object",
157
157
  properties: {
@@ -177,7 +177,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
177
177
  if (allowAnalyze) {
178
178
  tools.push({
179
179
  name: "analyze_query",
180
- description: "Analyze query performance and statistics using ANALYZE",
180
+ description: "Analyze query performance and statistics using ANALYZE. 한글: 쿼리 성능 분석, 쿼리 통계, ANALYZE",
181
181
  inputSchema: {
182
182
  type: "object",
183
183
  properties: {
@@ -320,7 +320,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
320
320
  }
321
321
  const results = [];
322
322
  const visited = new Set();
323
- const queue = [{ tableName: table, depth: 0 }];
323
+ const circularReferences = [];
324
+ const queue = [
325
+ { tableName: table, depth: 0, path: [table] }
326
+ ];
324
327
  visited.add(table);
325
328
  while (queue.length > 0) {
326
329
  const current = queue.shift();
@@ -331,8 +334,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
331
334
  SELECT
332
335
  TABLE_NAME as child_table,
333
336
  COLUMN_NAME as fk_column,
334
- REFERENCED_TABLE_NAME as parent_table,
335
- CONSTRAINT_NAME as constraint_name
337
+ REFERENCED_TABLE_NAME as parent_table
336
338
  FROM information_schema.KEY_COLUMN_USAGE
337
339
  WHERE REFERENCED_TABLE_SCHEMA = ?
338
340
  AND REFERENCED_TABLE_NAME = ?
@@ -345,12 +347,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
345
347
  child_table: row.child_table,
346
348
  fk_column: row.fk_column,
347
349
  parent_table: row.parent_table,
348
- constraint_name: row.constraint_name,
349
350
  match_type: 'fk_constraint'
350
351
  });
351
352
  if (!visited.has(row.child_table)) {
352
353
  visited.add(row.child_table);
353
- queue.push({ tableName: row.child_table, depth: current.depth + 1 });
354
+ queue.push({
355
+ tableName: row.child_table,
356
+ depth: current.depth + 1,
357
+ path: [...current.path, row.child_table]
358
+ });
359
+ }
360
+ else if (current.path.includes(row.child_table)) {
361
+ // 순환 참조 감지
362
+ const cycleStart = current.path.indexOf(row.child_table);
363
+ const cyclePath = [...current.path.slice(cycleStart), row.child_table];
364
+ circularReferences.push({
365
+ path: cyclePath,
366
+ description: `${cyclePath.join(' → ')}`
367
+ });
354
368
  }
355
369
  }
356
370
  // Find parent tables (tables that the current table references)
@@ -358,8 +372,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
358
372
  SELECT
359
373
  TABLE_NAME as child_table,
360
374
  COLUMN_NAME as fk_column,
361
- REFERENCED_TABLE_NAME as parent_table,
362
- CONSTRAINT_NAME as constraint_name
375
+ REFERENCED_TABLE_NAME as parent_table
363
376
  FROM information_schema.KEY_COLUMN_USAGE
364
377
  WHERE TABLE_SCHEMA = ?
365
378
  AND TABLE_NAME = ?
@@ -377,13 +390,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
377
390
  child_table: row.child_table,
378
391
  fk_column: row.fk_column,
379
392
  parent_table: row.parent_table,
380
- constraint_name: row.constraint_name,
381
393
  match_type: 'fk_constraint'
382
394
  });
383
395
  }
384
396
  if (!visited.has(row.parent_table)) {
385
397
  visited.add(row.parent_table);
386
- queue.push({ tableName: row.parent_table, depth: current.depth + 1 });
398
+ queue.push({
399
+ tableName: row.parent_table,
400
+ depth: current.depth + 1,
401
+ path: [...current.path, row.parent_table]
402
+ });
403
+ }
404
+ else if (current.path.includes(row.parent_table)) {
405
+ // 순환 참조 감지
406
+ const cycleStart = current.path.indexOf(row.parent_table);
407
+ const cyclePath = [...current.path.slice(cycleStart), row.parent_table];
408
+ circularReferences.push({
409
+ path: cyclePath,
410
+ description: `${cyclePath.join(' → ')}`
411
+ });
387
412
  }
388
413
  }
389
414
  }
@@ -423,7 +448,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
423
448
  child_table: row.related_table,
424
449
  fk_column: row.matching_column,
425
450
  parent_table: table,
426
- constraint_name: '(pattern match)',
427
451
  match_type: 'pattern_match'
428
452
  });
429
453
  }
@@ -437,6 +461,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
437
461
  fk_relations_count: results.length,
438
462
  pattern_match_count: patternMatchResults.length,
439
463
  total_relations: results.length + patternMatchResults.length,
464
+ circular_references_detected: circularReferences.length > 0,
465
+ circular_references: circularReferences.length > 0 ? circularReferences : undefined,
440
466
  fk_relations: results,
441
467
  pattern_match_relations: includePatternMatch ? patternMatchResults : undefined,
442
468
  note: includePatternMatch
@@ -446,6 +472,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
446
472
  if (warning) {
447
473
  response.warning = warning;
448
474
  }
475
+ if (circularReferences.length > 0) {
476
+ response.circular_reference_note = `⚠️ 순환참조가 감지되었습니다 (${circularReferences.length}개). 이미 방문한 테이블은 재탐색하지 않았습니다.`;
477
+ }
449
478
  return {
450
479
  content: [{
451
480
  type: "text",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cano721/mysql-mcp-server",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "An MCP server that provides read-only access to MySQL databases.",
5
5
  "type": "module",
6
6
  "bin": {