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.
- package/build/analyzers/connections.js +2 -1
- package/build/analyzers/schema.js +4 -1
- package/build/analyzers/vacuum.js +23 -9
- package/build/db-sqlite.js +3 -0
- package/build/errors.js +5 -1
- package/build/index.js +1 -1
- package/package.json +6 -6
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 = [];
|
package/build/db-sqlite.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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": ">=
|
|
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": "^
|
|
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": "^
|
|
67
|
+
"@types/node": "^25.5.0",
|
|
68
68
|
"@types/pg": "^8.11.0",
|
|
69
|
-
"typescript": "^
|
|
70
|
-
"vitest": "^4.0
|
|
69
|
+
"typescript": "^6.0.0",
|
|
70
|
+
"vitest": "^4.1.0"
|
|
71
71
|
}
|
|
72
72
|
}
|