mcp-jvm-diagnostics 0.1.0 → 0.1.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 +23 -32
- package/package.json +11 -7
- package/build/license.js +0 -114
package/build/index.js
CHANGED
|
@@ -10,9 +10,6 @@ import { analyzeGcPressure } from "./analyzers/gc-pressure.js";
|
|
|
10
10
|
import { parseHeapHisto } from "./parsers/heap-histo.js";
|
|
11
11
|
import { compareHeapHistos } from "./analyzers/heap-diff.js";
|
|
12
12
|
import { parseJfrSummary } from "./parsers/jfr-summary.js";
|
|
13
|
-
import { validateLicense, formatUpgradePrompt } from "./license.js";
|
|
14
|
-
// License check (reads MCP_LICENSE_KEY env var once at startup)
|
|
15
|
-
const license = validateLicense(process.env.MCP_LICENSE_KEY, "jvm-diagnostics");
|
|
16
13
|
// Handle --help
|
|
17
14
|
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
18
15
|
console.log(`mcp-jvm-diagnostics v0.1.0 — MCP server for JVM diagnostics
|
|
@@ -57,11 +54,29 @@ server.tool("analyze_thread_dump", "Parse a JVM thread dump (jstack output) and
|
|
|
57
54
|
for (const t of parsed.threads) {
|
|
58
55
|
stateCounts.set(t.state, (stateCounts.get(t.state) || 0) + 1);
|
|
59
56
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
// Ensure all canonical Java thread states appear
|
|
58
|
+
const canonicalStates = ["RUNNABLE", "WAITING", "TIMED_WAITING", "BLOCKED", "NEW", "TERMINATED"];
|
|
59
|
+
for (const s of canonicalStates) {
|
|
60
|
+
if (!stateCounts.has(s))
|
|
61
|
+
stateCounts.set(s, 0);
|
|
62
|
+
}
|
|
63
|
+
const total = parsed.threads.length;
|
|
64
|
+
const maxCount = Math.max(...stateCounts.values(), 1);
|
|
65
|
+
const barMaxWidth = 20;
|
|
66
|
+
sections.push(`\n### Thread State Summary`);
|
|
67
|
+
sections.push(`| State | Count | % | Histogram |`);
|
|
68
|
+
sections.push(`|-------|------:|--:|-----------|`);
|
|
69
|
+
// Sort: non-zero descending by count, then zero-count states in canonical order
|
|
70
|
+
const sorted = [...stateCounts.entries()].sort((a, b) => {
|
|
71
|
+
if (a[1] !== b[1])
|
|
72
|
+
return b[1] - a[1];
|
|
73
|
+
return canonicalStates.indexOf(a[0]) - canonicalStates.indexOf(b[0]);
|
|
74
|
+
});
|
|
75
|
+
for (const [state, count] of sorted) {
|
|
76
|
+
const pct = total > 0 ? ((count / total) * 100).toFixed(1) : "0.0";
|
|
77
|
+
const barLen = Math.round((count / maxCount) * barMaxWidth);
|
|
78
|
+
const bar = "\u2588".repeat(barLen);
|
|
79
|
+
sections.push(`| ${state} | ${count} | ${pct} | \`${bar}\` |`);
|
|
65
80
|
}
|
|
66
81
|
// Deadlocks
|
|
67
82
|
if (deadlocks.length > 0) {
|
|
@@ -222,18 +237,6 @@ server.tool("compare_heap_histos", "Compare two jmap -histo snapshots taken at d
|
|
|
222
237
|
.string()
|
|
223
238
|
.describe("The SECOND (later) jmap -histo output"),
|
|
224
239
|
}, async ({ before, after }) => {
|
|
225
|
-
if (!license.isPro) {
|
|
226
|
-
return {
|
|
227
|
-
content: [{
|
|
228
|
-
type: "text",
|
|
229
|
-
text: formatUpgradePrompt("compare_heap_histos", "Heap histogram comparison with:\n" +
|
|
230
|
-
"- Memory growth pattern detection between snapshots\n" +
|
|
231
|
-
"- Leak candidate identification\n" +
|
|
232
|
-
"- New class allocation tracking\n" +
|
|
233
|
-
"- Shrinking class analysis"),
|
|
234
|
-
}],
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
240
|
try {
|
|
238
241
|
const report = compareHeapHistos(before, after);
|
|
239
242
|
const sections = [];
|
|
@@ -373,18 +376,6 @@ server.tool("diagnose_jvm", "Unified JVM diagnosis combining thread dump and GC
|
|
|
373
376
|
.optional()
|
|
374
377
|
.describe("GC log text (from -Xlog:gc*)"),
|
|
375
378
|
}, async ({ thread_dump, gc_log }) => {
|
|
376
|
-
if (!license.isPro) {
|
|
377
|
-
return {
|
|
378
|
-
content: [{
|
|
379
|
-
type: "text",
|
|
380
|
-
text: formatUpgradePrompt("diagnose_jvm", "Unified JVM diagnosis with:\n" +
|
|
381
|
-
"- Combined thread dump + GC log analysis\n" +
|
|
382
|
-
"- Cross-correlation of GC pauses and thread contention\n" +
|
|
383
|
-
"- Root cause identification\n" +
|
|
384
|
-
"- Prioritized remediation plan"),
|
|
385
|
-
}],
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
379
|
try {
|
|
389
380
|
if (!thread_dump && !gc_log) {
|
|
390
381
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-jvm-diagnostics",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "MCP server for JVM diagnostics — analyze thread dumps, detect deadlocks, parse GC logs, and get JVM tuning recommendations",
|
|
5
5
|
"author": "Dmytro Lisnichenko",
|
|
6
6
|
"type": "module",
|
|
@@ -23,17 +23,21 @@
|
|
|
23
23
|
],
|
|
24
24
|
"keywords": [
|
|
25
25
|
"mcp",
|
|
26
|
+
"mcp-server",
|
|
26
27
|
"model-context-protocol",
|
|
28
|
+
"ai",
|
|
29
|
+
"claude",
|
|
30
|
+
"anthropic",
|
|
27
31
|
"jvm",
|
|
28
32
|
"java",
|
|
29
|
-
"thread-dump",
|
|
30
|
-
"deadlock",
|
|
31
|
-
"gc-log",
|
|
32
|
-
"garbage-collection",
|
|
33
33
|
"diagnostics",
|
|
34
|
+
"monitoring",
|
|
34
35
|
"performance",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
36
|
+
"heap",
|
|
37
|
+
"garbage-collection",
|
|
38
|
+
"thread-dump",
|
|
39
|
+
"deadlock",
|
|
40
|
+
"gc-log"
|
|
37
41
|
],
|
|
38
42
|
"license": "MIT",
|
|
39
43
|
"engines": {
|
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
|
-
}
|