@thotischner/observability-mcp 1.0.0

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 (59) hide show
  1. package/config/sources.yaml +45 -0
  2. package/dist/analysis/anomaly.d.ts +24 -0
  3. package/dist/analysis/anomaly.js +50 -0
  4. package/dist/analysis/anomaly.test.d.ts +1 -0
  5. package/dist/analysis/anomaly.test.js +87 -0
  6. package/dist/analysis/correlator.d.ts +7 -0
  7. package/dist/analysis/correlator.js +31 -0
  8. package/dist/analysis/correlator.test.d.ts +1 -0
  9. package/dist/analysis/correlator.test.js +53 -0
  10. package/dist/analysis/health.d.ts +19 -0
  11. package/dist/analysis/health.js +34 -0
  12. package/dist/analysis/health.test.d.ts +1 -0
  13. package/dist/analysis/health.test.js +70 -0
  14. package/dist/config/loader.d.ts +5 -0
  15. package/dist/config/loader.js +81 -0
  16. package/dist/config/loader.test.d.ts +1 -0
  17. package/dist/config/loader.test.js +163 -0
  18. package/dist/connectors/interface.d.ts +17 -0
  19. package/dist/connectors/interface.js +1 -0
  20. package/dist/connectors/loki.d.ts +25 -0
  21. package/dist/connectors/loki.js +182 -0
  22. package/dist/connectors/loki.test.d.ts +1 -0
  23. package/dist/connectors/loki.test.js +111 -0
  24. package/dist/connectors/prometheus.d.ts +28 -0
  25. package/dist/connectors/prometheus.js +196 -0
  26. package/dist/connectors/prometheus.test.d.ts +1 -0
  27. package/dist/connectors/prometheus.test.js +103 -0
  28. package/dist/connectors/registry.d.ts +18 -0
  29. package/dist/connectors/registry.js +90 -0
  30. package/dist/connectors/registry.test.d.ts +1 -0
  31. package/dist/connectors/registry.test.js +93 -0
  32. package/dist/connectors/tls.d.ts +7 -0
  33. package/dist/connectors/tls.js +25 -0
  34. package/dist/connectors/tls.test.d.ts +1 -0
  35. package/dist/connectors/tls.test.js +99 -0
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.js +421 -0
  38. package/dist/tools/detect-anomalies.d.ts +33 -0
  39. package/dist/tools/detect-anomalies.js +137 -0
  40. package/dist/tools/get-service-health.d.ts +25 -0
  41. package/dist/tools/get-service-health.js +111 -0
  42. package/dist/tools/handlers.test.d.ts +1 -0
  43. package/dist/tools/handlers.test.js +138 -0
  44. package/dist/tools/list-services.d.ts +22 -0
  45. package/dist/tools/list-services.js +57 -0
  46. package/dist/tools/list-sources.d.ts +15 -0
  47. package/dist/tools/list-sources.js +27 -0
  48. package/dist/tools/query-logs.d.ts +49 -0
  49. package/dist/tools/query-logs.js +93 -0
  50. package/dist/tools/query-metrics.d.ts +44 -0
  51. package/dist/tools/query-metrics.js +91 -0
  52. package/dist/tools/validation.d.ts +17 -0
  53. package/dist/tools/validation.js +45 -0
  54. package/dist/tools/validation.test.d.ts +1 -0
  55. package/dist/tools/validation.test.js +84 -0
  56. package/dist/types.d.ts +171 -0
  57. package/dist/types.js +1 -0
  58. package/dist/ui/index.html +675 -0
  59. package/package.json +35 -0
