@thotischner/observability-mcp 3.0.1 → 3.1.1

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.
Files changed (46) hide show
  1. package/dist/analysis/history.d.ts +36 -2
  2. package/dist/analysis/history.js +60 -2
  3. package/dist/analysis/history.test.js +46 -0
  4. package/dist/auth/csrf.d.ts +6 -0
  5. package/dist/auth/csrf.js +4 -0
  6. package/dist/auth/csrf.test.js +22 -0
  7. package/dist/auth/lockout.d.ts +72 -0
  8. package/dist/auth/lockout.js +134 -0
  9. package/dist/auth/lockout.test.d.ts +1 -0
  10. package/dist/auth/lockout.test.js +133 -0
  11. package/dist/auth/middleware.d.ts +5 -0
  12. package/dist/auth/middleware.js +6 -1
  13. package/dist/auth/middleware.test.js +31 -0
  14. package/dist/auth/password-policy.d.ts +52 -0
  15. package/dist/auth/password-policy.js +125 -0
  16. package/dist/auth/password-policy.test.d.ts +1 -0
  17. package/dist/auth/password-policy.test.js +111 -0
  18. package/dist/auth/revocation.d.ts +93 -0
  19. package/dist/auth/revocation.js +193 -0
  20. package/dist/auth/revocation.test.d.ts +1 -0
  21. package/dist/auth/revocation.test.js +136 -0
  22. package/dist/auth/session.d.ts +7 -0
  23. package/dist/auth/session.js +6 -0
  24. package/dist/auth/session.test.js +21 -0
  25. package/dist/conformance/mcp-2025-11-25.test.js +14 -0
  26. package/dist/connectors/interface.d.ts +5 -1
  27. package/dist/connectors/loki.d.ts +45 -1
  28. package/dist/connectors/loki.js +141 -8
  29. package/dist/connectors/loki.test.js +171 -1
  30. package/dist/index.js +244 -4
  31. package/dist/openapi.js +39 -0
  32. package/dist/openapi.test.js +1 -0
  33. package/dist/security/csp.d.ts +64 -0
  34. package/dist/security/csp.js +135 -0
  35. package/dist/security/csp.test.d.ts +1 -0
  36. package/dist/security/csp.test.js +97 -0
  37. package/dist/tools/query-logs-schema.test.d.ts +1 -0
  38. package/dist/tools/query-logs-schema.test.js +38 -0
  39. package/dist/tools/query-logs.d.ts +40 -0
  40. package/dist/tools/query-logs.js +69 -3
  41. package/dist/tools/validation.d.ts +13 -0
  42. package/dist/tools/validation.js +74 -0
  43. package/dist/tools/validation.test.js +54 -1
  44. package/dist/types.d.ts +48 -0
  45. package/dist/ui/index.html +42 -15
  46. package/package.json +1 -1
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Observability MCP Gateway</title>
7
- <script>
7
+ <script nonce="__CSP_NONCE__">
8
8
  // Resolve the theme + density BEFORE first paint to avoid a flash.
9
9
  // Explicit user choice (localStorage) wins; otherwise follow the OS
10
10
  // setting for theme and default to comfortable density.
@@ -2656,7 +2656,7 @@ curl -s http://localhost:3000/api/enterprise/status</pre>
2656
2656
  <div class="drawer-bd" id="drawer-body"></div>
2657
2657
  </aside>
2658
2658
 
2659
- <script>
2659
+ <script nonce="__CSP_NONCE__">
2660
2660
  let sourcesData=[], servicesData=[], supportedTypes=[], settings={}, healthThresholds={}, defaults={};
2661
2661
  let deleteTarget=null, deleteType=null;
2662
2662
  // Per-source metrics state
