mcp-db-analyzer 0.2.7 → 0.2.9

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.
@@ -172,7 +172,8 @@ async function analyzeMysqlConnections() {
172
172
  lines.push("| ID | User | Duration (s) | State | Query |");
173
173
  lines.push("|-----|------|-------------|-------|-------|");
174
174
  for (const row of longQueries.rows) {
175
- lines.push(`| ${row.id} | ${row.user} | ${row.time} | ${row.state} | ${row.info} |`);
175
+ const info = row.info ? row.info.replace(/\|/g, "\\|") : "-";
176
+ lines.push(`| ${row.id} | ${row.user} | ${row.time} | ${row.state} | ${info} |`);
176
177
  }
177
178
  lines.push("");
178
179
  }
@@ -56,7 +56,10 @@ async function listTablesSqlite() {
56
56
  lines.push("|-------|-------------|------------|");
57
57
  for (const row of result.rows) {
58
58
  // SQLite doesn't have built-in row count or size — use count
59
- const countResult = await query(`SELECT count(*) as cnt FROM "${row.name}"`);
59
+ // Escape embedded double-quote characters so table names like `weird"table` don't
60
+ // break the identifier quoting (same convention used in inspectTableSqlite).
61
+ const escapedName = row.name.replace(/"/g, '""');
62
+ const countResult = await query(`SELECT count(*) as cnt FROM "${escapedName}"`);
60
63
  const cnt = countResult.rows[0]?.cnt ?? 0;
61
64
  lines.push(`| ${row.name} | ${cnt} | - |`);
62
65
  }
@@ -35,15 +35,29 @@ export async function analyzeVacuum(schema = "public") {
35
35
  if (stats.rows.length === 0) {
36
36
  return `No user tables found in schema '${schema}'.`;
37
37
  }
38
- // Get autovacuum settings
39
- const settings = await query(`
40
- SELECT name, setting
41
- FROM pg_settings
42
- WHERE name LIKE 'autovacuum%'
43
- ORDER BY name
44
- `);
45
- const findings = analyzeFindings(stats.rows, settings.rows);
46
- return formatVacuumReport(schema, stats.rows, settings.rows, findings);
38
+ // Get autovacuum settings. pg_settings requires superuser or pg_read_all_settings
39
+ // in some PostgreSQL configurations — don't let a permission error discard the
40
+ // table stats we already fetched.
41
+ let settingsRows = [];
42
+ let settingsUnavailable = false;
43
+ try {
44
+ const settings = await query(`
45
+ SELECT name, setting
46
+ FROM pg_settings
47
+ WHERE name LIKE 'autovacuum%'
48
+ ORDER BY name
49
+ `);
50
+ settingsRows = settings.rows;
51
+ }
52
+ catch {
53
+ settingsUnavailable = true;
54
+ }
55
+ const findings = analyzeFindings(stats.rows, settingsRows);
56
+ const report = formatVacuumReport(schema, stats.rows, settingsRows, findings);
57
+ if (settingsUnavailable) {
58
+ return report + "\n\n*Note: Autovacuum configuration could not be read — the database user may need pg_read_all_settings or superuser privileges.*";
59
+ }
60
+ return report;
47
61
  }
48
62
  export function analyzeFindings(tables, settings) {
49
63
  const findings = [];
@@ -11,6 +11,9 @@ export function createSqliteAdapter() {
11
11
  }
12
12
  db = new Database(dbPath, { readonly: true });
13
13
  db.pragma("journal_mode = WAL");
14
+ // Enforce read-only at the SQL engine level, not just at the file level.
15
+ // This prevents any write attempt from succeeding even if the OS permits it.
16
+ db.pragma("query_only = ON");
14
17
  }
15
18
  return db;
16
19
  }
package/build/errors.js CHANGED
@@ -4,7 +4,11 @@
4
4
  */
5
5
  export function formatToolError(context, err) {
6
6
  const msg = err instanceof Error ? err.message : String(err);
7
- const sanitized = msg.replace(/\/\/[^@]+@/g, "//****:****@");
7
+ // Sanitize URL-style credentials (postgresql://user:pass@host) and
8
+ // key-value style passwords (password=secret) used by libpq and JDBC.
9
+ const sanitized = msg
10
+ .replace(/\/\/[^@]+@/g, "//****:****@")
11
+ .replace(/\bpassword\s*=\s*\S+/gi, "password=****");
8
12
  const isConnectionError = /ECONNREFUSED|ENOTFOUND|ETIMEDOUT|EHOSTUNREACH|getaddrinfo|connect ECONNRESET|password authentication failed|Access denied|no pg_hba\.conf|connection refused|Connection lost|SQLITE_CANTOPEN/i.test(msg);
9
13
  if (isConnectionError) {
10
14
  return `Error ${context}: ${sanitized}\n\nThis looks like a database connection issue. Check your configuration:\n- Set DATABASE_URL environment variable with a valid connection string\n- Or use driver-specific variables (PGHOST, MYSQL_HOST, SQLITE_PATH)\n- Ensure the database server is running and accessible`;
package/build/index.js CHANGED
@@ -220,7 +220,7 @@ server.tool("suggest_missing_indexes", "Find tables with high sequential scan co
220
220
  }
221
221
  });
222
222
  // --- Tool: analyze_slow_queries ---
223
- server.tool("analyze_slow_queries", "Find the slowest queries using pg_stat_statements (PostgreSQL) or performance_schema (MySQL). Shows execution times, call counts, and optimization recommendations.", {
223
+ server.tool("analyze_slow_queries", "Find the slowest queries using pg_stat_statements (PostgreSQL) or performance_schema (MySQL). Shows execution times, call counts, and optimization recommendations. PostgreSQL requires the pg_stat_statements extension to be installed and listed in shared_preload_libraries — the tool returns setup instructions if the extension is missing. Not available for SQLite.", {
224
224
  schema: z
225
225
  .string()
226
226
  .default("public")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-db-analyzer",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "MCP server for PostgreSQL, MySQL, and SQLite schema analysis, index optimization, and query plan inspection",
5
5
  "mcpName": "io.github.dmitriusan/mcp-db-analyzer",
6
6
  "author": "Dmytro Lisnichenko",
@@ -44,7 +44,7 @@
44
44
  ],
45
45
  "license": "MIT",
46
46
  "engines": {
47
- "node": ">=18.0.0"
47
+ "node": ">=20.0.0"
48
48
  },
49
49
  "repository": {
50
50
  "type": "git",
@@ -59,14 +59,14 @@
59
59
  "better-sqlite3": "^12.6.2",
60
60
  "mysql2": "^3.19.0",
61
61
  "pg": "^8.13.0",
62
- "zod": "^3.24.2"
62
+ "zod": "^4.0.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/better-sqlite3": "^7.6.13",
66
66
  "@types/mysql": "^2.15.27",
67
- "@types/node": "^22.0.0",
67
+ "@types/node": "^25.5.0",
68
68
  "@types/pg": "^8.11.0",
69
- "typescript": "^5.8.2",
70
- "vitest": "^4.0.18"
69
+ "typescript": "^6.0.0",
70
+ "vitest": "^4.1.0"
71
71
  }
72
72
  }