mcp-spring-boot-actuator 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -184,10 +184,10 @@ Once configured, try these prompts in Claude:
184
184
 
185
185
  ## Part of the MCP Java Backend Suite
186
186
 
187
- This server works alongside:
188
- - [mcp-db-analyzer](https://www.npmjs.com/package/mcp-db-analyzer) — Database performance analysis
187
+ - [mcp-db-analyzer](https://www.npmjs.com/package/mcp-db-analyzer) PostgreSQL/MySQL/SQLite schema analysis
189
188
  - [mcp-jvm-diagnostics](https://www.npmjs.com/package/mcp-jvm-diagnostics) — Thread dump and GC log analysis
190
- - [mcp-migration-advisor](https://www.npmjs.com/package/mcp-migration-advisor) — Schema migration risk analysis
189
+ - [mcp-redis-diagnostics](https://www.npmjs.com/package/mcp-redis-diagnostics) — Redis memory, slowlog, and client diagnostics
190
+ - [mcp-migration-advisor](https://www.npmjs.com/package/mcp-migration-advisor) — Flyway/Liquibase migration risk analysis
191
191
 
192
192
  ## Limitations & Known Issues
193
193
 
@@ -199,7 +199,7 @@ This server works alongside:
199
199
  - **Custom health indicators**: The tool recognizes standard health indicator patterns. Custom health indicators with non-standard status values may not trigger specific recommendations.
200
200
  - **Cache analysis**: Supports ConcurrentMapCache, Caffeine, Redis, and EhCache. Other cache providers may show limited analysis.
201
201
  - **Non-JSON responses**: Handles HTML error pages (401, 403, 500) gracefully with "Invalid JSON" warnings, but cannot extract useful data from them.
202
- - **Circular dependency depth**: Detects A→B→A cycles but may miss longer chains (A→B→C→A) in complex applications.
202
+ - **Circular dependency depth**: Detects cycles of any length, including multi-hop chains (A→B→C→A).
203
203
 
204
204
  ## License
205
205
 
@@ -10,7 +10,7 @@
10
10
  const SECRET_PATTERNS = [
11
11
  /password/i, /secret/i, /api[._-]?key/i, /token/i,
12
12
  /credential/i, /private[._-]?key/i, /access[._-]?key/i,
13
- /auth/i, /jwt/i,
13
+ /auth/i, /jwt/i, /client[._-]?secret/i,
14
14
  ];
15
15
  // Properties that should differ between dev and production
16
16
  const PRODUCTION_CHECKS = [
package/build/index.js CHANGED
@@ -8,9 +8,15 @@
8
8
  * analyze_env — Detect exposed secrets and risky configurations
9
9
  * analyze_beans — Detect circular dependencies and scope mismatches
10
10
  */
11
+ import { readFileSync } from "fs";
12
+ import { fileURLToPath } from "url";
13
+ import { dirname, join } from "path";
11
14
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
15
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
16
  import { z } from "zod";
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+ const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8"));
14
20
  import { parseHealth } from "./parsers/health.js";
15
21
  import { analyzeMetrics } from "./analyzers/metrics.js";
16
22
  import { analyzeEnv } from "./analyzers/env-risk.js";
@@ -21,7 +27,7 @@ import { analyzeLoggers } from "./analyzers/loggers.js";
21
27
  import { formatSeveritySummary } from "./format.js";
22
28
  // Handle --help
23
29
  if (process.argv.includes("--help") || process.argv.includes("-h")) {
24
- console.log(`mcp-spring-boot-actuator v0.1.1 — MCP server for Spring Boot Actuator diagnostics
30
+ console.log(`mcp-spring-boot-actuator v${pkg.version} — MCP server for Spring Boot Actuator diagnostics
25
31
 
26
32
  Usage:
27
33
  mcp-spring-boot-actuator [options]
@@ -34,216 +40,276 @@ Tools provided:
34
40
  analyze_metrics Analyze JVM, HTTP, and DB pool metrics
35
41
  analyze_env Detect exposed secrets and risky configurations
36
42
  analyze_beans Detect circular dependencies and scope mismatches
37
- analyze_startup Parse /startup endpoint for bean init times
43
+ analyze_startup Parse /startup endpoint for bean init times (Spring Boot 2.4+)
38
44
  analyze_caches Analyze /caches endpoint for cache health
39
45
  analyze_loggers Detect verbose logging and misconfigurations`);
40
46
  process.exit(0);
41
47
  }
42
48
  const server = new McpServer({
43
49
  name: "mcp-spring-boot-actuator",
44
- version: "0.1.0",
50
+ version: pkg.version,
45
51
  });
46
52
  // Tool 1: analyze_health
47
53
  server.tool("analyze_health", "Analyze a Spring Boot Actuator /health endpoint response. Diagnoses unhealthy components and provides recommendations.", {
48
54
  json: z.string().describe("JSON response from the /health endpoint (curl http://localhost:8080/actuator/health)"),
49
55
  }, async ({ json }) => {
50
- const report = parseHealth(json);
51
- let output = `## Health Analysis\n\n`;
52
- output += `**Overall Status**: ${report.overallStatus}\n`;
53
- output += `**Components**: ${report.components.length}\n\n`;
54
- if (report.components.length > 0) {
55
- output += "### Component Status\n\n";
56
- output += "| Component | Status | Details |\n|-----------|--------|--------|\n";
57
- for (const comp of report.components) {
58
- const details = Object.entries(comp.details).slice(0, 3).map(([k, v]) => `${k}=${v}`).join(", ");
59
- output += `| ${comp.name} | ${comp.status} | ${details || "-"} |\n`;
56
+ try {
57
+ const report = parseHealth(json);
58
+ let output = `## Health Analysis\n\n`;
59
+ output += `**Overall Status**: ${report.overallStatus}\n`;
60
+ output += `**Components**: ${report.components.length}\n\n`;
61
+ if (report.components.length > 0) {
62
+ output += "### Component Status\n\n";
63
+ output += "| Component | Status | Details |\n|-----------|--------|--------|\n";
64
+ for (const comp of report.components) {
65
+ const details = Object.entries(comp.details).slice(0, 3).map(([k, v]) => `${k}=${v}`).join(", ");
66
+ output += `| ${comp.name} | ${comp.status} | ${details || "-"} |\n`;
67
+ }
68
+ output += "\n";
60
69
  }
61
- output += "\n";
62
- }
63
- if (report.issues.length > 0) {
64
- output += "### Issues\n\n";
65
- for (const issue of report.issues) {
66
- output += `**${issue.severity}** [${issue.component}]: ${issue.message}\n\n`;
70
+ if (report.issues.length > 0) {
71
+ output += "### Issues\n\n";
72
+ for (const issue of report.issues) {
73
+ output += `**${issue.severity}** [${issue.component}]: ${issue.message}\n\n`;
74
+ }
67
75
  }
68
- }
69
- if (report.recommendations.length > 0) {
70
- output += "### Recommendations\n\n";
71
- for (const rec of report.recommendations) {
72
- output += `- ${rec}\n`;
76
+ if (report.recommendations.length > 0) {
77
+ output += "### Recommendations\n\n";
78
+ for (const rec of report.recommendations) {
79
+ output += `- ${rec}\n`;
80
+ }
73
81
  }
82
+ output += formatSeveritySummary(report.issues);
83
+ return { content: [{ type: "text", text: output }] };
84
+ }
85
+ catch (err) {
86
+ return {
87
+ content: [{
88
+ type: "text",
89
+ text: `Error analyzing health data: ${err instanceof Error ? err.message : String(err)}`,
90
+ }],
91
+ };
74
92
  }
75
- output += formatSeveritySummary(report.issues);
76
- return { content: [{ type: "text", text: output }] };
77
93
  });
78
94
  // Tool 2: analyze_metrics
79
95
  server.tool("analyze_metrics", "Analyze Spring Boot Actuator metrics data. Detects JVM memory pressure, high error rates, connection pool exhaustion, and GC issues.", {
80
96
  json: z.string().describe("JSON object with metric names as keys and values (e.g., from /metrics endpoints)"),
81
97
  }, async ({ json }) => {
82
- const report = analyzeMetrics(json);
83
- let output = `## Metrics Analysis\n\n`;
84
- if (report.jvm) {
85
- output += "### JVM\n\n";
86
- output += `| Metric | Value |\n|--------|-------|\n`;
87
- output += `| Heap Used | ${formatBytes(report.jvm.heapUsed)} |\n`;
88
- output += `| Heap Max | ${formatBytes(report.jvm.heapMax)} |\n`;
89
- output += `| Heap Utilization | ${(report.jvm.heapUtilization * 100).toFixed(1)}% |\n`;
90
- output += `| Thread Count | ${report.jvm.threadCount} |\n`;
91
- output += `| Thread Peak | ${report.jvm.threadPeak} |\n`;
92
- output += `| GC Pauses | ${report.jvm.gcPauseCount} (${report.jvm.gcPauseTotal.toFixed(1)}s total) |\n`;
93
- output += `| Loaded Classes | ${report.jvm.loadedClasses} |\n\n`;
94
- }
95
- if (report.http) {
96
- output += "### HTTP\n\n";
97
- output += `| Metric | Value |\n|--------|-------|\n`;
98
- output += `| Total Requests | ${report.http.totalRequests} |\n`;
99
- output += `| Error Rate | ${(report.http.errorRate * 100).toFixed(1)}% |\n`;
100
- output += `| Max Latency | ${report.http.maxLatency}ms |\n\n`;
101
- }
102
- if (report.issues.length > 0) {
103
- output += "### Issues\n\n";
104
- for (const issue of report.issues) {
105
- output += `**${issue.severity}** [${issue.category}]: ${issue.message}\n\n`;
98
+ try {
99
+ const report = analyzeMetrics(json);
100
+ let output = `## Metrics Analysis\n\n`;
101
+ if (report.jvm) {
102
+ output += "### JVM\n\n";
103
+ output += `| Metric | Value |\n|--------|-------|\n`;
104
+ output += `| Heap Used | ${formatBytes(report.jvm.heapUsed)} |\n`;
105
+ output += `| Heap Max | ${formatBytes(report.jvm.heapMax)} |\n`;
106
+ output += `| Heap Utilization | ${(report.jvm.heapUtilization * 100).toFixed(1)}% |\n`;
107
+ output += `| Thread Count | ${report.jvm.threadCount} |\n`;
108
+ output += `| Thread Peak | ${report.jvm.threadPeak} |\n`;
109
+ output += `| GC Pauses | ${report.jvm.gcPauseCount} (${report.jvm.gcPauseTotal.toFixed(1)}s total) |\n`;
110
+ output += `| Loaded Classes | ${report.jvm.loadedClasses} |\n\n`;
106
111
  }
107
- }
108
- if (report.recommendations.length > 0) {
109
- output += "### Recommendations\n\n";
110
- for (const rec of report.recommendations) {
111
- output += `- ${rec}\n`;
112
+ if (report.http) {
113
+ output += "### HTTP\n\n";
114
+ output += `| Metric | Value |\n|--------|-------|\n`;
115
+ output += `| Total Requests | ${report.http.totalRequests} |\n`;
116
+ output += `| Error Rate | ${(report.http.errorRate * 100).toFixed(1)}% |\n`;
117
+ output += `| Max Latency | ${report.http.maxLatency}ms |\n\n`;
118
+ }
119
+ if (report.issues.length > 0) {
120
+ output += "### Issues\n\n";
121
+ for (const issue of report.issues) {
122
+ output += `**${issue.severity}** [${issue.category}]: ${issue.message}\n\n`;
123
+ }
124
+ }
125
+ if (report.recommendations.length > 0) {
126
+ output += "### Recommendations\n\n";
127
+ for (const rec of report.recommendations) {
128
+ output += `- ${rec}\n`;
129
+ }
130
+ }
131
+ if (!report.jvm && !report.http && report.issues.length === 0) {
132
+ output += "No recognized metrics found. Provide metrics in the format: `{\"jvm.memory.used\": 1234567, ...}`\n";
112
133
  }
134
+ output += formatSeveritySummary(report.issues);
135
+ return { content: [{ type: "text", text: output }] };
113
136
  }
114
- if (!report.jvm && !report.http && report.issues.length === 0) {
115
- output += "No recognized metrics found. Provide metrics in the format: `{\"jvm.memory.used\": 1234567, ...}`\n";
137
+ catch (err) {
138
+ return {
139
+ content: [{
140
+ type: "text",
141
+ text: `Error analyzing metrics data: ${err instanceof Error ? err.message : String(err)}`,
142
+ }],
143
+ };
116
144
  }
117
- output += formatSeveritySummary(report.issues);
118
- return { content: [{ type: "text", text: output }] };
119
145
  });
120
146
  // Tool 3: analyze_env
121
147
  server.tool("analyze_env", "Analyze Spring Boot Actuator /env endpoint response. Detects exposed secrets, risky configurations, and missing production settings.", {
122
148
  json: z.string().describe("JSON response from the /env endpoint (curl http://localhost:8080/actuator/env)"),
123
149
  }, async ({ json }) => {
124
- const report = analyzeEnv(json);
125
- let output = `## Environment Analysis\n\n`;
126
- output += `**Active Profiles**: ${report.activeProfiles.length > 0 ? report.activeProfiles.join(", ") : "none"}\n`;
127
- output += `**Property Sources**: ${report.propertySources.length}\n\n`;
128
- if (report.risks.length > 0) {
129
- output += "### Risks\n\n";
130
- const critical = report.risks.filter(r => r.severity === "CRITICAL");
131
- const warning = report.risks.filter(r => r.severity === "WARNING");
132
- const info = report.risks.filter(r => r.severity === "INFO");
133
- for (const group of [critical, warning, info]) {
134
- for (const risk of group) {
135
- output += `**${risk.severity}** \`${risk.property}\`: ${risk.message}\n`;
136
- output += `> ${risk.recommendation}\n\n`;
150
+ try {
151
+ const report = analyzeEnv(json);
152
+ let output = `## Environment Analysis\n\n`;
153
+ output += `**Active Profiles**: ${report.activeProfiles.length > 0 ? report.activeProfiles.join(", ") : "none"}\n`;
154
+ output += `**Property Sources**: ${report.propertySources.length}\n\n`;
155
+ if (report.risks.length > 0) {
156
+ output += "### Risks\n\n";
157
+ const critical = report.risks.filter(r => r.severity === "CRITICAL");
158
+ const warning = report.risks.filter(r => r.severity === "WARNING");
159
+ const info = report.risks.filter(r => r.severity === "INFO");
160
+ for (const group of [critical, warning, info]) {
161
+ for (const risk of group) {
162
+ output += `**${risk.severity}** \`${risk.property}\`: ${risk.message}\n`;
163
+ output += `> ${risk.recommendation}\n\n`;
164
+ }
137
165
  }
138
166
  }
139
- }
140
- else {
141
- output += "### No risks detected.\n\n";
142
- }
143
- if (report.recommendations.length > 0) {
144
- output += "### Recommendations\n\n";
145
- for (const rec of report.recommendations) {
146
- output += `- ${rec}\n`;
167
+ else {
168
+ output += "### No risks detected.\n\n";
147
169
  }
170
+ if (report.recommendations.length > 0) {
171
+ output += "### Recommendations\n\n";
172
+ for (const rec of report.recommendations) {
173
+ output += `- ${rec}\n`;
174
+ }
175
+ }
176
+ output += formatSeveritySummary(report.risks);
177
+ return { content: [{ type: "text", text: output }] };
178
+ }
179
+ catch (err) {
180
+ return {
181
+ content: [{
182
+ type: "text",
183
+ text: `Error analyzing environment data: ${err instanceof Error ? err.message : String(err)}`,
184
+ }],
185
+ };
148
186
  }
149
- output += formatSeveritySummary(report.risks);
150
- return { content: [{ type: "text", text: output }] };
151
187
  });
152
188
  // Tool 4: analyze_beans
153
189
  server.tool("analyze_beans", "Analyze Spring Boot Actuator /beans endpoint response. Detects circular dependencies, scope mismatches, and bean architecture issues.", {
154
190
  json: z.string().describe("JSON response from the /beans endpoint (curl http://localhost:8080/actuator/beans)"),
155
191
  }, async ({ json }) => {
156
- const report = analyzeBeans(json);
157
- let output = `## Bean Analysis\n\n`;
158
- output += `**Total Beans**: ${report.totalBeans}\n`;
159
- if (report.contexts.length > 0) {
160
- output += `**Contexts**: ${report.contexts.join(", ")}\n`;
161
- }
162
- output += "\n";
163
- if (report.issues.length > 0) {
164
- output += "### Issues\n\n";
165
- for (const issue of report.issues) {
166
- output += `**${issue.severity}**: ${issue.message}\n`;
167
- if (issue.beans.length > 0) {
168
- output += `> Beans: ${issue.beans.join(", ")}\n`;
192
+ try {
193
+ const report = analyzeBeans(json);
194
+ let output = `## Bean Analysis\n\n`;
195
+ output += `**Total Beans**: ${report.totalBeans}\n`;
196
+ if (report.contexts.length > 0) {
197
+ output += `**Contexts**: ${report.contexts.join(", ")}\n`;
198
+ }
199
+ output += "\n";
200
+ if (report.issues.length > 0) {
201
+ output += "### Issues\n\n";
202
+ for (const issue of report.issues) {
203
+ output += `**${issue.severity}**: ${issue.message}\n`;
204
+ if (issue.beans.length > 0) {
205
+ output += `> Beans: ${issue.beans.join(", ")}\n`;
206
+ }
207
+ output += "\n";
169
208
  }
170
- output += "\n";
171
209
  }
172
- }
173
- else {
174
- output += "### No issues detected.\n\n";
175
- }
176
- if (report.recommendations.length > 0) {
177
- output += "### Recommendations\n\n";
178
- for (const rec of report.recommendations) {
179
- output += `- ${rec}\n`;
210
+ else {
211
+ output += "### No issues detected.\n\n";
212
+ }
213
+ if (report.recommendations.length > 0) {
214
+ output += "### Recommendations\n\n";
215
+ for (const rec of report.recommendations) {
216
+ output += `- ${rec}\n`;
217
+ }
180
218
  }
219
+ output += formatSeveritySummary(report.issues);
220
+ return { content: [{ type: "text", text: output }] };
221
+ }
222
+ catch (err) {
223
+ return {
224
+ content: [{
225
+ type: "text",
226
+ text: `Error analyzing beans data: ${err instanceof Error ? err.message : String(err)}`,
227
+ }],
228
+ };
181
229
  }
182
- output += formatSeveritySummary(report.issues);
183
- return { content: [{ type: "text", text: output }] };
184
230
  });
185
231
  // Tool 5: analyze_startup
186
- server.tool("analyze_startup", "Analyze Spring Boot Actuator /startup endpoint (Spring Boot 3.2+). Detects slow bean initialization, heavy auto-configurations, and startup bottlenecks.", {
232
+ server.tool("analyze_startup", "Analyze Spring Boot Actuator /startup endpoint (Spring Boot 2.4+). Detects slow bean initialization, heavy auto-configurations, and startup bottlenecks.", {
187
233
  json: z.string().describe("JSON response from the /startup endpoint (curl http://localhost:8080/actuator/startup)"),
188
234
  }, async ({ json }) => {
189
- const report = analyzeStartup(json);
190
- let output = `## Startup Analysis\n\n`;
191
- output += `**Total Startup Time**: ${(report.totalDurationMs / 1000).toFixed(1)}s\n`;
192
- output += `**Startup Steps**: ${report.steps.length}\n`;
193
- output += `**Slow Steps**: ${report.slowSteps.length}\n\n`;
194
- if (report.slowSteps.length > 0) {
195
- output += "### Slowest Steps\n\n";
196
- output += "| Step | Duration | Bean |\n|------|----------|------|\n";
197
- for (const step of report.slowSteps.slice(0, 15)) {
198
- const bean = step.tags.beanName || "-";
199
- output += `| ${step.name} | ${(step.durationMs / 1000).toFixed(2)}s | ${bean} |\n`;
235
+ try {
236
+ const report = analyzeStartup(json);
237
+ let output = `## Startup Analysis\n\n`;
238
+ output += `**Total Startup Time**: ${(report.totalDurationMs / 1000).toFixed(1)}s\n`;
239
+ output += `**Startup Steps**: ${report.steps.length}\n`;
240
+ output += `**Slow Steps**: ${report.slowSteps.length}\n\n`;
241
+ if (report.slowSteps.length > 0) {
242
+ output += "### Slowest Steps\n\n";
243
+ output += "| Step | Duration | Bean |\n|------|----------|------|\n";
244
+ for (const step of report.slowSteps.slice(0, 15)) {
245
+ const bean = step.tags.beanName || "-";
246
+ output += `| ${step.name} | ${(step.durationMs / 1000).toFixed(2)}s | ${bean} |\n`;
247
+ }
248
+ output += "\n";
200
249
  }
201
- output += "\n";
202
- }
203
- if (report.issues.length > 0) {
204
- output += "### Issues\n\n";
205
- for (const issue of report.issues) {
206
- output += `**${issue.severity}**: ${issue.message}\n\n`;
250
+ if (report.issues.length > 0) {
251
+ output += "### Issues\n\n";
252
+ for (const issue of report.issues) {
253
+ output += `**${issue.severity}**: ${issue.message}\n\n`;
254
+ }
207
255
  }
208
- }
209
- if (report.recommendations.length > 0) {
210
- output += "### Recommendations\n\n";
211
- for (const rec of report.recommendations) {
212
- output += `- ${rec}\n`;
256
+ if (report.recommendations.length > 0) {
257
+ output += "### Recommendations\n\n";
258
+ for (const rec of report.recommendations) {
259
+ output += `- ${rec}\n`;
260
+ }
213
261
  }
262
+ output += formatSeveritySummary(report.issues);
263
+ return { content: [{ type: "text", text: output }] };
264
+ }
265
+ catch (err) {
266
+ return {
267
+ content: [{
268
+ type: "text",
269
+ text: `Error analyzing startup data: ${err instanceof Error ? err.message : String(err)}`,
270
+ }],
271
+ };
214
272
  }
215
- output += formatSeveritySummary(report.issues);
216
- return { content: [{ type: "text", text: output }] };
217
273
  });
218
274
  // Tool 6: analyze_caches
219
275
  server.tool("analyze_caches", "Analyze Spring Boot Actuator /caches endpoint. Lists registered caches, detects unbounded caches (memory leak risk), and identifies cache configuration issues.", {
220
276
  json: z.string().describe("JSON response from the /caches endpoint (curl http://localhost:8080/actuator/caches)"),
221
277
  }, async ({ json }) => {
222
- const report = analyzeCaches(json);
223
- let output = `## Cache Analysis\n\n`;
224
- output += `**Registered Caches**: ${report.caches.length}\n\n`;
225
- if (report.caches.length > 0) {
226
- output += "### Cache Registry\n\n";
227
- output += "| Cache | Manager | Implementation |\n|-------|---------|----------------|\n";
228
- for (const cache of report.caches) {
229
- output += `| ${cache.name} | ${cache.cacheManager} | ${cache.target} |\n`;
278
+ try {
279
+ const report = analyzeCaches(json);
280
+ let output = `## Cache Analysis\n\n`;
281
+ output += `**Registered Caches**: ${report.caches.length}\n\n`;
282
+ if (report.caches.length > 0) {
283
+ output += "### Cache Registry\n\n";
284
+ output += "| Cache | Manager | Implementation |\n|-------|---------|----------------|\n";
285
+ for (const cache of report.caches) {
286
+ output += `| ${cache.name} | ${cache.cacheManager} | ${cache.target} |\n`;
287
+ }
288
+ output += "\n";
230
289
  }
231
- output += "\n";
232
- }
233
- if (report.issues.length > 0) {
234
- output += "### Issues\n\n";
235
- for (const issue of report.issues) {
236
- output += `**${issue.severity}**${issue.cache ? ` [${issue.cache}]` : ""}: ${issue.message}\n\n`;
290
+ if (report.issues.length > 0) {
291
+ output += "### Issues\n\n";
292
+ for (const issue of report.issues) {
293
+ output += `**${issue.severity}**${issue.cache ? ` [${issue.cache}]` : ""}: ${issue.message}\n\n`;
294
+ }
237
295
  }
238
- }
239
- if (report.recommendations.length > 0) {
240
- output += "### Recommendations\n\n";
241
- for (const rec of report.recommendations) {
242
- output += `- ${rec}\n`;
296
+ if (report.recommendations.length > 0) {
297
+ output += "### Recommendations\n\n";
298
+ for (const rec of report.recommendations) {
299
+ output += `- ${rec}\n`;
300
+ }
243
301
  }
302
+ output += formatSeveritySummary(report.issues);
303
+ return { content: [{ type: "text", text: output }] };
304
+ }
305
+ catch (err) {
306
+ return {
307
+ content: [{
308
+ type: "text",
309
+ text: `Error analyzing cache data: ${err instanceof Error ? err.message : String(err)}`,
310
+ }],
311
+ };
244
312
  }
245
- output += formatSeveritySummary(report.issues);
246
- return { content: [{ type: "text", text: output }] };
247
313
  });
248
314
  // Tool 7: analyze_loggers
249
315
  server.tool("analyze_loggers", "Analyze Spring Boot /loggers endpoint response. Detects DEBUG/TRACE in production, inconsistent log levels across packages, and verbose framework logging.", {
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "mcp-spring-boot-actuator",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "MCP server for Spring Boot Actuator analysis — health, metrics, environment, and bean diagnostics",
5
+ "mcpName": "io.github.dmitriusan/mcp-spring-boot-actuator",
5
6
  "main": "build/index.js",
6
7
  "bin": {
7
8
  "mcp-spring-boot-actuator": "build/index.js"