@@ -6251,9 +6251,8 @@ function saveMetric() {
6251
6251
  // --- Health Dashboard ---
6252
6252
  let healthData={};
6253
6253
  let healthInterval=null;
6254
- // Score history per service, kept client-side. The server doesn't yet
6255
- // expose a per-service score timeseries this gives an at-a-glance trend
6256
- // for the last ~7.5 minutes (30 points × 15s refresh).
6254
+ // Client-side live-score trend, kept as a fallback for when the server's
6255
+ // anomaly-history sink isn't active. ~7.5 minutes (30 points × 15s refresh).
6257
6256
  const SPARK_MAX = 30;
6258
6257
  const scoreHistory = {};
6259
6258
  function pushScore(name, score) {
@@ -6262,31 +6261,59 @@ function pushScore(name, score) {
6262
6261
  arr.push(score);
6263
6262
  if (arr.length > SPARK_MAX) arr.shift();
6264
6263
  }
6265
- function sparkSvg(name, status) {
6266
- const pts = scoreHistory[name] || [];
6264
+ // Server-side anomaly-score history (Q21). Populated from
6265
+ // /api/health/anomaly-sparklines the last hour of omcp_anomaly_score
6266
+ // from the anomaly-history sink. Survives reloads; preferred over the
6267
+ // client-side trend when present.
6268
+ let anomalySpark = { enabled:false, windowMs:0, series:{} };
6269
+
6270
+ function drawSpark(values, status, domainMax, title){
6267
6271
  const w = 100, h = 36, pad = 2;
6268
- if (pts.length < 2) {
6269
- // Placeholder dashed midline until we have ≥2 samples.
6272
+ if (!values || values.length < 2) {
6270
6273
  return `<svg class="hc-spark ${status}" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none" aria-hidden="true">`
6271
6274
  + `<line class="spark-empty" x1="0" y1="${h/2}" x2="${w}" y2="${h/2}"/></svg>`;
6272
6275
  }
6273
- const min = 0, max = 100;
6274
- const step = (w - pad*2) / (pts.length - 1);
6275
- const y = v => h - pad - ((v - min) / (max - min)) * (h - pad*2);
6276
- const coords = pts.map((v, i) => `${pad + i*step},${y(v)}`);
6276
+ const min = 0, max = domainMax;
6277
+ const step = (w - pad*2) / (values.length - 1);
6278
+ const y = v => h - pad - ((Math.max(min, Math.min(max, v)) - min) / (max - min || 1)) * (h - pad*2);
6279
+ const coords = values.map((v, i) => `${pad + i*step},${y(v)}`);
6277
6280
  const line = coords.join(' ');
6278
- const area = `M${coords[0]} L${line.split(' ').join(' L')} L${pad + (pts.length-1)*step},${h-pad} L${pad},${h-pad} Z`;
6281
+ const area = `M${coords[0]} L${line.split(' ').join(' L')} L${pad + (values.length-1)*step},${h-pad} L${pad},${h-pad} Z`;
6279
6282
  const last = coords[coords.length - 1].split(',');
6280
6283
  return `<svg class="hc-spark ${status}" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none" aria-hidden="true">`
6284
+ + (title?`<title>${esc(title)}</title>`:'')
6281
6285
  + `<path class="spark-fill" d="${area}"/>`
6282
6286
  + `<polyline class="spark-line" points="${line}"/>`
6283
6287
  + `<circle class="spark-dot" cx="${last[0]}" cy="${last[1]}" r="1.8"/>`
6284
6288
  + `</svg>`;
6285
6289
  }
6286
6290
 
6291
+ function sparkSvg(name, status) {
6292
+ // Prefer the server anomaly-score series (real omcp_anomaly_score, last
6293
+ // hour, survives reload). Anomaly scores are 0..1; widen the domain if a
6294
+ // score ever exceeds 1 so spikes aren't clipped.
6295
+ const series = (anomalySpark.series && anomalySpark.series[name]) || [];
6296
+ if (series.length >= 2) {
6297
+ const scores = series.map(p => p.score);
6298
+ const domainMax = Math.max(1, ...scores);
6299
+ const mins = anomalySpark.windowMs ? Math.round(anomalySpark.windowMs/60000) : 60;
6300
+ return drawSpark(scores, status, domainMax, `anomaly score · ${series.length} pts · last ${mins}m`);
6301
+ }
6302
+ // Fallback: client-side live health-score trend (0..100).
6303
+ return drawSpark(scoreHistory[name] || [], status, 100, 'live score trend');
6304
+ }
6305
+
6287
6306
  async function loadHealthData() {
6288
6307
  try {
6289
- healthData=await(await fetch('/api/health')).json();
6308
+ // Fetch health + the server-side anomaly-score sparkline data in
6309
+ // parallel. The sparkline endpoint is best-effort: if it fails we
6310
+ // simply fall back to the client-side live-score trend.
6311
+ const [h, spark] = await Promise.all([
6312
+ fetch('/api/health').then(r=>r.json()),
6313
+ fetch('/api/health/anomaly-sparklines').then(r=>r.ok?r.json():null).catch(()=>null),
6314
+ ]);
6315
+ healthData=h;
6316
+ if (spark && spark.series) anomalySpark = spark;
6290
6317
  renderHealthCards();
6291
6318
  } catch(e){ document.getElementById('health-cards').innerHTML='<div class="empty">Failed to load health data.</div>'; }
6292
6319
  if(!healthInterval) healthInterval=setInterval(()=>{ if(document.getElementById('page-health').classList.contains('active')) loadHealthData(); },15000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thotischner/observability-mcp",
3
- "version": "3.0.1",
3
+ "version": "3.1.1",
4
4
  "description": "Unified observability gateway for AI agents — one MCP server for Prometheus, Loki, and any backend",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",