@thotischner/observability-mcp 1.3.1 → 1.3.2

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.
@@ -2,5 +2,6 @@ import type { Config, GeneralSettings, HealthThresholds } from "../types.js";
2
2
  export declare const DEFAULT_SETTINGS: GeneralSettings;
3
3
  export declare const DEFAULT_HEALTH_THRESHOLDS: HealthThresholds;
4
4
  export declare function substituteEnv(raw: string): string;
5
+ export declare function substituteEnvInTree<T>(node: T): T;
5
6
  export declare function loadConfig(): Config;
6
7
  export declare function saveConfig(config: Config): void;
@@ -34,10 +34,28 @@ export function substituteEnv(raw) {
34
34
  return "";
35
35
  });
36
36
  }
37
+ // Walk the parsed YAML tree and substitute ${VAR} only inside string values.
38
+ // Comments don't survive yaml.load(), so this side-steps the bug where the
39
+ // regex previously fired on `${...}` written in #-prefixed YAML comments.
40
+ export function substituteEnvInTree(node) {
41
+ if (typeof node === "string")
42
+ return substituteEnv(node);
43
+ if (Array.isArray(node))
44
+ return node.map((v) => substituteEnvInTree(v));
45
+ if (node && typeof node === "object") {
46
+ const out = {};
47
+ for (const [k, v] of Object.entries(node)) {
48
+ out[k] = substituteEnvInTree(v);
49
+ }
50
+ return out;
51
+ }
52
+ return node;
53
+ }
37
54
  export function loadConfig() {
38
55
  try {
39
56
  const raw = readFileSync(CONFIG_PATH, "utf-8");
40
- const parsed = yaml.load(substituteEnv(raw));
57
+ const parsedRaw = yaml.load(raw);
58
+ const parsed = substituteEnvInTree(parsedRaw || {});
41
59
  return {
42
60
  sources: parsed?.sources || [],
43
61
  settings: { ...DEFAULT_SETTINGS, ...parsed?.settings },
@@ -167,6 +167,34 @@ sources:
167
167
  delete process.env.A;
168
168
  delete process.env.B;
169
169
  });
170
+ it("does not substitute or warn for ${VAR} inside YAML comments (#17)", async () => {
171
+ delete process.env.VAR;
172
+ const configPath = join(TMP_DIR, "comments.yaml");
173
+ writeFileSync(configPath, `
174
+ # Note: \${VAR}-style substitution is resolved at runtime from .env.
175
+ # Another mention of \${SHOULD_NOT_WARN} in prose.
176
+ sources:
177
+ - name: example
178
+ type: prometheus
179
+ url: http://localhost:9090
180
+ enabled: true
181
+ `);
182
+ process.env.CONFIG_PATH = configPath;
183
+ const warnings = [];
184
+ const origWarn = console.warn;
185
+ console.warn = (msg) => warnings.push(msg);
186
+ try {
187
+ const mod = await import("./loader.js?" + Date.now());
188
+ const config = mod.loadConfig();
189
+ assert.equal(config.sources.length, 1);
190
+ assert.equal(config.sources[0].url, "http://localhost:9090");
191
+ const hits = warnings.filter((w) => w.includes("VAR") || w.includes("SHOULD_NOT_WARN"));
192
+ assert.equal(hits.length, 0, `expected no warnings for comment placeholders, got: ${hits.join("; ")}`);
193
+ }
194
+ finally {
195
+ console.warn = origWarn;
196
+ }
197
+ });
170
198
  it("substitutes inside loaded YAML config", async () => {
171
199
  process.env.GRAFANA_USER = "12345";
172
200
  process.env.GRAFANA_TOKEN = "secret-token";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thotischner/observability-mcp",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
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",