@thotischner/observability-mcp 1.1.0 → 1.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.
@@ -30,13 +30,14 @@ export class LokiConnector {
30
30
  async healthCheck() {
31
31
  const start = Date.now();
32
32
  try {
33
- const res = await fetch(`${this.baseUrl}/ready`, this.fetchOptions());
34
- const text = await res.text();
35
- const isReady = res.ok && text.trim() === "ready";
33
+ // Use the labels query API instead of /ready: managed Loki (Grafana
34
+ // Cloud, etc.) does not expose the operational health endpoint.
35
+ // /loki/api/v1/labels returns 200 with auth on any reachable Loki.
36
+ const res = await fetch(`${this.baseUrl}/loki/api/v1/labels`, this.fetchOptions());
36
37
  return {
37
- status: isReady ? "up" : "down",
38
+ status: res.ok ? "up" : "down",
38
39
  latencyMs: Date.now() - start,
39
- message: isReady ? "Loki is ready" : `HTTP ${res.status}: ${text}`,
40
+ message: res.ok ? "Loki is ready" : `HTTP ${res.status}`,
40
41
  };
41
42
  }
42
43
  catch (err) {
@@ -15,6 +15,7 @@ export declare class PrometheusConnector implements ObservabilityConnector {
15
15
  healthCheck(): Promise<ConnectorHealth>;
16
16
  disconnect(): Promise<void>;
17
17
  listServices(): Promise<ServiceInfo[]>;
18
+ private listServicesFromJobLabel;
18
19
  listAvailableMetrics(_service: string): Promise<MetricInfo[]>;
19
20
  queryMetrics(params: MetricQuery): Promise<MetricResult>;
20
21
  private buildQuery;
@@ -38,7 +38,11 @@ export class PrometheusConnector {
38
38
  async healthCheck() {
39
39
  const start = Date.now();
40
40
  try {
41
- const res = await fetch(`${this.baseUrl}/-/ready`, this.fetchOptions());
41
+ // Use the query API instead of /-/ready: works on both OSS Prometheus
42
+ // and managed offerings (Grafana Cloud / Mimir, AWS Managed Prometheus,
43
+ // Chronosphere) which do not expose the operational health endpoint.
44
+ // 'up' is a synthetic metric guaranteed to exist on any Prometheus.
45
+ const res = await fetch(`${this.baseUrl}/api/v1/query?query=up`, this.fetchOptions());
42
46
  return {
43
47
  status: res.ok ? "up" : "down",
44
48
  latencyMs: Date.now() - start,
@@ -51,21 +55,47 @@ export class PrometheusConnector {
51
55
  }
52
56
  async disconnect() { }
53
57
  async listServices() {
54
- const data = await this.apiGet("/api/v1/targets");
55
- const targets = data?.data?.activeTargets || [];
56
- const services = new Map();
57
- for (const t of targets) {
58
- const name = t.labels?.service || t.labels?.job || t.discoveredLabels?.__address__ || "unknown";
59
- if (!services.has(name)) {
60
- services.set(name, {
61
- name,
62
- source: this.name,
63
- signalType: "metrics",
64
- labels: t.labels,
65
- });
58
+ // Prefer /api/v1/targets — gives full label detail incl. service/job/address.
59
+ // Managed Prometheus (Mimir, AMP, Chronosphere) returns 404 on this path
60
+ // because targets are an operational concept of the OSS scraper. Fall back
61
+ // to /api/v1/label/job/values, which is the canonical query-time source
62
+ // for service names and is supported everywhere.
63
+ try {
64
+ const data = await this.apiGet("/api/v1/targets");
65
+ const targets = data?.data?.activeTargets || [];
66
+ if (targets.length === 0) {
67
+ return await this.listServicesFromJobLabel();
68
+ }
69
+ const services = new Map();
70
+ for (const t of targets) {
71
+ const name = t.labels?.service || t.labels?.job || t.discoveredLabels?.__address__ || "unknown";
72
+ if (!services.has(name)) {
73
+ services.set(name, {
74
+ name,
75
+ source: this.name,
76
+ signalType: "metrics",
77
+ labels: t.labels,
78
+ });
79
+ }
80
+ }
81
+ return Array.from(services.values());
82
+ }
83
+ catch (err) {
84
+ const msg = String(err);
85
+ if (msg.includes("404")) {
86
+ return await this.listServicesFromJobLabel();
66
87
  }
88
+ throw err;
67
89
  }
68
- return Array.from(services.values());
90
+ }
91
+ async listServicesFromJobLabel() {
92
+ const data = await this.apiGet("/api/v1/label/job/values");
93
+ const jobs = data?.data || [];
94
+ return jobs.map((name) => ({
95
+ name,
96
+ source: this.name,
97
+ signalType: "metrics",
98
+ }));
69
99
  }
70
100
  async listAvailableMetrics(_service) {
71
101
  const data = await this.apiGet("/api/v1/metadata");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thotischner/observability-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.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": "MIT",
@@ -9,11 +9,21 @@
9
9
  "type": "git",
10
10
  "url": "https://github.com/ThoTischner/observability-mcp"
11
11
  },
12
- "keywords": ["mcp", "observability", "prometheus", "loki", "model-context-protocol", "anomaly-detection"],
12
+ "keywords": [
13
+ "mcp",
14
+ "observability",
15
+ "prometheus",
16
+ "loki",
17
+ "model-context-protocol",
18
+ "anomaly-detection"
19
+ ],
13
20
  "bin": {
14
21
  "observability-mcp": "./dist/index.js"
15
22
  },
16
- "files": ["dist", "config"],
23
+ "files": [
24
+ "dist",
25
+ "config"
26
+ ],
17
27
  "scripts": {
18
28
  "dev": "tsx watch src/index.ts",
19
29
  "build": "tsc && cp -r src/ui dist/ui",