@adevguide/mcp-database-server 1.0.2 → 1.0.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.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.ts
4
4
  import dotenv from "dotenv";
5
5
  import { parseArgs } from "util";
6
- import { readFileSync } from "fs";
6
+ import { readFileSync, existsSync as existsSync2 } from "fs";
7
7
  import { fileURLToPath } from "url";
8
8
  import { dirname as dirname2, join as join2 } from "path";
9
9
 
@@ -264,7 +264,34 @@ function findJoinPaths(tables, relationships, maxDepth = 3) {
264
264
  }
265
265
 
266
266
  // src/config.ts
267
+ function findProjectRoot(startDir = process.cwd()) {
268
+ const projectMarkers = ["package.json", ".git", "tsconfig.json", "pyproject.toml", "Cargo.toml", "go.mod"];
269
+ let currentDir = resolve(startDir);
270
+ while (true) {
271
+ for (const marker of projectMarkers) {
272
+ if (existsSync(join(currentDir, marker))) {
273
+ return currentDir;
274
+ }
275
+ }
276
+ const parentDir = dirname(currentDir);
277
+ if (parentDir === currentDir) {
278
+ break;
279
+ }
280
+ currentDir = parentDir;
281
+ }
282
+ return null;
283
+ }
267
284
  function findConfigFile(fileName, startDir = process.cwd()) {
285
+ const projectRoot = findProjectRoot(startDir);
286
+ if (projectRoot) {
287
+ const configFromProjectRoot = findConfigFileFromDir(fileName, projectRoot);
288
+ if (configFromProjectRoot) {
289
+ return configFromProjectRoot;
290
+ }
291
+ }
292
+ return findConfigFileFromDir(fileName, startDir);
293
+ }
294
+ function findConfigFileFromDir(fileName, startDir) {
268
295
  let currentDir = resolve(startDir);
269
296
  while (true) {
270
297
  const configPath = join(currentDir, fileName);
@@ -336,17 +363,21 @@ import pg from "pg";
336
363
  import pino from "pino";
337
364
  var logger;
338
365
  function initLogger(level = "info", pretty = false) {
339
- logger = pino({
340
- level,
341
- transport: pretty ? {
342
- target: "pino-pretty",
343
- options: {
344
- colorize: true,
345
- translateTime: "SYS:standard",
346
- ignore: "pid,hostname"
347
- }
348
- } : void 0
349
- });
366
+ logger = pino(
367
+ {
368
+ level,
369
+ transport: pretty ? {
370
+ target: "pino-pretty",
371
+ options: {
372
+ colorize: true,
373
+ translateTime: "SYS:standard",
374
+ ignore: "pid,hostname"
375
+ }
376
+ } : void 0
377
+ },
378
+ pino.destination({ dest: 2, sync: false })
379
+ // Write to stderr (fd 2) for MCP protocol compatibility
380
+ );
350
381
  return logger;
351
382
  }
352
383
  function getLogger() {
@@ -1607,18 +1638,370 @@ var SchemaCache = class {
1607
1638
  }
1608
1639
  };
1609
1640
 
1641
+ // src/query-optimizer.ts
1642
+ var QueryOptimizer = class {
1643
+ slowQueryThresholdMs;
1644
+ slowQueryAlerts = /* @__PURE__ */ new Map();
1645
+ constructor(options = {
1646
+ slowQueryThresholdMs: 1e3,
1647
+ maxHistoryForAnalysis: 1e3,
1648
+ enableAutoAnalysis: true
1649
+ }) {
1650
+ this.slowQueryThresholdMs = options.slowQueryThresholdMs;
1651
+ }
1652
+ /**
1653
+ * Analyze query complexity and extract performance metrics
1654
+ */
1655
+ analyzeQueryComplexity(sql) {
1656
+ const complexity = {
1657
+ selectColumns: this.countSelectColumns(sql),
1658
+ whereConditions: this.countWhereConditions(sql),
1659
+ joinCount: this.countJoins(sql),
1660
+ subqueryCount: this.countSubqueries(sql),
1661
+ hasAggregations: /\b(COUNT|SUM|AVG|MIN|MAX)\s*\(/i.test(sql),
1662
+ hasDistinct: /\bDISTINCT\b/i.test(sql),
1663
+ hasOrderBy: /\bORDER\s+BY\b/i.test(sql),
1664
+ hasGroupBy: /\bGROUP\s+BY\b/i.test(sql),
1665
+ estimatedComplexity: "simple"
1666
+ };
1667
+ let score = 0;
1668
+ score += complexity.selectColumns * 0.5;
1669
+ score += complexity.whereConditions * 1;
1670
+ score += complexity.joinCount * 2;
1671
+ score += complexity.subqueryCount * 3;
1672
+ score += complexity.hasAggregations ? 2 : 0;
1673
+ score += complexity.hasDistinct ? 1 : 0;
1674
+ score += complexity.hasOrderBy ? 1 : 0;
1675
+ score += complexity.hasGroupBy ? 1 : 0;
1676
+ if (score <= 3) complexity.estimatedComplexity = "simple";
1677
+ else if (score <= 7) complexity.estimatedComplexity = "medium";
1678
+ else if (score <= 12) complexity.estimatedComplexity = "complex";
1679
+ else complexity.estimatedComplexity = "very_complex";
1680
+ return complexity;
1681
+ }
1682
+ /**
1683
+ * Generate index recommendations based on query history and schema
1684
+ */
1685
+ generateIndexRecommendations(queryHistory, schema) {
1686
+ const recommendations = [];
1687
+ const columnUsage = /* @__PURE__ */ new Map();
1688
+ for (const entry of queryHistory) {
1689
+ if (entry.error) continue;
1690
+ const tables = entry.tables;
1691
+ const whereMatch = entry.sql.match(/WHERE\s+(.+?)(?:\s+(GROUP|ORDER|LIMIT|$))/i);
1692
+ if (whereMatch) {
1693
+ const whereClause = whereMatch[1];
1694
+ const columns = this.extractColumnsFromCondition(whereClause, tables, schema);
1695
+ for (const col of columns) {
1696
+ const key = `${col.table}.${col.column}`;
1697
+ const existing = columnUsage.get(key) || { table: col.table, usage: 0, inWhere: false, inJoin: false };
1698
+ existing.usage++;
1699
+ existing.inWhere = true;
1700
+ columnUsage.set(key, existing);
1701
+ }
1702
+ }
1703
+ const joinMatches = entry.sql.match(/JOIN\s+\w+\s+ON\s+(.+?)(?:\s+(WHERE|GROUP|ORDER|LIMIT|$))/gi);
1704
+ if (joinMatches) {
1705
+ for (const joinMatch of joinMatches) {
1706
+ const joinClause = joinMatch.replace(/JOIN\s+\w+\s+ON\s+/i, "");
1707
+ const columns = this.extractColumnsFromCondition(joinClause, tables, schema);
1708
+ for (const col of columns) {
1709
+ const key = `${col.table}.${col.column}`;
1710
+ const existing = columnUsage.get(key) || { table: col.table, usage: 0, inWhere: false, inJoin: false };
1711
+ existing.usage++;
1712
+ existing.inJoin = true;
1713
+ columnUsage.set(key, existing);
1714
+ }
1715
+ }
1716
+ }
1717
+ }
1718
+ for (const [key, usage] of columnUsage) {
1719
+ if (usage.usage < 3) continue;
1720
+ const [tableName, columnName] = key.split(".");
1721
+ const table = this.findTableInSchema(schema, tableName);
1722
+ if (!table) continue;
1723
+ const existingIndex = table.indexes.find(
1724
+ (idx) => idx.columns.includes(columnName) && !idx.isPrimary
1725
+ );
1726
+ if (existingIndex) continue;
1727
+ const recommendation = {
1728
+ table: tableName,
1729
+ columns: [columnName],
1730
+ type: "single",
1731
+ reason: `Column ${columnName} is frequently used in ${usage.inWhere ? "WHERE" : ""}${usage.inWhere && usage.inJoin ? " and " : ""}${usage.inJoin ? "JOIN" : ""} conditions`,
1732
+ impact: usage.usage > 10 ? "high" : usage.usage > 5 ? "medium" : "low"
1733
+ };
1734
+ recommendations.push(recommendation);
1735
+ }
1736
+ return recommendations.sort((a, b) => {
1737
+ const impactOrder = { high: 3, medium: 2, low: 1 };
1738
+ return impactOrder[b.impact] - impactOrder[a.impact];
1739
+ });
1740
+ }
1741
+ /**
1742
+ * Profile query performance using EXPLAIN plan
1743
+ */
1744
+ async profileQueryPerformance(_dbId, sql, explainResult, executionTimeMs, rowCount) {
1745
+ const bottlenecks = [];
1746
+ const recommendations = [];
1747
+ if (explainResult.plan) {
1748
+ bottlenecks.push(...this.analyzeExplainPlan(explainResult.plan));
1749
+ }
1750
+ if (executionTimeMs > this.slowQueryThresholdMs) {
1751
+ bottlenecks.push({
1752
+ type: "table_scan",
1753
+ severity: executionTimeMs > this.slowQueryThresholdMs * 5 ? "critical" : "high",
1754
+ description: `Query execution time (${executionTimeMs}ms) exceeds threshold (${this.slowQueryThresholdMs}ms)`,
1755
+ estimatedCost: executionTimeMs
1756
+ });
1757
+ }
1758
+ for (const bottleneck of bottlenecks) {
1759
+ switch (bottleneck.type) {
1760
+ case "table_scan":
1761
+ recommendations.push({
1762
+ type: "add_index",
1763
+ description: `Consider adding an index on ${bottleneck.table || "frequently queried columns"}`,
1764
+ impact: "high",
1765
+ effort: "medium"
1766
+ });
1767
+ break;
1768
+ case "join":
1769
+ recommendations.push({
1770
+ type: "optimize_join",
1771
+ description: "Review JOIN conditions and ensure proper indexing on join columns",
1772
+ impact: "high",
1773
+ effort: "medium"
1774
+ });
1775
+ break;
1776
+ case "sort":
1777
+ recommendations.push({
1778
+ type: "add_index",
1779
+ description: "Consider adding an index to avoid sorting operations",
1780
+ impact: "medium",
1781
+ effort: "medium"
1782
+ });
1783
+ break;
1784
+ }
1785
+ }
1786
+ let score = 100;
1787
+ for (const bottleneck of bottlenecks) {
1788
+ const severityPenalty = { critical: 30, high: 20, medium: 10, low: 5 };
1789
+ score -= severityPenalty[bottleneck.severity];
1790
+ }
1791
+ score = Math.max(0, Math.min(100, score));
1792
+ return {
1793
+ queryId: this.generateQueryId(sql),
1794
+ sql,
1795
+ executionTimeMs,
1796
+ rowCount,
1797
+ bottlenecks,
1798
+ recommendations,
1799
+ overallScore: score
1800
+ };
1801
+ }
1802
+ /**
1803
+ * Detect and alert on slow queries
1804
+ */
1805
+ detectSlowQueries(queryHistory, dbId) {
1806
+ for (const entry of queryHistory) {
1807
+ if (entry.executionTimeMs > this.slowQueryThresholdMs) {
1808
+ const queryId = this.generateQueryId(entry.sql);
1809
+ const existingAlerts = this.slowQueryAlerts.get(dbId) || [];
1810
+ const existingAlert = existingAlerts.find((a) => a.queryId === queryId);
1811
+ if (existingAlert) {
1812
+ existingAlert.frequency++;
1813
+ existingAlert.timestamp = entry.timestamp;
1814
+ if (entry.executionTimeMs > existingAlert.executionTimeMs) {
1815
+ existingAlert.executionTimeMs = entry.executionTimeMs;
1816
+ }
1817
+ } else {
1818
+ const alert = {
1819
+ dbId,
1820
+ queryId,
1821
+ sql: entry.sql,
1822
+ executionTimeMs: entry.executionTimeMs,
1823
+ thresholdMs: this.slowQueryThresholdMs,
1824
+ timestamp: entry.timestamp,
1825
+ frequency: 1,
1826
+ recommendations: []
1827
+ };
1828
+ existingAlerts.push(alert);
1829
+ }
1830
+ this.slowQueryAlerts.set(dbId, existingAlerts);
1831
+ }
1832
+ }
1833
+ return this.slowQueryAlerts.get(dbId) || [];
1834
+ }
1835
+ /**
1836
+ * Suggest optimized versions of queries
1837
+ */
1838
+ suggestQueryRewrites(sql, schema) {
1839
+ const optimizations = [];
1840
+ let optimizedQuery = sql;
1841
+ let performanceGain = 0;
1842
+ if (/\bDISTINCT\b/i.test(sql) && this.canRemoveDistinct(sql, schema)) {
1843
+ optimizedQuery = optimizedQuery.replace(/\bDISTINCT\b/i, "");
1844
+ optimizations.push("Removed unnecessary DISTINCT clause");
1845
+ performanceGain += 15;
1846
+ }
1847
+ if (!/\bLIMIT\b/i.test(sql) && !/\bCOUNT\b/i.test(sql)) {
1848
+ optimizedQuery += " LIMIT 1000";
1849
+ optimizations.push("Added LIMIT clause to prevent large result sets");
1850
+ performanceGain += 10;
1851
+ }
1852
+ if (/\bSELECT\s+\*\s+FROM\b/i.test(sql)) {
1853
+ optimizations.push("Consider selecting only required columns instead of SELECT *");
1854
+ performanceGain += 5;
1855
+ }
1856
+ const tables = extractTableNames(sql);
1857
+ for (const table of tables) {
1858
+ const tableMeta = this.findTableInSchema(schema, table);
1859
+ if (tableMeta && !/\bWHERE\b/i.test(sql)) {
1860
+ optimizations.push(`Consider adding WHERE clause for table ${table} to reduce data scanned`);
1861
+ performanceGain += 20;
1862
+ }
1863
+ }
1864
+ return {
1865
+ originalQuery: sql,
1866
+ optimizedQuery,
1867
+ improvements: optimizations,
1868
+ performanceGain: Math.min(100, performanceGain),
1869
+ confidence: optimizations.length > 2 ? "high" : optimizations.length > 0 ? "medium" : "low"
1870
+ };
1871
+ }
1872
+ /**
1873
+ * Get performance analytics across all queries
1874
+ */
1875
+ getPerformanceAnalytics(queryHistory) {
1876
+ const analytics = {
1877
+ totalQueries: queryHistory.length,
1878
+ slowQueries: queryHistory.filter((q) => q.executionTimeMs > this.slowQueryThresholdMs).length,
1879
+ avgExecutionTime: 0,
1880
+ p95ExecutionTime: 0,
1881
+ errorRate: 0,
1882
+ mostFrequentTables: [],
1883
+ performanceTrend: "stable"
1884
+ };
1885
+ if (queryHistory.length === 0) return analytics;
1886
+ const executionTimes = queryHistory.map((q) => q.executionTimeMs).sort((a, b) => a - b);
1887
+ analytics.avgExecutionTime = executionTimes.reduce((a, b) => a + b, 0) / executionTimes.length;
1888
+ analytics.p95ExecutionTime = executionTimes[Math.floor(executionTimes.length * 0.95)];
1889
+ analytics.errorRate = queryHistory.filter((q) => q.error).length / queryHistory.length * 100;
1890
+ const tableUsage = /* @__PURE__ */ new Map();
1891
+ for (const query of queryHistory) {
1892
+ for (const table of query.tables) {
1893
+ tableUsage.set(table, (tableUsage.get(table) || 0) + 1);
1894
+ }
1895
+ }
1896
+ analytics.mostFrequentTables = Array.from(tableUsage.entries()).map(([table, count]) => ({ table, count })).sort((a, b) => b.count - a.count).slice(0, 10);
1897
+ const midpoint = Math.floor(queryHistory.length / 2);
1898
+ const recentAvg = executionTimes.slice(midpoint).reduce((a, b) => a + b, 0) / (executionTimes.length - midpoint);
1899
+ const olderAvg = executionTimes.slice(0, midpoint).reduce((a, b) => a + b, 0) / midpoint;
1900
+ if (recentAvg < olderAvg * 0.8) analytics.performanceTrend = "improving";
1901
+ else if (recentAvg > olderAvg * 1.2) analytics.performanceTrend = "degrading";
1902
+ return analytics;
1903
+ }
1904
+ // Helper methods
1905
+ countSelectColumns(sql) {
1906
+ const selectMatch = sql.match(/SELECT\s+(.+?)\s+FROM/i);
1907
+ if (!selectMatch) return 0;
1908
+ const selectClause = selectMatch[1];
1909
+ if (selectClause.includes("*")) return 1;
1910
+ return (selectClause.match(/,/g) || []).length + 1;
1911
+ }
1912
+ countWhereConditions(sql) {
1913
+ const whereMatch = sql.match(/WHERE\s+(.+?)(?:\s+(GROUP|ORDER|LIMIT|$))/i);
1914
+ if (!whereMatch) return 0;
1915
+ const whereClause = whereMatch[1];
1916
+ return (whereClause.match(/\bAND\b/gi) || []).length + 1;
1917
+ }
1918
+ countJoins(sql) {
1919
+ return (sql.match(/\bJOIN\b/gi) || []).length;
1920
+ }
1921
+ countSubqueries(sql) {
1922
+ return (sql.match(/\(\s*SELECT/gi) || []).length;
1923
+ }
1924
+ extractColumnsFromCondition(condition, tables, schema) {
1925
+ const columns = [];
1926
+ const columnMatches = condition.match(/\b(\w+\.)?(\w+)\b/g) || [];
1927
+ for (const match of columnMatches) {
1928
+ if (match.includes(".")) {
1929
+ const [table, column] = match.split(".");
1930
+ if (tables.includes(table)) {
1931
+ columns.push({ table, column });
1932
+ }
1933
+ } else {
1934
+ const column = match;
1935
+ for (const table of tables) {
1936
+ const tableMeta = this.findTableInSchema(schema, table);
1937
+ if (tableMeta?.columns.some((col) => col.name === column)) {
1938
+ columns.push({ table, column });
1939
+ break;
1940
+ }
1941
+ }
1942
+ }
1943
+ }
1944
+ return columns;
1945
+ }
1946
+ analyzeExplainPlan(plan) {
1947
+ const bottlenecks = [];
1948
+ const planStr = JSON.stringify(plan).toLowerCase();
1949
+ if (planStr.includes("table scan") || planStr.includes("seq scan")) {
1950
+ bottlenecks.push({
1951
+ type: "table_scan",
1952
+ severity: "high",
1953
+ description: "Full table scan detected - consider adding indexes",
1954
+ estimatedCost: 100
1955
+ });
1956
+ }
1957
+ if (planStr.includes("sort") && !planStr.includes("index")) {
1958
+ bottlenecks.push({
1959
+ type: "sort",
1960
+ severity: "medium",
1961
+ description: "In-memory sort operation - consider indexed ORDER BY",
1962
+ estimatedCost: 50
1963
+ });
1964
+ }
1965
+ return bottlenecks;
1966
+ }
1967
+ canRemoveDistinct(sql, _schema) {
1968
+ return /\bGROUP\s+BY\b/i.test(sql) || /\bPRIMARY\s+KEY\b/i.test(sql);
1969
+ }
1970
+ findTableInSchema(schema, tableName) {
1971
+ for (const schemaMeta of schema.schemas) {
1972
+ const table = schemaMeta.tables.find((t) => t.name === tableName);
1973
+ if (table) return table;
1974
+ }
1975
+ return null;
1976
+ }
1977
+ generateQueryId(sql) {
1978
+ let hash = 0;
1979
+ for (let i = 0; i < sql.length; i++) {
1980
+ const char = sql.charCodeAt(i);
1981
+ hash = (hash << 5) - hash + char;
1982
+ hash = hash & hash;
1983
+ }
1984
+ return Math.abs(hash).toString(16);
1985
+ }
1986
+ };
1987
+
1610
1988
  // src/query-tracker.ts
1611
1989
  var QueryTracker = class {
1612
1990
  history = /* @__PURE__ */ new Map();
1613
1991
  maxHistoryPerDb = 100;
1614
- track(dbId, sql, executionTimeMs, rowCount, error) {
1992
+ optimizer = new QueryOptimizer();
1993
+ track(dbId, sql, executionTimeMs, rowCount, error, explainPlan) {
1994
+ const complexity = this.optimizer.analyzeQueryComplexity(sql);
1615
1995
  const entry = {
1616
1996
  timestamp: /* @__PURE__ */ new Date(),
1617
1997
  sql,
1618
1998
  tables: extractTableNames(sql),
1619
1999
  executionTimeMs,
1620
2000
  rowCount,
1621
- error
2001
+ error,
2002
+ explainPlan,
2003
+ queryComplexity: complexity,
2004
+ performanceScore: this.calculatePerformanceScore(executionTimeMs, complexity)
1622
2005
  };
1623
2006
  if (!this.history.has(dbId)) {
1624
2007
  this.history.set(dbId, []);
@@ -1642,24 +2025,59 @@ var QueryTracker = class {
1642
2025
  totalQueries: dbHistory.length,
1643
2026
  avgExecutionTime: 0,
1644
2027
  errorCount: 0,
1645
- tableUsage: {}
2028
+ tableUsage: {},
2029
+ performanceMetrics: {
2030
+ avgScore: 0,
2031
+ slowQueryCount: 0,
2032
+ complexityDistribution: {}
2033
+ }
1646
2034
  };
1647
2035
  if (dbHistory.length === 0) {
1648
2036
  return stats;
1649
2037
  }
1650
2038
  let totalTime = 0;
2039
+ let totalScore = 0;
1651
2040
  for (const entry of dbHistory) {
1652
2041
  totalTime += entry.executionTimeMs;
1653
2042
  if (entry.error) {
1654
2043
  stats.errorCount++;
1655
2044
  }
2045
+ if (entry.performanceScore !== void 0) {
2046
+ totalScore += entry.performanceScore;
2047
+ }
2048
+ if (entry.executionTimeMs > 1e3) {
2049
+ stats.performanceMetrics.slowQueryCount++;
2050
+ }
2051
+ if (entry.queryComplexity) {
2052
+ const complexity = entry.queryComplexity.estimatedComplexity;
2053
+ stats.performanceMetrics.complexityDistribution[complexity] = (stats.performanceMetrics.complexityDistribution[complexity] || 0) + 1;
2054
+ }
1656
2055
  for (const table of entry.tables) {
1657
2056
  stats.tableUsage[table] = (stats.tableUsage[table] || 0) + 1;
1658
2057
  }
1659
2058
  }
1660
2059
  stats.avgExecutionTime = totalTime / dbHistory.length;
2060
+ stats.performanceMetrics.avgScore = totalScore / dbHistory.length;
1661
2061
  return stats;
1662
2062
  }
2063
+ getPerformanceAnalytics(dbId) {
2064
+ const history = this.getHistory(dbId);
2065
+ return this.optimizer.getPerformanceAnalytics(history);
2066
+ }
2067
+ getIndexRecommendations(dbId, schema) {
2068
+ const history = this.getHistory(dbId);
2069
+ return this.optimizer.generateIndexRecommendations(history, schema);
2070
+ }
2071
+ getSlowQueryAlerts(dbId) {
2072
+ const history = this.getHistory(dbId);
2073
+ return this.optimizer.detectSlowQueries(history, dbId);
2074
+ }
2075
+ suggestQueryRewrite(sql, schema) {
2076
+ return this.optimizer.suggestQueryRewrites(sql, schema);
2077
+ }
2078
+ async profileQueryPerformance(dbId, sql, explainResult, executionTimeMs, rowCount) {
2079
+ return this.optimizer.profileQueryPerformance(dbId, sql, explainResult, executionTimeMs, rowCount);
2080
+ }
1663
2081
  clear(dbId) {
1664
2082
  if (dbId) {
1665
2083
  this.history.delete(dbId);
@@ -1667,6 +2085,20 @@ var QueryTracker = class {
1667
2085
  this.history.clear();
1668
2086
  }
1669
2087
  }
2088
+ calculatePerformanceScore(executionTimeMs, complexity) {
2089
+ let score = 100;
2090
+ if (executionTimeMs > 5e3) score -= 40;
2091
+ else if (executionTimeMs > 1e3) score -= 20;
2092
+ else if (executionTimeMs > 100) score -= 10;
2093
+ const complexityPenalty = {
2094
+ simple: 0,
2095
+ medium: 5,
2096
+ complex: 15,
2097
+ very_complex: 25
2098
+ };
2099
+ score -= complexityPenalty[complexity.estimatedComplexity];
2100
+ return Math.max(0, Math.min(100, score));
2101
+ }
1670
2102
  };
1671
2103
 
1672
2104
  // src/database-manager.ts
@@ -1794,7 +2226,15 @@ var DatabaseManager = class {
1794
2226
  const adapter = this.getAdapter(dbId);
1795
2227
  try {
1796
2228
  const result = await adapter.query(sql, params, timeoutMs);
1797
- this.queryTracker.track(dbId, sql, result.executionTimeMs, result.rowCount);
2229
+ let explainPlan;
2230
+ if (!isWriteOperation(sql)) {
2231
+ try {
2232
+ explainPlan = await adapter.explain(sql, params);
2233
+ } catch (_explainError) {
2234
+ this.logger.debug({ dbId, sql }, "EXPLAIN failed, continuing without performance analysis");
2235
+ }
2236
+ }
2237
+ this.queryTracker.track(dbId, sql, result.executionTimeMs, result.rowCount, void 0, explainPlan);
1798
2238
  return result;
1799
2239
  } catch (error) {
1800
2240
  this.queryTracker.track(dbId, sql, 0, 0, error.message);
@@ -1823,6 +2263,29 @@ var DatabaseManager = class {
1823
2263
  getQueryHistory(dbId, limit) {
1824
2264
  return this.queryTracker.getHistory(dbId, limit);
1825
2265
  }
2266
+ getPerformanceAnalytics(dbId) {
2267
+ return this.queryTracker.getPerformanceAnalytics(dbId);
2268
+ }
2269
+ async getIndexRecommendations(dbId) {
2270
+ const schema = await this.getSchema(dbId);
2271
+ return this.queryTracker.getIndexRecommendations(dbId, schema);
2272
+ }
2273
+ getSlowQueryAlerts(dbId) {
2274
+ return this.queryTracker.getSlowQueryAlerts(dbId);
2275
+ }
2276
+ async suggestQueryRewrite(dbId, sql) {
2277
+ const schema = await this.getSchema(dbId);
2278
+ return this.queryTracker.suggestQueryRewrite(sql, schema);
2279
+ }
2280
+ async profileQueryPerformance(dbId, sql, params = []) {
2281
+ await this.ensureConnected(dbId);
2282
+ const adapter = this.getAdapter(dbId);
2283
+ const startTime = Date.now();
2284
+ const result = await adapter.query(sql, params);
2285
+ const executionTimeMs = Date.now() - startTime;
2286
+ const explainResult = await adapter.explain(sql, params);
2287
+ return this.queryTracker.profileQueryPerformance(dbId, sql, explainResult, executionTimeMs, result.rowCount);
2288
+ }
1826
2289
  };
1827
2290
 
1828
2291
  // src/mcp-server.ts
@@ -1832,7 +2295,8 @@ import {
1832
2295
  CallToolRequestSchema,
1833
2296
  ListToolsRequestSchema,
1834
2297
  ListResourcesRequestSchema,
1835
- ReadResourceRequestSchema
2298
+ ReadResourceRequestSchema,
2299
+ InitializeRequestSchema
1836
2300
  } from "@modelcontextprotocol/sdk/types.js";
1837
2301
  var MCPServer = class {
1838
2302
  constructor(_dbManager, _config) {
@@ -1855,6 +2319,21 @@ var MCPServer = class {
1855
2319
  server;
1856
2320
  logger = getLogger();
1857
2321
  setupHandlers() {
2322
+ this.server.setRequestHandler(InitializeRequestSchema, async (request) => {
2323
+ const { protocolVersion } = request.params;
2324
+ this.logger.info({ protocolVersion }, "MCP server initializing");
2325
+ return {
2326
+ protocolVersion,
2327
+ capabilities: {
2328
+ tools: {},
2329
+ resources: {}
2330
+ },
2331
+ serverInfo: {
2332
+ name: "mcp-database-server",
2333
+ version: "1.0.0"
2334
+ }
2335
+ };
2336
+ });
1858
2337
  this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
1859
2338
  tools: [
1860
2339
  {
@@ -2034,6 +2513,89 @@ var MCPServer = class {
2034
2513
  }
2035
2514
  }
2036
2515
  }
2516
+ },
2517
+ {
2518
+ name: "analyze_performance",
2519
+ description: "Get detailed performance analytics for a database",
2520
+ inputSchema: {
2521
+ type: "object",
2522
+ properties: {
2523
+ dbId: {
2524
+ type: "string",
2525
+ description: "Database ID to analyze"
2526
+ }
2527
+ },
2528
+ required: ["dbId"]
2529
+ }
2530
+ },
2531
+ {
2532
+ name: "suggest_indexes",
2533
+ description: "Analyze query patterns and suggest optimal indexes",
2534
+ inputSchema: {
2535
+ type: "object",
2536
+ properties: {
2537
+ dbId: {
2538
+ type: "string",
2539
+ description: "Database ID to analyze"
2540
+ }
2541
+ },
2542
+ required: ["dbId"]
2543
+ }
2544
+ },
2545
+ {
2546
+ name: "detect_slow_queries",
2547
+ description: "Identify and alert on slow-running queries",
2548
+ inputSchema: {
2549
+ type: "object",
2550
+ properties: {
2551
+ dbId: {
2552
+ type: "string",
2553
+ description: "Database ID to analyze"
2554
+ }
2555
+ },
2556
+ required: ["dbId"]
2557
+ }
2558
+ },
2559
+ {
2560
+ name: "rewrite_query",
2561
+ description: "Suggest optimized versions of SQL queries",
2562
+ inputSchema: {
2563
+ type: "object",
2564
+ properties: {
2565
+ dbId: {
2566
+ type: "string",
2567
+ description: "Database ID"
2568
+ },
2569
+ sql: {
2570
+ type: "string",
2571
+ description: "SQL query to optimize"
2572
+ }
2573
+ },
2574
+ required: ["dbId", "sql"]
2575
+ }
2576
+ },
2577
+ {
2578
+ name: "profile_query",
2579
+ description: "Profile query performance with detailed analysis",
2580
+ inputSchema: {
2581
+ type: "object",
2582
+ properties: {
2583
+ dbId: {
2584
+ type: "string",
2585
+ description: "Database ID"
2586
+ },
2587
+ sql: {
2588
+ type: "string",
2589
+ description: "SQL query to profile"
2590
+ },
2591
+ params: {
2592
+ type: "array",
2593
+ description: "Query parameters",
2594
+ items: {}
2595
+ }
2596
+ },
2597
+ required: ["dbId", "sql"]
2598
+ }
2037
2599
  }
2038
2600
  ]
2039
2601
  }));
@@ -2059,6 +2621,16 @@ var MCPServer = class {
2059
2621
  return await this.handleCacheStatus(args);
2060
2622
  case "health_check":
2061
2623
  return await this.handleHealthCheck(args);
2624
+ case "analyze_performance":
2625
+ return await this.handleAnalyzePerformance(args);
2626
+ case "suggest_indexes":
2627
+ return await this.handleSuggestIndexes(args);
2628
+ case "detect_slow_queries":
2629
+ return await this.handleDetectSlowQueries(args);
2630
+ case "rewrite_query":
2631
+ return await this.handleRewriteQuery(args);
2632
+ case "profile_query":
2633
+ return await this.handleProfileQuery(args);
2062
2634
  default:
2063
2635
  throw new Error(`Unknown tool: ${name}`);
2064
2636
  }
@@ -2294,9 +2866,67 @@ var MCPServer = class {
2294
2866
  ]
2295
2867
  };
2296
2868
  }
2869
+ async handleAnalyzePerformance(args) {
2870
+ const analytics = this._dbManager.getPerformanceAnalytics(args.dbId);
2871
+ return {
2872
+ content: [
2873
+ {
2874
+ type: "text",
2875
+ text: JSON.stringify(analytics, null, 2)
2876
+ }
2877
+ ]
2878
+ };
2879
+ }
2880
+ async handleSuggestIndexes(args) {
2881
+ const recommendations = await this._dbManager.getIndexRecommendations(args.dbId);
2882
+ return {
2883
+ content: [
2884
+ {
2885
+ type: "text",
2886
+ text: JSON.stringify(recommendations, null, 2)
2887
+ }
2888
+ ]
2889
+ };
2890
+ }
2891
+ async handleDetectSlowQueries(args) {
2892
+ const alerts = this._dbManager.getSlowQueryAlerts(args.dbId);
2893
+ return {
2894
+ content: [
2895
+ {
2896
+ type: "text",
2897
+ text: JSON.stringify(alerts, null, 2)
2898
+ }
2899
+ ]
2900
+ };
2901
+ }
2902
+ async handleRewriteQuery(args) {
2903
+ const suggestion = await this._dbManager.suggestQueryRewrite(args.dbId, args.sql);
2904
+ return {
2905
+ content: [
2906
+ {
2907
+ type: "text",
2908
+ text: JSON.stringify(suggestion, null, 2)
2909
+ }
2910
+ ]
2911
+ };
2912
+ }
2913
+ async handleProfileQuery(args) {
2914
+ const profile = await this._dbManager.profileQueryPerformance(args.dbId, args.sql, args.params);
2915
+ return {
2916
+ content: [
2917
+ {
2918
+ type: "text",
2919
+ text: JSON.stringify(profile, null, 2)
2920
+ }
2921
+ ]
2922
+ };
2923
+ }
2297
2924
  async start() {
2925
+ console.error("Starting MCP server...");
2298
2926
  const transport = new StdioServerTransport();
2927
+ console.error("Created transport, connecting...");
2299
2928
  await this.server.connect(transport);
2929
+ console.error("MCP server connected and started");
2300
2930
  this.logger.info("MCP server started");
2301
2931
  }
2302
2932
  };
@@ -2315,8 +2945,7 @@ async function main() {
2315
2945
  options: {
2316
2946
  config: {
2317
2947
  type: "string",
2318
- short: "c",
2319
- default: "./.mcp-database-server.config"
2948
+ short: "c"
2320
2949
  },
2321
2950
  help: {
2322
2951
  type: "boolean",
@@ -2340,7 +2969,7 @@ Usage:
2340
2969
  mcp-database-server [options]
2341
2970
 
2342
2971
  Options:
2343
- -c, --config <path> Path to configuration file (default: ./.mcp-database-server.config)
2972
+ -c, --config <path> Path to configuration file (if not specified, searches for .mcp-database-server.config from project root upwards)
2344
2973
  -h, --help Show this help message
2345
2974
  -v, --version Show version number
2346
2975
 
@@ -2358,13 +2987,19 @@ Examples:
2358
2987
  `);
2359
2988
  process.exit(0);
2360
2989
  }
2361
- let configPath = values.config;
2362
- if (configPath === "./.mcp-database-server.config") {
2990
+ let configPath;
2991
+ if (values.config) {
2992
+ configPath = values.config;
2993
+ if (!existsSync2(configPath)) {
2994
+ console.error(`Error: Specified config file ${configPath} not found`);
2995
+ process.exit(1);
2996
+ }
2997
+ } else {
2363
2998
  const foundPath = findConfigFile(".mcp-database-server.config");
2364
2999
  if (foundPath) {
2365
3000
  configPath = foundPath;
2366
3001
  } else {
2367
- console.error("Error: Config file .mcp-database-server.config not found");
3002
+ console.error("Error: No config file specified, and .mcp-database-server.config not found");
2368
3003
  console.error("Searched in current directory and all parent directories");
2369
3004
  console.error("\nTo create a config file:");
2370
3005
  console.error(" cp mcp-database-server.config.example .mcp-database-server.config");