mcp-db-analyzer 0.2.2 → 0.2.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/build/index.js +0 -49
- package/package.json +1 -1
- package/build/license.js +0 -114
package/build/index.js
CHANGED
|
@@ -13,9 +13,6 @@ import { analyzeTableRelationships } from "./analyzers/relationships.js";
|
|
|
13
13
|
import { analyzeVacuum } from "./analyzers/vacuum.js";
|
|
14
14
|
import { closePool, initDriver, setConnectionTimeoutMs } from "./db.js";
|
|
15
15
|
import { formatToolError } from "./errors.js";
|
|
16
|
-
import { validateLicense, formatUpgradePrompt } from "./license.js";
|
|
17
|
-
// License check (reads MCP_LICENSE_KEY env var once at startup)
|
|
18
|
-
const license = validateLicense(process.env.MCP_LICENSE_KEY, "db-analyzer");
|
|
19
16
|
// Handle --help
|
|
20
17
|
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
21
18
|
console.log(`mcp-db-analyzer v0.1.0 — MCP server for database analysis
|
|
@@ -204,17 +201,6 @@ server.tool("suggest_missing_indexes", "Find tables with high sequential scan co
|
|
|
204
201
|
timeout_ms: timeoutParam,
|
|
205
202
|
}, async ({ schema, timeout_ms }) => {
|
|
206
203
|
applyTimeout(timeout_ms);
|
|
207
|
-
if (!license.isPro) {
|
|
208
|
-
return {
|
|
209
|
-
content: [{
|
|
210
|
-
type: "text",
|
|
211
|
-
text: formatUpgradePrompt("suggest_missing_indexes", "Actionable index recommendations with:\n" +
|
|
212
|
-
"- Tables with high sequential scan counts\n" +
|
|
213
|
-
"- Cross-referenced unused indexes wasting space\n" +
|
|
214
|
-
"- Ready-to-run CREATE INDEX and DROP INDEX statements"),
|
|
215
|
-
}],
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
204
|
try {
|
|
219
205
|
const result = await suggestMissingIndexes(schema);
|
|
220
206
|
return { content: [{ type: "text", text: result }] };
|
|
@@ -243,17 +229,6 @@ server.tool("analyze_slow_queries", "Find the slowest queries using pg_stat_stat
|
|
|
243
229
|
timeout_ms: timeoutParam,
|
|
244
230
|
}, async ({ schema, limit, timeout_ms }) => {
|
|
245
231
|
applyTimeout(timeout_ms);
|
|
246
|
-
if (!license.isPro) {
|
|
247
|
-
return {
|
|
248
|
-
content: [{
|
|
249
|
-
type: "text",
|
|
250
|
-
text: formatUpgradePrompt("analyze_slow_queries", "Slow query analysis with:\n" +
|
|
251
|
-
"- Top queries ranked by total execution time\n" +
|
|
252
|
-
"- Call counts, mean/max times, rows returned\n" +
|
|
253
|
-
"- Optimization recommendations"),
|
|
254
|
-
}],
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
232
|
try {
|
|
258
233
|
const result = await analyzeSlowQueries(schema, limit);
|
|
259
234
|
return { content: [{ type: "text", text: result }] };
|
|
@@ -274,18 +249,6 @@ server.tool("analyze_connections", "Analyze active database connections. Detects
|
|
|
274
249
|
timeout_ms: timeoutParam,
|
|
275
250
|
}, async ({ timeout_ms }) => {
|
|
276
251
|
applyTimeout(timeout_ms);
|
|
277
|
-
if (!license.isPro) {
|
|
278
|
-
return {
|
|
279
|
-
content: [{
|
|
280
|
-
type: "text",
|
|
281
|
-
text: formatUpgradePrompt("analyze_connections", "Connection analysis with:\n" +
|
|
282
|
-
"- Idle-in-transaction session detection\n" +
|
|
283
|
-
"- Long-running query identification\n" +
|
|
284
|
-
"- Lock contention analysis\n" +
|
|
285
|
-
"- Connection pool utilization metrics"),
|
|
286
|
-
}],
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
252
|
try {
|
|
290
253
|
const result = await analyzeConnections();
|
|
291
254
|
return { content: [{ type: "text", text: result }] };
|
|
@@ -310,18 +273,6 @@ server.tool("analyze_table_relationships", "Analyze foreign key relationships be
|
|
|
310
273
|
timeout_ms: timeoutParam,
|
|
311
274
|
}, async ({ schema, timeout_ms }) => {
|
|
312
275
|
applyTimeout(timeout_ms);
|
|
313
|
-
if (!license.isPro) {
|
|
314
|
-
return {
|
|
315
|
-
content: [{
|
|
316
|
-
type: "text",
|
|
317
|
-
text: formatUpgradePrompt("analyze_table_relationships", "Table relationship analysis with:\n" +
|
|
318
|
-
"- Foreign key dependency graph\n" +
|
|
319
|
-
"- Orphan table detection\n" +
|
|
320
|
-
"- Cascading delete chain analysis\n" +
|
|
321
|
-
"- Hub entity identification"),
|
|
322
|
-
}],
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
276
|
try {
|
|
326
277
|
const result = await analyzeTableRelationships(schema);
|
|
327
278
|
return { content: [{ type: "text", text: result }] };
|
package/package.json
CHANGED
package/build/license.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* License validation for MCP Migration Advisor (Pro features).
|
|
3
|
-
*
|
|
4
|
-
* Validates license keys offline using HMAC-SHA256.
|
|
5
|
-
* Missing or invalid keys gracefully degrade to free mode — never errors.
|
|
6
|
-
*
|
|
7
|
-
* Key format: MCPJBS-XXXXX-XXXXX-XXXXX-XXXXX
|
|
8
|
-
* Payload (12 bytes = 20 base32 chars):
|
|
9
|
-
* [0] product mask (8 bits)
|
|
10
|
-
* [1-2] expiry days since 2026-01-01 (16 bits)
|
|
11
|
-
* [3-5] customer ID (24 bits)
|
|
12
|
-
* [6-11] HMAC-SHA256 truncated (48 bits)
|
|
13
|
-
*/
|
|
14
|
-
import { createHmac } from "node:crypto";
|
|
15
|
-
const KEY_PREFIX = "MCPJBS-";
|
|
16
|
-
const EPOCH = new Date("2026-01-01T00:00:00Z");
|
|
17
|
-
const BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
18
|
-
const HMAC_SECRET = "mcp-java-backend-suite-license-v1";
|
|
19
|
-
const PRODUCTS = {
|
|
20
|
-
"db-analyzer": 0,
|
|
21
|
-
"jvm-diagnostics": 1,
|
|
22
|
-
"migration-advisor": 2,
|
|
23
|
-
"spring-boot-actuator": 3,
|
|
24
|
-
"redis-diagnostics": 4,
|
|
25
|
-
};
|
|
26
|
-
export function validateLicense(key, product) {
|
|
27
|
-
const FREE = {
|
|
28
|
-
isPro: false,
|
|
29
|
-
expiresAt: null,
|
|
30
|
-
customerId: null,
|
|
31
|
-
reason: "No license key provided",
|
|
32
|
-
};
|
|
33
|
-
if (!key || key.trim().length === 0)
|
|
34
|
-
return FREE;
|
|
35
|
-
const trimmed = key.trim().toUpperCase();
|
|
36
|
-
if (!trimmed.startsWith(KEY_PREFIX)) {
|
|
37
|
-
return { ...FREE, reason: "Invalid key format: missing MCPJBS- prefix" };
|
|
38
|
-
}
|
|
39
|
-
const body = trimmed.slice(KEY_PREFIX.length).replace(/-/g, "");
|
|
40
|
-
if (body.length < 20) {
|
|
41
|
-
return { ...FREE, reason: "Invalid key format: too short" };
|
|
42
|
-
}
|
|
43
|
-
let decoded;
|
|
44
|
-
try {
|
|
45
|
-
decoded = base32Decode(body.slice(0, 20));
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return { ...FREE, reason: "Invalid key format: bad base32 encoding" };
|
|
49
|
-
}
|
|
50
|
-
if (decoded.length < 12) {
|
|
51
|
-
return { ...FREE, reason: "Invalid key format: decoded data too short" };
|
|
52
|
-
}
|
|
53
|
-
const payload = decoded.subarray(0, 6);
|
|
54
|
-
const providedSignature = decoded.subarray(6, 12);
|
|
55
|
-
const expectedHmac = createHmac("sha256", HMAC_SECRET)
|
|
56
|
-
.update(payload)
|
|
57
|
-
.digest();
|
|
58
|
-
const expectedSignature = expectedHmac.subarray(0, 6);
|
|
59
|
-
if (!providedSignature.equals(expectedSignature)) {
|
|
60
|
-
return { ...FREE, reason: "Invalid license key: signature mismatch" };
|
|
61
|
-
}
|
|
62
|
-
const productMask = payload[0];
|
|
63
|
-
const daysSinceEpoch = (payload[1] << 8) | payload[2];
|
|
64
|
-
const customerId = (payload[3] << 16) | (payload[4] << 8) | payload[5];
|
|
65
|
-
const productBit = PRODUCTS[product];
|
|
66
|
-
if (productBit === undefined) {
|
|
67
|
-
return { ...FREE, reason: `Unknown product: ${product}` };
|
|
68
|
-
}
|
|
69
|
-
if ((productMask & (1 << productBit)) === 0) {
|
|
70
|
-
return { ...FREE, customerId, reason: `License does not include ${product}` };
|
|
71
|
-
}
|
|
72
|
-
const expiresAt = new Date(EPOCH.getTime() + daysSinceEpoch * 24 * 60 * 60 * 1000);
|
|
73
|
-
if (new Date() > expiresAt) {
|
|
74
|
-
return {
|
|
75
|
-
isPro: false,
|
|
76
|
-
expiresAt,
|
|
77
|
-
customerId,
|
|
78
|
-
reason: `License expired on ${expiresAt.toISOString().slice(0, 10)}`,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
return { isPro: true, expiresAt, customerId, reason: "Valid Pro license" };
|
|
82
|
-
}
|
|
83
|
-
export function formatUpgradePrompt(toolName, featureDescription) {
|
|
84
|
-
return [
|
|
85
|
-
`## ${toolName} (Pro Feature)`,
|
|
86
|
-
"",
|
|
87
|
-
"This analysis is available with MCP Java Backend Suite Pro.",
|
|
88
|
-
"",
|
|
89
|
-
`**What you'll get:**`,
|
|
90
|
-
featureDescription,
|
|
91
|
-
"",
|
|
92
|
-
"**Upgrade**: https://mcpjbs.dev/pricing",
|
|
93
|
-
"**Price**: $19/month or $190/year",
|
|
94
|
-
"",
|
|
95
|
-
"> Already have a key? Set `MCP_LICENSE_KEY` in your Claude Desktop config.",
|
|
96
|
-
].join("\n");
|
|
97
|
-
}
|
|
98
|
-
function base32Decode(encoded) {
|
|
99
|
-
const bytes = [];
|
|
100
|
-
let bits = 0;
|
|
101
|
-
let value = 0;
|
|
102
|
-
for (const char of encoded) {
|
|
103
|
-
const idx = BASE32_CHARS.indexOf(char);
|
|
104
|
-
if (idx === -1)
|
|
105
|
-
throw new Error(`Invalid base32 character: ${char}`);
|
|
106
|
-
value = (value << 5) | idx;
|
|
107
|
-
bits += 5;
|
|
108
|
-
if (bits >= 8) {
|
|
109
|
-
bits -= 8;
|
|
110
|
-
bytes.push((value >> bits) & 0xff);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return Buffer.from(bytes);
|
|
114
|
-
}
|