@cano721/mysql-mcp-server 0.9.2 → 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 +44 -10
  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();
@@ -348,7 +351,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
348
351
  });
349
352
  if (!visited.has(row.child_table)) {
350
353
  visited.add(row.child_table);
351
- 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
+ });
352
368
  }
353
369
  }
354
370
  // Find parent tables (tables that the current table references)
@@ -379,7 +395,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
379
395
  }
380
396
  if (!visited.has(row.parent_table)) {
381
397
  visited.add(row.parent_table);
382
- 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
+ });
383
412
  }
384
413
  }
385
414
  }
@@ -432,6 +461,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
432
461
  fk_relations_count: results.length,
433
462
  pattern_match_count: patternMatchResults.length,
434
463
  total_relations: results.length + patternMatchResults.length,
464
+ circular_references_detected: circularReferences.length > 0,
465
+ circular_references: circularReferences.length > 0 ? circularReferences : undefined,
435
466
  fk_relations: results,
436
467
  pattern_match_relations: includePatternMatch ? patternMatchResults : undefined,
437
468
  note: includePatternMatch
@@ -441,6 +472,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
441
472
  if (warning) {
442
473
  response.warning = warning;
443
474
  }
475
+ if (circularReferences.length > 0) {
476
+ response.circular_reference_note = `⚠️ 순환참조가 감지되었습니다 (${circularReferences.length}개). 이미 방문한 테이블은 재탐색하지 않았습니다.`;
477
+ }
444
478
  return {
445
479
  content: [{
446
480
  type: "text",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cano721/mysql-mcp-server",
3
- "version": "0.9.2",
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": {