@@ -0,0 +1,45 @@
1
+ const DURATION_RE = /^\d+[mhd]$/;
2
+ const SAFE_LABEL_RE = /^[a-zA-Z0-9_\-.:]+$/;
3
+ export function validateDuration(duration) {
4
+ if (!DURATION_RE.test(duration)) {
5
+ return `Invalid duration "${duration}". Expected format: <number><unit> where unit is m (minutes), h (hours), or d (days). Examples: 5m, 1h, 24h, 7d`;
6
+ }
7
+ return null;
8
+ }
9
+ export function validateMetricName(metric, registry) {
10
+ const allMetrics = new Set();
11
+ for (const c of registry.getBySignal("metrics")) {
12
+ for (const m of c.getMetrics())
13
+ allMetrics.add(m.name);
14
+ }
15
+ if (allMetrics.size > 0 && !allMetrics.has(metric)) {
16
+ return `Unknown metric "${metric}". Available metrics: ${[...allMetrics].join(", ")}`;
17
+ }
18
+ return null;
19
+ }
20
+ /**
21
+ * Sanitize a label value for use in PromQL/LogQL queries.
22
+ * Only allows alphanumeric, hyphens, underscores, dots, colons.
23
+ * Rejects anything that could be used for injection.
24
+ */
25
+ export function sanitizeLabelValue(value) {
26
+ if (!value || value.length > 128) {
27
+ return null;
28
+ }
29
+ if (!SAFE_LABEL_RE.test(value)) {
30
+ return null;
31
+ }
32
+ return value;
33
+ }
34
+ export function validateServiceName(service) {
35
+ if (!sanitizeLabelValue(service)) {
36
+ return `Invalid service name "${service}". Only alphanumeric characters, hyphens, underscores, dots, and colons are allowed (max 128 chars).`;
37
+ }
38
+ return null;
39
+ }
40
+ export function errorResponse(message) {
41
+ return {
42
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
43
+ isError: true,
44
+ };
45
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,84 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { validateDuration, validateServiceName, sanitizeLabelValue, errorResponse } from "./validation.js";
4
+ describe("validateDuration", () => {
5
+ it("accepts valid durations", () => {
6
+ assert.equal(validateDuration("5m"), null);
7
+ assert.equal(validateDuration("1h"), null);
8
+ assert.equal(validateDuration("24h"), null);
9
+ assert.equal(validateDuration("7d"), null);
10
+ assert.equal(validateDuration("30m"), null);
11
+ assert.equal(validateDuration("365d"), null);
12
+ });
13
+ it("rejects invalid durations", () => {
14
+ assert.ok(validateDuration("") !== null);
15
+ assert.ok(validateDuration("5") !== null);
16
+ assert.ok(validateDuration("m") !== null);
17
+ assert.ok(validateDuration("5s") !== null); // seconds not supported
18
+ assert.ok(validateDuration("5min") !== null); // only single-char units
19
+ assert.ok(validateDuration("-5m") !== null); // no negative
20
+ assert.ok(validateDuration("5.5m") !== null); // no decimals
21
+ assert.ok(validateDuration("5m 1h") !== null); // no spaces
22
+ assert.ok(validateDuration("abc") !== null);
23
+ });
24
+ it("returns helpful error message", () => {
25
+ const err = validateDuration("5s");
26
+ assert.ok(err.includes("Invalid duration"));
27
+ assert.ok(err.includes("5m")); // example in message
28
+ });
29
+ });
30
+ describe("sanitizeLabelValue", () => {
31
+ it("accepts valid label values", () => {
32
+ assert.equal(sanitizeLabelValue("api-gateway"), "api-gateway");
33
+ assert.equal(sanitizeLabelValue("payment_service"), "payment_service");
34
+ assert.equal(sanitizeLabelValue("svc.prod.us-east-1"), "svc.prod.us-east-1");
35
+ assert.equal(sanitizeLabelValue("host:8080"), "host:8080");
36
+ assert.equal(sanitizeLabelValue("a"), "a");
37
+ });
38
+ it("rejects empty string", () => {
39
+ assert.equal(sanitizeLabelValue(""), null);
40
+ });
41
+ it("rejects strings over 128 characters", () => {
42
+ assert.equal(sanitizeLabelValue("a".repeat(129)), null);
43
+ });
44
+ it("accepts exactly 128 characters", () => {
45
+ assert.equal(sanitizeLabelValue("a".repeat(128)), "a".repeat(128));
46
+ });
47
+ it("rejects injection attempts", () => {
48
+ assert.equal(sanitizeLabelValue('service"}[5m])'), null); // PromQL injection
49
+ assert.equal(sanitizeLabelValue("service`rm -rf`"), null); // command injection
50
+ assert.equal(sanitizeLabelValue("svc;drop"), null); // semicolon
51
+ assert.equal(sanitizeLabelValue("svc name"), null); // spaces
52
+ assert.equal(sanitizeLabelValue('svc"name'), null); // quotes
53
+ assert.equal(sanitizeLabelValue("svc'name"), null); // single quotes
54
+ assert.equal(sanitizeLabelValue("svc{name}"), null); // braces
55
+ assert.equal(sanitizeLabelValue("svc|name"), null); // pipe
56
+ });
57
+ });
58
+ describe("validateServiceName", () => {
59
+ it("returns null for valid service names", () => {
60
+ assert.equal(validateServiceName("api-gateway"), null);
61
+ assert.equal(validateServiceName("payment-service"), null);
62
+ assert.equal(validateServiceName("my_svc_v2"), null);
63
+ });
64
+ it("returns error for invalid service names", () => {
65
+ assert.ok(validateServiceName("") !== null);
66
+ assert.ok(validateServiceName("svc name") !== null);
67
+ assert.ok(validateServiceName('svc"injection') !== null);
68
+ });
69
+ it("returns helpful error message", () => {
70
+ const err = validateServiceName("bad name!");
71
+ assert.ok(err.includes("Invalid service name"));
72
+ assert.ok(err.includes("alphanumeric"));
73
+ });
74
+ });
75
+ describe("errorResponse", () => {
76
+ it("returns MCP error format", () => {
77
+ const res = errorResponse("something went wrong");
78
+ assert.equal(res.isError, true);
79
+ assert.equal(res.content.length, 1);
80
+ assert.equal(res.content[0].type, "text");
81
+ const parsed = JSON.parse(res.content[0].text);
82
+ assert.equal(parsed.error, "something went wrong");
83
+ });
84
+ });
@@ -0,0 +1,171 @@
1
+ export type SignalType = "metrics" | "logs" | "traces";
2
+ export type HealthStatus = "healthy" | "degraded" | "critical";
3
+ export type Trend = "rising" | "falling" | "stable";
4
+ export type AnomalySeverity = "low" | "medium" | "high";
5
+ /** A metric definition tied to a specific connector type's query language */
6
+ export interface MetricDefinition {
7
+ name: string;
8
+ query: string;
9
+ unit: string;
10
+ description: string;
11
+ }
12
+ export interface SourceAuth {
13
+ type: "none" | "basic" | "bearer";
14
+ username?: string;
15
+ password?: string;
16
+ token?: string;
17
+ }
18
+ export interface SourceTls {
19
+ skipVerify?: boolean;
20
+ caCert?: string;
21
+ clientCert?: string;
22
+ clientKey?: string;
23
+ }
24
+ export interface SourceConfig {
25
+ name: string;
26
+ type: string;
27
+ url: string;
28
+ enabled: boolean;
29
+ auth?: SourceAuth;
30
+ tls?: SourceTls;
31
+ /** @deprecated Use tls.skipVerify instead */
32
+ tlsSkipVerify?: boolean;
33
+ metrics?: MetricDefinition[];
34
+ }
35
+ export interface GeneralSettings {
36
+ checkIntervalMs: number;
37
+ defaultSensitivity: "low" | "medium" | "high";
38
+ }
39
+ export interface HealthThresholds {
40
+ weights: {
41
+ errorRate: number;
42
+ latency: number;
43
+ cpu: number;
44
+ logErrors: number;
45
+ };
46
+ cpu: {
47
+ good: number;
48
+ warn: number;
49
+ crit: number;
50
+ };
51
+ errorRate: {
52
+ good: number;
53
+ warn: number;
54
+ crit: number;
55
+ };
56
+ latencyP99: {
57
+ good: number;
58
+ warn: number;
59
+ crit: number;
60
+ };
61
+ logErrors: {
62
+ good: number;
63
+ warn: number;
64
+ crit: number;
65
+ };
66
+ statusBoundaries: {
67
+ healthy: number;
68
+ degraded: number;
69
+ };
70
+ }
71
+ export interface Config {
72
+ sources: SourceConfig[];
73
+ settings: GeneralSettings;
74
+ healthThresholds: HealthThresholds;
75
+ }
76
+ export interface ConnectorHealth {
77
+ status: "up" | "down";
78
+ latencyMs: number;
79
+ message?: string;
80
+ }
81
+ export interface ServiceInfo {
82
+ name: string;
83
+ source: string;
84
+ signalType: SignalType;
85
+ labels?: Record<string, string>;
86
+ }
87
+ export interface MetricInfo {
88
+ name: string;
89
+ type: string;
90
+ help: string;
91
+ unit?: string;
92
+ }
93
+ export interface MetricQuery {
94
+ service: string;
95
+ metric: string;
96
+ duration: string;
97
+ step?: string;
98
+ }
99
+ export interface LogQuery {
100
+ service: string;
101
+ query?: string;
102
+ duration: string;
103
+ limit?: number;
104
+ level?: string;
105
+ }
106
+ export interface DataPoint {
107
+ timestamp: string;
108
+ value: number;
109
+ }
110
+ export interface MetricSummary {
111
+ current: number;
112
+ average: number;
113
+ min: number;
114
+ max: number;
115
+ trend: Trend;
116
+ }
117
+ export interface MetricResult {
118
+ source: string;
119
+ service: string;
120
+ metric: string;
121
+ unit: string;
122
+ values: DataPoint[];
123
+ summary: MetricSummary;
124
+ }
125
+ export interface LogEntry {
126
+ timestamp: string;
127
+ level: string;
128
+ message: string;
129
+ labels: Record<string, string>;
130
+ }
131
+ export interface LogSummary {
132
+ total: number;
133
+ errorCount: number;
134
+ warnCount: number;
135
+ topPatterns: string[];
136
+ }
137
+ export interface LogResult {
138
+ source: string;
139
+ service: string;
140
+ entries: LogEntry[];
141
+ summary: LogSummary;
142
+ }
143
+ export interface AnomalyReport {
144
+ metric: string;
145
+ severity: AnomalySeverity;
146
+ description: string;
147
+ currentValue: number;
148
+ baselineValue: number;
149
+ deviationPercent: number;
150
+ source: string;
151
+ service: string;
152
+ }
153
+ export interface ServiceHealth {
154
+ service: string;
155
+ status: HealthStatus;
156
+ score: number;
157
+ signals: {
158
+ metrics: {
159
+ cpu: number;
160
+ memory: number;
161
+ errorRate: number;
162
+ latencyP99: number;
163
+ };
164
+ logs: {
165
+ errorRate: number;
166
+ topErrors: string[];
167
+ };
168
+ };
169
+ anomalies: AnomalyReport[];
170
+ correlations: string[];
171
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};