@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.
- package/build/index.js +70 -55
- 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
|
|
334
|
-
|
|
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:
|
|
348
|
-
fk_column:
|
|
349
|
-
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(
|
|
353
|
-
visited.add(
|
|
375
|
+
if (!visited.has(relation.child)) {
|
|
376
|
+
visited.add(relation.child);
|
|
354
377
|
queue.push({
|
|
355
|
-
tableName:
|
|
378
|
+
tableName: relation.child,
|
|
356
379
|
depth: current.depth + 1,
|
|
357
|
-
path: [...current.path,
|
|
380
|
+
path: [...current.path, relation.child]
|
|
358
381
|
});
|
|
359
382
|
}
|
|
360
|
-
else if (current.path.includes(
|
|
383
|
+
else if (current.path.includes(relation.child)) {
|
|
361
384
|
// 순환 참조 감지
|
|
362
|
-
const cycleStart = current.path.indexOf(
|
|
363
|
-
const cyclePath = [...current.path.slice(cycleStart),
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
|
372
|
-
|
|
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 ===
|
|
385
|
-
r.parent_table ===
|
|
386
|
-
r.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:
|
|
391
|
-
fk_column:
|
|
392
|
-
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(
|
|
397
|
-
visited.add(
|
|
410
|
+
if (!visited.has(relation.parent)) {
|
|
411
|
+
visited.add(relation.parent);
|
|
398
412
|
queue.push({
|
|
399
|
-
tableName:
|
|
413
|
+
tableName: relation.parent,
|
|
400
414
|
depth: current.depth + 1,
|
|
401
|
-
path: [...current.path,
|
|
415
|
+
path: [...current.path, relation.parent]
|
|
402
416
|
});
|
|
403
417
|
}
|
|
404
|
-
else if (current.path.includes(
|
|
418
|
+
else if (current.path.includes(relation.parent)) {
|
|
405
419
|
// 순환 참조 감지
|
|
406
|
-
const cycleStart = current.path.indexOf(
|
|
407
|
-
const cyclePath = [...current.path.slice(cycleStart),
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
}
|