mcp-db-analyzer 0.2.0 → 0.2.2
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/db-mysql.js +4 -1
- package/build/db-postgres.js +9 -1
- package/build/db.js +7 -0
- package/build/index.js +38 -10
- package/package.json +11 -5
package/build/db-mysql.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import mysql from "mysql2/promise";
|
|
2
|
+
import { getConnectionTimeoutMs } from "./db.js";
|
|
2
3
|
function wrapConnectionError(err) {
|
|
3
4
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4
5
|
const sanitized = msg.replace(/\/\/[^@]+@/g, "//****:****@");
|
|
@@ -11,9 +12,10 @@ export function createMysqlAdapter() {
|
|
|
11
12
|
let pool = null;
|
|
12
13
|
function getPool() {
|
|
13
14
|
if (!pool) {
|
|
15
|
+
const timeoutMs = getConnectionTimeoutMs();
|
|
14
16
|
const uri = process.env.DATABASE_URL;
|
|
15
17
|
if (uri) {
|
|
16
|
-
pool = mysql.createPool(uri);
|
|
18
|
+
pool = mysql.createPool({ uri, connectTimeout: timeoutMs });
|
|
17
19
|
}
|
|
18
20
|
else {
|
|
19
21
|
pool = mysql.createPool({
|
|
@@ -22,6 +24,7 @@ export function createMysqlAdapter() {
|
|
|
22
24
|
database: process.env.MYSQL_DATABASE || process.env.DB_NAME,
|
|
23
25
|
user: process.env.MYSQL_USER || process.env.DB_USER,
|
|
24
26
|
password: process.env.MYSQL_PASSWORD || process.env.DB_PASSWORD,
|
|
27
|
+
connectTimeout: timeoutMs,
|
|
25
28
|
});
|
|
26
29
|
}
|
|
27
30
|
}
|
package/build/db-postgres.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import pg from "pg";
|
|
2
|
+
import { getConnectionTimeoutMs } from "./db.js";
|
|
2
3
|
const { Pool } = pg;
|
|
3
4
|
function wrapConnectionError(err) {
|
|
4
5
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -12,9 +13,14 @@ export function createPostgresAdapter() {
|
|
|
12
13
|
let pool = null;
|
|
13
14
|
function getPool() {
|
|
14
15
|
if (!pool) {
|
|
16
|
+
const timeoutMs = getConnectionTimeoutMs();
|
|
15
17
|
const connectionString = process.env.DATABASE_URL;
|
|
16
18
|
if (connectionString) {
|
|
17
|
-
pool = new Pool({
|
|
19
|
+
pool = new Pool({
|
|
20
|
+
connectionString,
|
|
21
|
+
connectionTimeoutMillis: timeoutMs,
|
|
22
|
+
query_timeout: timeoutMs,
|
|
23
|
+
});
|
|
18
24
|
}
|
|
19
25
|
else {
|
|
20
26
|
pool = new Pool({
|
|
@@ -23,6 +29,8 @@ export function createPostgresAdapter() {
|
|
|
23
29
|
database: process.env.PGDATABASE || "postgres",
|
|
24
30
|
user: process.env.PGUSER,
|
|
25
31
|
password: process.env.PGPASSWORD,
|
|
32
|
+
connectionTimeoutMillis: timeoutMs,
|
|
33
|
+
query_timeout: timeoutMs,
|
|
26
34
|
});
|
|
27
35
|
}
|
|
28
36
|
}
|
package/build/db.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
let adapter = null;
|
|
2
2
|
let driverType = "postgres";
|
|
3
|
+
let connectionTimeoutMs = 30000;
|
|
3
4
|
export function getDriverType() {
|
|
4
5
|
return driverType;
|
|
5
6
|
}
|
|
7
|
+
export function getConnectionTimeoutMs() {
|
|
8
|
+
return connectionTimeoutMs;
|
|
9
|
+
}
|
|
10
|
+
export function setConnectionTimeoutMs(ms) {
|
|
11
|
+
connectionTimeoutMs = ms;
|
|
12
|
+
}
|
|
6
13
|
export function setAdapter(a) {
|
|
7
14
|
adapter = a;
|
|
8
15
|
driverType = a.driver;
|
package/build/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { analyzeSlowQueries } from "./analyzers/slow-queries.js";
|
|
|
11
11
|
import { analyzeConnections } from "./analyzers/connections.js";
|
|
12
12
|
import { analyzeTableRelationships } from "./analyzers/relationships.js";
|
|
13
13
|
import { analyzeVacuum } from "./analyzers/vacuum.js";
|
|
14
|
-
import { closePool, initDriver } from "./db.js";
|
|
14
|
+
import { closePool, initDriver, setConnectionTimeoutMs } from "./db.js";
|
|
15
15
|
import { formatToolError } from "./errors.js";
|
|
16
16
|
import { validateLicense, formatUpgradePrompt } from "./license.js";
|
|
17
17
|
// License check (reads MCP_LICENSE_KEY env var once at startup)
|
|
@@ -73,6 +73,15 @@ const server = new McpServer({
|
|
|
73
73
|
name: "mcp-db-analyzer",
|
|
74
74
|
version: "0.1.0",
|
|
75
75
|
});
|
|
76
|
+
// Shared Zod parameter for connection timeout
|
|
77
|
+
const timeoutParam = z
|
|
78
|
+
.number()
|
|
79
|
+
.optional()
|
|
80
|
+
.default(30000)
|
|
81
|
+
.describe("Connection timeout in milliseconds (default: 30000). Increase for slow or remote databases.");
|
|
82
|
+
function applyTimeout(timeout_ms) {
|
|
83
|
+
setConnectionTimeoutMs(timeout_ms);
|
|
84
|
+
}
|
|
76
85
|
// --- Tool: inspect_schema ---
|
|
77
86
|
server.tool("inspect_schema", "List all tables in a schema with row counts and sizes, or inspect a specific table's columns, types, constraints, and foreign keys.", {
|
|
78
87
|
table: z
|
|
@@ -83,7 +92,9 @@ server.tool("inspect_schema", "List all tables in a schema with row counts and s
|
|
|
83
92
|
.string()
|
|
84
93
|
.default("public")
|
|
85
94
|
.describe("Database schema to inspect (default: public)"),
|
|
86
|
-
|
|
95
|
+
timeout_ms: timeoutParam,
|
|
96
|
+
}, async ({ table, schema, timeout_ms }) => {
|
|
97
|
+
applyTimeout(timeout_ms);
|
|
87
98
|
try {
|
|
88
99
|
const result = table
|
|
89
100
|
? await inspectTable(table, schema)
|
|
@@ -111,7 +122,9 @@ server.tool("analyze_indexes", "Analyze index usage statistics to find unused in
|
|
|
111
122
|
.enum(["usage", "missing", "all"])
|
|
112
123
|
.default("all")
|
|
113
124
|
.describe("Analysis mode: 'usage' for unused index detection, 'missing' for missing index suggestions, 'all' for both"),
|
|
114
|
-
|
|
125
|
+
timeout_ms: timeoutParam,
|
|
126
|
+
}, async ({ schema, mode, timeout_ms }) => {
|
|
127
|
+
applyTimeout(timeout_ms);
|
|
115
128
|
try {
|
|
116
129
|
const parts = [];
|
|
117
130
|
if (mode === "usage" || mode === "all") {
|
|
@@ -140,7 +153,9 @@ server.tool("explain_query", "Run EXPLAIN on a SQL query and return a formatted
|
|
|
140
153
|
.boolean()
|
|
141
154
|
.default(false)
|
|
142
155
|
.describe("Run EXPLAIN ANALYZE to get actual execution times (executes the query). Only allowed for SELECT queries."),
|
|
143
|
-
|
|
156
|
+
timeout_ms: timeoutParam,
|
|
157
|
+
}, async ({ sql, analyze, timeout_ms }) => {
|
|
158
|
+
applyTimeout(timeout_ms);
|
|
144
159
|
try {
|
|
145
160
|
const result = await explainQuery(sql, analyze);
|
|
146
161
|
return { content: [{ type: "text", text: result }] };
|
|
@@ -162,7 +177,9 @@ server.tool("analyze_table_bloat", "Analyze table bloat by checking dead tuple r
|
|
|
162
177
|
.string()
|
|
163
178
|
.default("public")
|
|
164
179
|
.describe("Database schema to analyze (default: public)"),
|
|
165
|
-
|
|
180
|
+
timeout_ms: timeoutParam,
|
|
181
|
+
}, async ({ schema, timeout_ms }) => {
|
|
182
|
+
applyTimeout(timeout_ms);
|
|
166
183
|
try {
|
|
167
184
|
const result = await analyzeTableBloat(schema);
|
|
168
185
|
return { content: [{ type: "text", text: result }] };
|
|
@@ -184,7 +201,9 @@ server.tool("suggest_missing_indexes", "Find tables with high sequential scan co
|
|
|
184
201
|
.string()
|
|
185
202
|
.default("public")
|
|
186
203
|
.describe("Database schema to analyze (default: public)"),
|
|
187
|
-
|
|
204
|
+
timeout_ms: timeoutParam,
|
|
205
|
+
}, async ({ schema, timeout_ms }) => {
|
|
206
|
+
applyTimeout(timeout_ms);
|
|
188
207
|
if (!license.isPro) {
|
|
189
208
|
return {
|
|
190
209
|
content: [{
|
|
@@ -221,7 +240,9 @@ server.tool("analyze_slow_queries", "Find the slowest queries using pg_stat_stat
|
|
|
221
240
|
.number()
|
|
222
241
|
.default(10)
|
|
223
242
|
.describe("Number of slow queries to return (default: 10)"),
|
|
224
|
-
|
|
243
|
+
timeout_ms: timeoutParam,
|
|
244
|
+
}, async ({ schema, limit, timeout_ms }) => {
|
|
245
|
+
applyTimeout(timeout_ms);
|
|
225
246
|
if (!license.isPro) {
|
|
226
247
|
return {
|
|
227
248
|
content: [{
|
|
@@ -249,7 +270,10 @@ server.tool("analyze_slow_queries", "Find the slowest queries using pg_stat_stat
|
|
|
249
270
|
}
|
|
250
271
|
});
|
|
251
272
|
// Tool 7: analyze_connections
|
|
252
|
-
server.tool("analyze_connections", "Analyze active database connections. Detects idle-in-transaction sessions, long-running queries, lock contention, and connection pool utilization. PostgreSQL and MySQL only.", {
|
|
273
|
+
server.tool("analyze_connections", "Analyze active database connections. Detects idle-in-transaction sessions, long-running queries, lock contention, and connection pool utilization. PostgreSQL and MySQL only.", {
|
|
274
|
+
timeout_ms: timeoutParam,
|
|
275
|
+
}, async ({ timeout_ms }) => {
|
|
276
|
+
applyTimeout(timeout_ms);
|
|
253
277
|
if (!license.isPro) {
|
|
254
278
|
return {
|
|
255
279
|
content: [{
|
|
@@ -283,7 +307,9 @@ server.tool("analyze_table_relationships", "Analyze foreign key relationships be
|
|
|
283
307
|
.string()
|
|
284
308
|
.default("public")
|
|
285
309
|
.describe("Database schema to analyze (default: public)"),
|
|
286
|
-
|
|
310
|
+
timeout_ms: timeoutParam,
|
|
311
|
+
}, async ({ schema, timeout_ms }) => {
|
|
312
|
+
applyTimeout(timeout_ms);
|
|
287
313
|
if (!license.isPro) {
|
|
288
314
|
return {
|
|
289
315
|
content: [{
|
|
@@ -317,7 +343,9 @@ server.tool("analyze_vacuum", "Analyze PostgreSQL VACUUM maintenance status. Che
|
|
|
317
343
|
.string()
|
|
318
344
|
.default("public")
|
|
319
345
|
.describe("Database schema to analyze (default: public)"),
|
|
320
|
-
|
|
346
|
+
timeout_ms: timeoutParam,
|
|
347
|
+
}, async ({ schema, timeout_ms }) => {
|
|
348
|
+
applyTimeout(timeout_ms);
|
|
321
349
|
try {
|
|
322
350
|
const result = await analyzeVacuum(schema);
|
|
323
351
|
return { content: [{ type: "text", text: result }] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-db-analyzer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "MCP server for PostgreSQL, MySQL, and SQLite schema analysis, index optimization, and query plan inspection",
|
|
5
5
|
"author": "Dmytro Lisnichenko",
|
|
6
6
|
"type": "module",
|
|
@@ -24,16 +24,22 @@
|
|
|
24
24
|
],
|
|
25
25
|
"keywords": [
|
|
26
26
|
"mcp",
|
|
27
|
+
"mcp-server",
|
|
27
28
|
"model-context-protocol",
|
|
29
|
+
"ai",
|
|
30
|
+
"claude",
|
|
31
|
+
"anthropic",
|
|
32
|
+
"database",
|
|
33
|
+
"sql",
|
|
34
|
+
"schema",
|
|
35
|
+
"database-analyzer",
|
|
36
|
+
"database-diagnostics",
|
|
28
37
|
"postgresql",
|
|
29
38
|
"mysql",
|
|
30
39
|
"sqlite",
|
|
31
|
-
"database",
|
|
32
40
|
"schema-analyzer",
|
|
33
41
|
"index-optimization",
|
|
34
|
-
"query-plan"
|
|
35
|
-
"ai",
|
|
36
|
-
"claude"
|
|
42
|
+
"query-plan"
|
|
37
43
|
],
|
|
38
44
|
"license": "MIT",
|
|
39
45
|
"engines": {
|