@cano721/mysql-mcp-server 0.9.3 → 0.9.4

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 (2) hide show
  1. package/build/index.js +70 -55
  2. package/package.json +1 -1
package/build/index.js CHANGED
@@ -318,6 +318,39 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
318
318
  throw new McpError(ErrorCode.InvalidParams, "Database name is required (no default database set)");
319
319
  }
320
320
  }
321
+ // 🚀 최적화: 한 번에 모든 FK 관계를 가져오기
322
+ console.error('[Tool] Fetching all FK relationships at once');
323
+ const allFKQuery = `
324
+ SELECT
325
+ TABLE_NAME as child_table,
326
+ COLUMN_NAME as fk_column,
327
+ REFERENCED_TABLE_NAME as parent_table
328
+ FROM information_schema.KEY_COLUMN_USAGE
329
+ WHERE TABLE_SCHEMA = ?
330
+ AND REFERENCED_TABLE_NAME IS NOT NULL
331
+ `;
332
+ const { rows: allFKRows } = await executeQuery(pool, allFKQuery, [dbName]);
333
+ // FK 관계를 Map으로 구성 (빠른 조회를 위해)
334
+ const childToParents = new Map();
335
+ const parentToChildren = new Map();
336
+ for (const row of allFKRows) {
337
+ const relation = {
338
+ child: row.child_table,
339
+ fk_column: row.fk_column,
340
+ parent: row.parent_table
341
+ };
342
+ // child -> parent 매핑
343
+ if (!childToParents.has(row.child_table)) {
344
+ childToParents.set(row.child_table, []);
345
+ }
346
+ childToParents.get(row.child_table).push(relation);
347
+ // parent -> child 매핑
348
+ if (!parentToChildren.has(row.parent_table)) {
349
+ parentToChildren.set(row.parent_table, []);
350
+ }
351
+ parentToChildren.get(row.parent_table).push(relation);
352
+ }
353
+ console.error(`[Tool] Loaded ${allFKRows.length} FK relationships into memory`);
321
354
  const results = [];
322
355
  const visited = new Set();
323
356
  const circularReferences = [];
@@ -330,85 +363,67 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
330
363
  if (current.depth >= requestedDepth)
331
364
  continue;
332
365
  // Find child tables (tables that reference the current table)
333
- const childQuery = `
334
- SELECT
335
- TABLE_NAME as child_table,
336
- COLUMN_NAME as fk_column,
337
- REFERENCED_TABLE_NAME as parent_table
338
- FROM information_schema.KEY_COLUMN_USAGE
339
- WHERE REFERENCED_TABLE_SCHEMA = ?
340
- AND REFERENCED_TABLE_NAME = ?
341
- AND REFERENCED_TABLE_NAME IS NOT NULL
342
- `;
343
- const { rows: childRows } = await executeQuery(pool, childQuery, [dbName, current.tableName]);
344
- for (const row of childRows) {
366
+ const childRelations = parentToChildren.get(current.tableName) || [];
367
+ for (const relation of childRelations) {
345
368
  results.push({
346
369
  depth: current.depth + 1,
347
- child_table: row.child_table,
348
- fk_column: row.fk_column,
349
- parent_table: row.parent_table,
370
+ child_table: relation.child,
371
+ fk_column: relation.fk_column,
372
+ parent_table: relation.parent,
350
373
  match_type: 'fk_constraint'
351
374
  });
352
- if (!visited.has(row.child_table)) {
353
- visited.add(row.child_table);
375
+ if (!visited.has(relation.child)) {
376
+ visited.add(relation.child);
354
377
  queue.push({
355
- tableName: row.child_table,
378
+ tableName: relation.child,
356
379
  depth: current.depth + 1,
357
- path: [...current.path, row.child_table]
380
+ path: [...current.path, relation.child]
358
381
  });
359
382
  }
360
- else if (current.path.includes(row.child_table)) {
383
+ else if (current.path.includes(relation.child)) {
361
384
  // 순환 참조 감지
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
- });
385
+ const cycleStart = current.path.indexOf(relation.child);
386
+ const cyclePath = [...current.path.slice(cycleStart), relation.child];
387
+ const description = cyclePath.join(' → ');
388
+ // 중복 제거
389
+ if (!circularReferences.some(ref => ref.description === description)) {
390
+ circularReferences.push({ path: cyclePath, description });
391
+ }
368
392
  }
369
393
  }
370
394
  // Find parent tables (tables that the current table references)
371
- const parentQuery = `
372
- SELECT
373
- TABLE_NAME as child_table,
374
- COLUMN_NAME as fk_column,
375
- REFERENCED_TABLE_NAME as parent_table
376
- FROM information_schema.KEY_COLUMN_USAGE
377
- WHERE TABLE_SCHEMA = ?
378
- AND TABLE_NAME = ?
379
- AND REFERENCED_TABLE_NAME IS NOT NULL
380
- `;
381
- const { rows: parentRows } = await executeQuery(pool, parentQuery, [dbName, current.tableName]);
382
- for (const row of parentRows) {
395
+ const parentRelations = childToParents.get(current.tableName) || [];
396
+ for (const relation of parentRelations) {
383
397
  // Only add if not already in results (avoid duplicates)
384
- const exists = results.some(r => r.child_table === row.child_table &&
385
- r.parent_table === row.parent_table &&
386
- r.fk_column === row.fk_column);
398
+ const exists = results.some(r => r.child_table === relation.child &&
399
+ r.parent_table === relation.parent &&
400
+ r.fk_column === relation.fk_column);
387
401
  if (!exists) {
388
402
  results.push({
389
403
  depth: current.depth + 1,
390
- child_table: row.child_table,
391
- fk_column: row.fk_column,
392
- parent_table: row.parent_table,
404
+ child_table: relation.child,
405
+ fk_column: relation.fk_column,
406
+ parent_table: relation.parent,
393
407
  match_type: 'fk_constraint'
394
408
  });
395
409
  }
396
- if (!visited.has(row.parent_table)) {
397
- visited.add(row.parent_table);
410
+ if (!visited.has(relation.parent)) {
411
+ visited.add(relation.parent);
398
412
  queue.push({
399
- tableName: row.parent_table,
413
+ tableName: relation.parent,
400
414
  depth: current.depth + 1,
401
- path: [...current.path, row.parent_table]
415
+ path: [...current.path, relation.parent]
402
416
  });
403
417
  }
404
- else if (current.path.includes(row.parent_table)) {
418
+ else if (current.path.includes(relation.parent)) {
405
419
  // 순환 참조 감지
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
- });
420
+ const cycleStart = current.path.indexOf(relation.parent);
421
+ const cyclePath = [...current.path.slice(cycleStart), relation.parent];
422
+ const description = cyclePath.join(' → ');
423
+ // 중복 제거
424
+ if (!circularReferences.some(ref => ref.description === description)) {
425
+ circularReferences.push({ path: cyclePath, description });
426
+ }
412
427
  }
413
428
  }
414
429
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cano721/mysql-mcp-server",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "description": "An MCP server that provides read-only access to MySQL databases.",
5
5
  "type": "module",
6
6
  "bin": {