@newrelic/preflight 1.6.11 → 1.6.13
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/dist/config.d.ts +50 -24
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +229 -84
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +35 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +97 -55
- package/dist/index.js.map +1 -1
- package/dist/metrics/context-window-tracker.d.ts.map +1 -1
- package/dist/metrics/context-window-tracker.js +9 -0
- package/dist/metrics/context-window-tracker.js.map +1 -1
- package/dist/metrics/session-tracker.d.ts.map +1 -1
- package/dist/metrics/session-tracker.js +15 -2
- package/dist/metrics/session-tracker.js.map +1 -1
- package/dist/storage/local-store.d.ts +7 -6
- package/dist/storage/local-store.d.ts.map +1 -1
- package/dist/storage/local-store.js +7 -6
- package/dist/storage/local-store.js.map +1 -1
- package/dist/storage/retention.d.ts +1 -0
- package/dist/storage/retention.d.ts.map +1 -1
- package/dist/storage/retention.js +21 -10
- package/dist/storage/retention.js.map +1 -1
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import type { PersonalAlertThresholds } from './alerts/types.js';
|
|
|
6
6
|
export interface McpServerConfig {
|
|
7
7
|
readonly licenseKey?: string;
|
|
8
8
|
readonly accountId?: string;
|
|
9
|
+
/** Config-file schema version. Defaults to 1; see CURRENT_CONFIG_VERSION. */
|
|
10
|
+
readonly configVersion: number;
|
|
9
11
|
readonly appName: string;
|
|
10
12
|
readonly developer: string;
|
|
11
13
|
readonly teamId: string | null;
|
|
@@ -32,31 +34,40 @@ export interface McpServerConfig {
|
|
|
32
34
|
readonly nrApiKey: string | null;
|
|
33
35
|
readonly digestWebhookUrl: string | null;
|
|
34
36
|
readonly digestSchedule: string;
|
|
37
|
+
/** Default: 90. `null` disables retention (only reachable via an explicit `null` in config.json). */
|
|
35
38
|
readonly retainSessionsDays: number | null;
|
|
36
39
|
readonly personalAlertThresholds: PersonalAlertThresholds;
|
|
37
|
-
readonly otlpEndpoint: string | null;
|
|
38
|
-
readonly otlpHeaders: Readonly<Record<string, string>>;
|
|
39
|
-
readonly transport: 'nr-events-api' | 'otlp' | 'both';
|
|
40
40
|
readonly mode: 'cloud' | 'local' | 'both';
|
|
41
41
|
readonly platformTarget?: PlatformTarget;
|
|
42
|
-
/** Enable the local OTLP/HTTP receiver. Default: false. */
|
|
43
|
-
readonly otlpReceiverEnabled: boolean;
|
|
44
|
-
/** Port for the local OTLP/HTTP receiver. Default: 4318. */
|
|
45
|
-
readonly otlpReceiverPort: number;
|
|
46
|
-
/** Bind address for the local OTLP/HTTP receiver. Default: '127.0.0.1'. */
|
|
47
|
-
readonly otlpReceiverBindAddress: string;
|
|
48
42
|
/**
|
|
49
|
-
* OTLP
|
|
50
|
-
*
|
|
51
|
-
*
|
|
43
|
+
* OTLP-related config, grouped to match the `dashboard`/`alerts` nesting
|
|
44
|
+
* precedent. The config-file schema also still accepts the legacy flat
|
|
45
|
+
* top-level keys (`otlpEndpoint`, `otlpReceiverPort`, etc.) for backward
|
|
46
|
+
* compatibility — see `pickOtlpValue()` in loadMcpConfig().
|
|
52
47
|
*/
|
|
53
|
-
readonly
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
readonly otlp: {
|
|
49
|
+
readonly endpoint: string | null;
|
|
50
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
51
|
+
readonly transport: 'nr-events-api' | 'otlp' | 'both';
|
|
52
|
+
/** Enable the local OTLP/HTTP receiver. Default: false. */
|
|
53
|
+
readonly receiverEnabled: boolean;
|
|
54
|
+
/** Port for the local OTLP/HTTP receiver. Default: 4318. */
|
|
55
|
+
readonly receiverPort: number;
|
|
56
|
+
/** Bind address for the local OTLP/HTTP receiver. Default: '127.0.0.1'. */
|
|
57
|
+
readonly receiverBindAddress: string;
|
|
58
|
+
/**
|
|
59
|
+
* OTLP forward endpoint — where to relay received spans.
|
|
60
|
+
* Defaults to New Relic US OTLP endpoint when licenseKey is present.
|
|
61
|
+
* Set to null to disable forwarding (receive and enrich only, then drop).
|
|
62
|
+
*/
|
|
63
|
+
readonly forwardEndpoint: string | null;
|
|
64
|
+
/**
|
|
65
|
+
* HTTP headers added to every OTLP forward request (e.g. `{ 'api-key': licenseKey }`).
|
|
66
|
+
* Defaults to `{ 'api-key': licenseKey }` when licenseKey is present.
|
|
67
|
+
* Configurable via NR_AI_OTLP_FORWARD_HEADERS (comma-separated key=value pairs).
|
|
68
|
+
*/
|
|
69
|
+
readonly forwardHeaders: Readonly<Record<string, string>>;
|
|
70
|
+
};
|
|
60
71
|
readonly dashboard: {
|
|
61
72
|
readonly port: number;
|
|
62
73
|
readonly host: string;
|
|
@@ -78,6 +89,7 @@ export interface McpServerConfig {
|
|
|
78
89
|
export declare const DEFAULT_STORAGE_PATH: string;
|
|
79
90
|
export type PlatformTarget = 'native' | 'wsl-windows-cc' | 'wsl-linux-cc';
|
|
80
91
|
export declare const ConfigFileSchema: z.ZodObject<{
|
|
92
|
+
configVersion: z.ZodOptional<z.ZodNumber>;
|
|
81
93
|
licenseKey: z.ZodOptional<z.ZodString>;
|
|
82
94
|
accountId: z.ZodOptional<z.ZodString>;
|
|
83
95
|
appName: z.ZodOptional<z.ZodString>;
|
|
@@ -116,6 +128,11 @@ export declare const ConfigFileSchema: z.ZodObject<{
|
|
|
116
128
|
otlp: "otlp";
|
|
117
129
|
both: "both";
|
|
118
130
|
}>>;
|
|
131
|
+
otlpReceiverEnabled: z.ZodOptional<z.ZodBoolean>;
|
|
132
|
+
otlpReceiverPort: z.ZodOptional<z.ZodNumber>;
|
|
133
|
+
otlpReceiverBindAddress: z.ZodOptional<z.ZodString>;
|
|
134
|
+
otlpForwardEndpoint: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
135
|
+
otlpForwardHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
119
136
|
mode: z.ZodOptional<z.ZodEnum<{
|
|
120
137
|
both: "both";
|
|
121
138
|
cloud: "cloud";
|
|
@@ -126,11 +143,20 @@ export declare const ConfigFileSchema: z.ZodObject<{
|
|
|
126
143
|
"wsl-windows-cc": "wsl-windows-cc";
|
|
127
144
|
"wsl-linux-cc": "wsl-linux-cc";
|
|
128
145
|
}>>;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
otlp: z.ZodOptional<z.ZodObject<{
|
|
147
|
+
endpoint: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
148
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
149
|
+
transport: z.ZodOptional<z.ZodEnum<{
|
|
150
|
+
"nr-events-api": "nr-events-api";
|
|
151
|
+
otlp: "otlp";
|
|
152
|
+
both: "both";
|
|
153
|
+
}>>;
|
|
154
|
+
receiverEnabled: z.ZodOptional<z.ZodBoolean>;
|
|
155
|
+
receiverPort: z.ZodOptional<z.ZodNumber>;
|
|
156
|
+
receiverBindAddress: z.ZodOptional<z.ZodString>;
|
|
157
|
+
forwardEndpoint: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
158
|
+
forwardHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
159
|
+
}, z.core.$loose>>;
|
|
134
160
|
alerts: z.ZodOptional<z.ZodObject<{
|
|
135
161
|
personal: z.ZodOptional<z.ZodObject<{
|
|
136
162
|
dailyCostUsd: z.ZodOptional<z.ZodNumber>;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAOjE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,cAAc,EAAE,SAAS,cAAc,EAAE,CAAC;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,uBAAuB,EAAE,uBAAuB,CAAC;IAC1D,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
CHANGED
|
@@ -9,8 +9,21 @@ import { REDACTION_PATTERNS as DEFAULT_REDACTION_PATTERNS } from './redaction-pa
|
|
|
9
9
|
import { resolveRecordContent } from './record-content-gate.js';
|
|
10
10
|
const logger = createLogger('mcp-config');
|
|
11
11
|
export const DEFAULT_STORAGE_PATH = resolve(homedir(), '.newrelic-preflight');
|
|
12
|
+
// Applied only when neither the env var nor the config file specify
|
|
13
|
+
// retainSessionsDays at all — an explicit `null` in config.json still means
|
|
14
|
+
// "retention disabled" (see the resolution logic below).
|
|
15
|
+
const DEFAULT_RETAIN_SESSIONS_DAYS = 90;
|
|
16
|
+
/**
|
|
17
|
+
* Bump when a change to config.json's shape is non-additive (a field is
|
|
18
|
+
* renamed, moved, or removed) so a future migration path has something to
|
|
19
|
+
* branch on. Every change to date has been additive — including the `otlp`
|
|
20
|
+
* nested object below, which was introduced as an accept-both addition
|
|
21
|
+
* rather than a breaking rename — so this has never moved past 1.
|
|
22
|
+
*/
|
|
23
|
+
const CURRENT_CONFIG_VERSION = 1;
|
|
12
24
|
export const ConfigFileSchema = z
|
|
13
25
|
.object({
|
|
26
|
+
configVersion: z.number().int().optional(),
|
|
14
27
|
licenseKey: z.string().optional(),
|
|
15
28
|
accountId: z.string().optional(),
|
|
16
29
|
appName: z.string().optional(),
|
|
@@ -37,16 +50,32 @@ export const ConfigFileSchema = z
|
|
|
37
50
|
digestWebhookUrl: z.string().nullable().optional(),
|
|
38
51
|
digestSchedule: z.string().optional(),
|
|
39
52
|
retainSessionsDays: z.number().nullable().optional(),
|
|
53
|
+
// Legacy flat OTLP fields — deprecated in favor of the nested `otlp`
|
|
54
|
+
// object below, but still accepted for backward compatibility (using
|
|
55
|
+
// one logs a deprecation warning; see pickOtlpValue() in loadMcpConfig).
|
|
40
56
|
otlpEndpoint: z.string().nullable().optional(),
|
|
41
57
|
otlpHeaders: z.record(z.string(), z.string()).optional(),
|
|
42
58
|
transport: z.enum(['nr-events-api', 'otlp', 'both']).optional(),
|
|
43
|
-
mode: z.enum(['cloud', 'local', 'both']).optional(),
|
|
44
|
-
platformTarget: z.enum(['native', 'wsl-windows-cc', 'wsl-linux-cc']).optional(),
|
|
45
59
|
otlpReceiverEnabled: z.boolean().optional(),
|
|
46
60
|
otlpReceiverPort: z.number().optional(),
|
|
47
61
|
otlpReceiverBindAddress: z.string().optional(),
|
|
48
62
|
otlpForwardEndpoint: z.string().nullable().optional(),
|
|
49
63
|
otlpForwardHeaders: z.record(z.string(), z.string()).optional(),
|
|
64
|
+
mode: z.enum(['cloud', 'local', 'both']).optional(),
|
|
65
|
+
platformTarget: z.enum(['native', 'wsl-windows-cc', 'wsl-linux-cc']).optional(),
|
|
66
|
+
otlp: z
|
|
67
|
+
.object({
|
|
68
|
+
endpoint: z.string().nullable().optional(),
|
|
69
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
70
|
+
transport: z.enum(['nr-events-api', 'otlp', 'both']).optional(),
|
|
71
|
+
receiverEnabled: z.boolean().optional(),
|
|
72
|
+
receiverPort: z.number().optional(),
|
|
73
|
+
receiverBindAddress: z.string().optional(),
|
|
74
|
+
forwardEndpoint: z.string().nullable().optional(),
|
|
75
|
+
forwardHeaders: z.record(z.string(), z.string()).optional(),
|
|
76
|
+
})
|
|
77
|
+
.passthrough()
|
|
78
|
+
.optional(),
|
|
50
79
|
alerts: z
|
|
51
80
|
.object({
|
|
52
81
|
personal: z
|
|
@@ -161,18 +190,25 @@ function envBool(key, defaultValue) {
|
|
|
161
190
|
return false;
|
|
162
191
|
return defaultValue;
|
|
163
192
|
}
|
|
193
|
+
// Applied to every return path of envInt (not just the parsed-env-var one) so
|
|
194
|
+
// a bounds-checked call site is protected uniformly regardless of whether the
|
|
195
|
+
// value actually came from the env var or from its (often config-file- or
|
|
196
|
+
// CLI-derived) `defaultValue` argument.
|
|
197
|
+
function clampNumber(value, bounds) {
|
|
198
|
+
if (bounds?.min !== undefined && value < bounds.min)
|
|
199
|
+
return bounds.min;
|
|
200
|
+
if (bounds?.max !== undefined && value > bounds.max)
|
|
201
|
+
return bounds.max;
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
164
204
|
function envInt(key, defaultValue, bounds) {
|
|
165
205
|
const val = process.env[key];
|
|
166
206
|
if (val === undefined)
|
|
167
|
-
return defaultValue;
|
|
207
|
+
return clampNumber(defaultValue, bounds);
|
|
168
208
|
const parsed = parseInt(val, 10);
|
|
169
209
|
if (Number.isNaN(parsed))
|
|
170
|
-
return defaultValue;
|
|
171
|
-
|
|
172
|
-
return bounds.min;
|
|
173
|
-
if (bounds?.max !== undefined && parsed > bounds.max)
|
|
174
|
-
return bounds.max;
|
|
175
|
-
return parsed;
|
|
210
|
+
return clampNumber(defaultValue, bounds);
|
|
211
|
+
return clampNumber(parsed, bounds);
|
|
176
212
|
}
|
|
177
213
|
function envLogLevel(key, defaultValue) {
|
|
178
214
|
const val = process.env[key]?.toLowerCase();
|
|
@@ -255,6 +291,20 @@ function parseOtlpHeaders(headerString) {
|
|
|
255
291
|
}
|
|
256
292
|
return result;
|
|
257
293
|
}
|
|
294
|
+
// Resolves a config-file OTLP field: the new nested `otlp.X` value wins when
|
|
295
|
+
// present; otherwise falls back to the legacy flat top-level key, recording
|
|
296
|
+
// its name in `used` so the caller can warn about exactly the legacy keys
|
|
297
|
+
// that were actually consulted (not merely present-but-shadowed by a nested
|
|
298
|
+
// value for the same field).
|
|
299
|
+
function pickOtlpValue(nested, flat, legacyKey, used) {
|
|
300
|
+
if (nested !== undefined)
|
|
301
|
+
return nested;
|
|
302
|
+
if (flat !== undefined) {
|
|
303
|
+
used.push(legacyKey);
|
|
304
|
+
return flat;
|
|
305
|
+
}
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
258
308
|
/**
|
|
259
309
|
* Validate the alerts.rulesPath value. The path is read from the user's
|
|
260
310
|
* config file or NR_AI_ALERTS_RULES_PATH env var; both are user-controlled
|
|
@@ -294,6 +344,21 @@ function validateRulesPath(rawPath, storagePath) {
|
|
|
294
344
|
}
|
|
295
345
|
return resolved;
|
|
296
346
|
}
|
|
347
|
+
// Config-file-sourced numeric fields (budgets, retention) validate positivity
|
|
348
|
+
// only on the env-var path today — a negative/zero value in config.json
|
|
349
|
+
// silently disables the guardrail with no warning, unlike every other
|
|
350
|
+
// malformed-input path in this file (see validateRulesPath, dashboard.host,
|
|
351
|
+
// parseProxyUpstreams). Callers decide their own fallback value; this only
|
|
352
|
+
// validates and logs.
|
|
353
|
+
function validateFilePositiveNumber(value, fieldName) {
|
|
354
|
+
if (Number.isFinite(value) && value > 0)
|
|
355
|
+
return true;
|
|
356
|
+
logger.warn(`${fieldName} in config file must be a positive number — ignoring value`, {
|
|
357
|
+
fieldName,
|
|
358
|
+
value,
|
|
359
|
+
});
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
297
362
|
function parseProxyUpstreams(envValue, fileValue) {
|
|
298
363
|
// Env var takes precedence (JSON string)
|
|
299
364
|
if (envValue) {
|
|
@@ -363,6 +428,16 @@ export function loadMcpConfig(cliOptions) {
|
|
|
363
428
|
'antiPatternCountMax',
|
|
364
429
|
]);
|
|
365
430
|
const knownDashboardKeys = new Set(['port', 'host', 'openOnStart']);
|
|
431
|
+
const knownOtlpKeys = new Set([
|
|
432
|
+
'endpoint',
|
|
433
|
+
'headers',
|
|
434
|
+
'transport',
|
|
435
|
+
'receiverEnabled',
|
|
436
|
+
'receiverPort',
|
|
437
|
+
'receiverBindAddress',
|
|
438
|
+
'forwardEndpoint',
|
|
439
|
+
'forwardHeaders',
|
|
440
|
+
]);
|
|
366
441
|
if (file.alerts && typeof file.alerts === 'object') {
|
|
367
442
|
const alerts = file.alerts;
|
|
368
443
|
const unknownAlertsKeys = Object.keys(alerts).filter((k) => !knownAlertsKeys.has(k));
|
|
@@ -393,6 +468,15 @@ export function loadMcpConfig(cliOptions) {
|
|
|
393
468
|
});
|
|
394
469
|
}
|
|
395
470
|
}
|
|
471
|
+
if (file.otlp && typeof file.otlp === 'object') {
|
|
472
|
+
const unknownOtlpKeys = Object.keys(file.otlp).filter((k) => !knownOtlpKeys.has(k));
|
|
473
|
+
if (unknownOtlpKeys.length > 0) {
|
|
474
|
+
logger.warn('Unknown keys in otlp config (ignored)', {
|
|
475
|
+
unknownFields: unknownOtlpKeys,
|
|
476
|
+
path: configFilePath,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
396
480
|
// --- Resolve mode early so we can gate licenseKey/accountId requirements ---
|
|
397
481
|
// File mode is already validated by the zod schema in loadConfigFile.
|
|
398
482
|
const VALID_MODES = ['cloud', 'local', 'both'];
|
|
@@ -442,6 +526,7 @@ export function loadMcpConfig(cliOptions) {
|
|
|
442
526
|
const config = {
|
|
443
527
|
licenseKey,
|
|
444
528
|
accountId,
|
|
529
|
+
configVersion: typeof file.configVersion === 'number' ? file.configVersion : CURRENT_CONFIG_VERSION,
|
|
445
530
|
appName: process.env.NEW_RELIC_AI_MCP_APP_NAME ??
|
|
446
531
|
(typeof file.appName === 'string' ? file.appName : 'preflight'),
|
|
447
532
|
model: process.env.NEW_RELIC_AI_MODEL ??
|
|
@@ -472,7 +557,11 @@ export function loadMcpConfig(cliOptions) {
|
|
|
472
557
|
if (Number.isFinite(v) && v > 0)
|
|
473
558
|
return v;
|
|
474
559
|
}
|
|
475
|
-
|
|
560
|
+
if (typeof file.sessionBudgetUsd !== 'number')
|
|
561
|
+
return null;
|
|
562
|
+
return validateFilePositiveNumber(file.sessionBudgetUsd, 'sessionBudgetUsd')
|
|
563
|
+
? file.sessionBudgetUsd
|
|
564
|
+
: null;
|
|
476
565
|
})(),
|
|
477
566
|
dailyBudgetUsd: (() => {
|
|
478
567
|
const raw = process.env.NEW_RELIC_AI_DAILY_BUDGET_USD;
|
|
@@ -481,7 +570,11 @@ export function loadMcpConfig(cliOptions) {
|
|
|
481
570
|
if (Number.isFinite(v) && v > 0)
|
|
482
571
|
return v;
|
|
483
572
|
}
|
|
484
|
-
|
|
573
|
+
if (typeof file.dailyBudgetUsd !== 'number')
|
|
574
|
+
return null;
|
|
575
|
+
return validateFilePositiveNumber(file.dailyBudgetUsd, 'dailyBudgetUsd')
|
|
576
|
+
? file.dailyBudgetUsd
|
|
577
|
+
: null;
|
|
485
578
|
})(),
|
|
486
579
|
weeklyBudgetUsd: (() => {
|
|
487
580
|
const raw = process.env.NEW_RELIC_AI_WEEKLY_BUDGET_USD;
|
|
@@ -490,13 +583,20 @@ export function loadMcpConfig(cliOptions) {
|
|
|
490
583
|
if (Number.isFinite(v) && v > 0)
|
|
491
584
|
return v;
|
|
492
585
|
}
|
|
493
|
-
|
|
586
|
+
if (typeof file.weeklyBudgetUsd !== 'number')
|
|
587
|
+
return null;
|
|
588
|
+
return validateFilePositiveNumber(file.weeklyBudgetUsd, 'weeklyBudgetUsd')
|
|
589
|
+
? file.weeklyBudgetUsd
|
|
590
|
+
: null;
|
|
494
591
|
})(),
|
|
495
|
-
|
|
592
|
+
// clampNumber wraps the whole expression, not just envInt's own bounds
|
|
593
|
+
// param, because cliOptions?.port short-circuits envInt (and its
|
|
594
|
+
// clamping) entirely when a CLI value is supplied.
|
|
595
|
+
port: clampNumber(cliOptions?.port ??
|
|
496
596
|
envInt('NEW_RELIC_AI_MCP_PORT', typeof file.port === 'number' ? file.port : 9847, {
|
|
497
597
|
min: 1,
|
|
498
598
|
max: 65535,
|
|
499
|
-
}),
|
|
599
|
+
}), { min: 1, max: 65535 }),
|
|
500
600
|
logLevel: cliOptions?.logLevel ??
|
|
501
601
|
envLogLevel('NEW_RELIC_AI_MCP_LOG_LEVEL', typeof file.logLevel === 'string' &&
|
|
502
602
|
['debug', 'info', 'warn', 'error'].includes(file.logLevel)
|
|
@@ -517,83 +617,128 @@ export function loadMcpConfig(cliOptions) {
|
|
|
517
617
|
if (Number.isFinite(v) && v > 0)
|
|
518
618
|
return v;
|
|
519
619
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
return typeof file.otlpHeaders === 'object' && file.otlpHeaders !== null
|
|
529
|
-
? file.otlpHeaders
|
|
530
|
-
: {};
|
|
531
|
-
})(),
|
|
532
|
-
transport: process.env.NEW_RELIC_AI_TRANSPORT === 'otlp'
|
|
533
|
-
? 'otlp'
|
|
534
|
-
: process.env.NEW_RELIC_AI_TRANSPORT === 'both'
|
|
535
|
-
? 'both'
|
|
536
|
-
: typeof file.transport === 'string' &&
|
|
537
|
-
(file.transport === 'otlp' || file.transport === 'both')
|
|
538
|
-
? file.transport
|
|
539
|
-
: 'nr-events-api',
|
|
540
|
-
mode,
|
|
541
|
-
otlpReceiverEnabled: envBool('NR_AI_OTLP_RECEIVER_ENABLED', typeof file.otlpReceiverEnabled === 'boolean' ? file.otlpReceiverEnabled : false),
|
|
542
|
-
otlpReceiverPort: envInt('NR_AI_OTLP_RECEIVER_PORT', typeof file.otlpReceiverPort === 'number' ? file.otlpReceiverPort : 4318, { min: 1, max: 65535 }),
|
|
543
|
-
otlpReceiverBindAddress: (() => {
|
|
544
|
-
const envVal = process.env.NR_AI_OTLP_RECEIVER_BIND_ADDRESS;
|
|
545
|
-
if (envVal !== undefined && envVal !== '')
|
|
546
|
-
return envVal;
|
|
547
|
-
if (typeof file.otlpReceiverBindAddress === 'string' && file.otlpReceiverBindAddress !== '') {
|
|
548
|
-
return file.otlpReceiverBindAddress;
|
|
620
|
+
if (typeof file.retainSessionsDays === 'number') {
|
|
621
|
+
// An invalid (non-positive) value falls back to the default rather
|
|
622
|
+
// than null — null already has a distinct, intentional meaning
|
|
623
|
+
// (explicit opt-out, handled below); a typo'd negative number is a
|
|
624
|
+
// mistake, not an opt-out.
|
|
625
|
+
return validateFilePositiveNumber(file.retainSessionsDays, 'retainSessionsDays')
|
|
626
|
+
? file.retainSessionsDays
|
|
627
|
+
: DEFAULT_RETAIN_SESSIONS_DAYS;
|
|
549
628
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
const envVal = process.env.NR_AI_OTLP_FORWARD_ENDPOINT;
|
|
554
|
-
// Default to the NR OTLP endpoint only when not in local mode. Local users
|
|
555
|
-
// may still set the value explicitly via env or config file (e.g. to point
|
|
556
|
-
// at a self-hosted collector); we just don't synthesize a NR default for them.
|
|
557
|
-
const defaultEndpoint = mode !== 'local' && licenseKey !== undefined ? 'https://otlp.nr-data.net' : null;
|
|
558
|
-
const endpoint = envVal !== undefined
|
|
559
|
-
? envVal || null
|
|
560
|
-
: typeof file.otlpForwardEndpoint === 'string'
|
|
561
|
-
? file.otlpForwardEndpoint || null
|
|
562
|
-
: defaultEndpoint;
|
|
563
|
-
if (endpoint === null)
|
|
629
|
+
// Explicit `null` in the config file is a deliberate opt-out — distinct
|
|
630
|
+
// from the key being absent entirely, which falls through to the default.
|
|
631
|
+
if (file.retainSessionsDays === null)
|
|
564
632
|
return null;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
633
|
+
return DEFAULT_RETAIN_SESSIONS_DAYS;
|
|
634
|
+
})(),
|
|
635
|
+
mode,
|
|
636
|
+
// The 8 OTLP-related fields are grouped here (matching the dashboard/alerts
|
|
637
|
+
// nesting precedent). Each still accepts the legacy flat top-level key for
|
|
638
|
+
// backward compatibility — pickOtlpValue() prefers the nested `otlp.X`
|
|
639
|
+
// value and records which legacy keys were actually consulted so exactly
|
|
640
|
+
// those can be named in a single deprecation warning below.
|
|
641
|
+
otlp: (() => {
|
|
642
|
+
const fileOtlp = file.otlp ?? {};
|
|
643
|
+
const usedLegacyOtlpKeys = [];
|
|
644
|
+
const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT ??
|
|
645
|
+
pickOtlpValue(typeof fileOtlp.endpoint === 'string' ? fileOtlp.endpoint : undefined, typeof file.otlpEndpoint === 'string' ? file.otlpEndpoint : undefined, 'otlpEndpoint', usedLegacyOtlpKeys) ??
|
|
646
|
+
null;
|
|
647
|
+
const headers = (() => {
|
|
648
|
+
const envValue = process.env.OTEL_EXPORTER_OTLP_HEADERS;
|
|
649
|
+
if (envValue)
|
|
650
|
+
return parseOtlpHeaders(envValue);
|
|
651
|
+
return (pickOtlpValue(typeof fileOtlp.headers === 'object' && fileOtlp.headers !== null
|
|
652
|
+
? fileOtlp.headers
|
|
653
|
+
: undefined, typeof file.otlpHeaders === 'object' && file.otlpHeaders !== null
|
|
654
|
+
? file.otlpHeaders
|
|
655
|
+
: undefined, 'otlpHeaders', usedLegacyOtlpKeys) ?? {});
|
|
656
|
+
})();
|
|
657
|
+
const transportValue = process.env.NEW_RELIC_AI_TRANSPORT === 'otlp'
|
|
658
|
+
? 'otlp'
|
|
659
|
+
: process.env.NEW_RELIC_AI_TRANSPORT === 'both'
|
|
660
|
+
? 'both'
|
|
661
|
+
: (pickOtlpValue(fileOtlp.transport === 'otlp' || fileOtlp.transport === 'both'
|
|
662
|
+
? fileOtlp.transport
|
|
663
|
+
: undefined, file.transport === 'otlp' || file.transport === 'both' ? file.transport : undefined, 'transport', usedLegacyOtlpKeys) ?? 'nr-events-api');
|
|
664
|
+
const receiverEnabled = envBool('NR_AI_OTLP_RECEIVER_ENABLED', pickOtlpValue(typeof fileOtlp.receiverEnabled === 'boolean' ? fileOtlp.receiverEnabled : undefined, typeof file.otlpReceiverEnabled === 'boolean' ? file.otlpReceiverEnabled : undefined, 'otlpReceiverEnabled', usedLegacyOtlpKeys) ?? false);
|
|
665
|
+
const receiverPort = envInt('NR_AI_OTLP_RECEIVER_PORT', pickOtlpValue(typeof fileOtlp.receiverPort === 'number' ? fileOtlp.receiverPort : undefined, typeof file.otlpReceiverPort === 'number' ? file.otlpReceiverPort : undefined, 'otlpReceiverPort', usedLegacyOtlpKeys) ?? 4318, { min: 1, max: 65535 });
|
|
666
|
+
const receiverBindAddress = (() => {
|
|
667
|
+
const envVal = process.env.NR_AI_OTLP_RECEIVER_BIND_ADDRESS;
|
|
668
|
+
if (envVal !== undefined && envVal !== '')
|
|
669
|
+
return envVal;
|
|
670
|
+
return (pickOtlpValue(typeof fileOtlp.receiverBindAddress === 'string' && fileOtlp.receiverBindAddress !== ''
|
|
671
|
+
? fileOtlp.receiverBindAddress
|
|
672
|
+
: undefined, typeof file.otlpReceiverBindAddress === 'string' && file.otlpReceiverBindAddress !== ''
|
|
673
|
+
? file.otlpReceiverBindAddress
|
|
674
|
+
: undefined, 'otlpReceiverBindAddress', usedLegacyOtlpKeys) ?? '127.0.0.1');
|
|
675
|
+
})();
|
|
676
|
+
const forwardEndpoint = (() => {
|
|
677
|
+
const envVal = process.env.NR_AI_OTLP_FORWARD_ENDPOINT;
|
|
678
|
+
// Default to the NR OTLP endpoint only when not in local mode. Local users
|
|
679
|
+
// may still set the value explicitly via env or config file (e.g. to point
|
|
680
|
+
// at a self-hosted collector); we just don't synthesize a NR default for them.
|
|
681
|
+
const defaultEndpoint = mode !== 'local' && licenseKey !== undefined ? 'https://otlp.nr-data.net' : null;
|
|
682
|
+
const fileValue = pickOtlpValue(typeof fileOtlp.forwardEndpoint === 'string' ? fileOtlp.forwardEndpoint : undefined, typeof file.otlpForwardEndpoint === 'string' ? file.otlpForwardEndpoint : undefined, 'otlpForwardEndpoint', usedLegacyOtlpKeys);
|
|
683
|
+
const endpoint = envVal !== undefined
|
|
684
|
+
? envVal || null
|
|
685
|
+
: fileValue !== undefined
|
|
686
|
+
? fileValue || null
|
|
687
|
+
: defaultEndpoint;
|
|
688
|
+
if (endpoint === null)
|
|
689
|
+
return null;
|
|
690
|
+
try {
|
|
691
|
+
const url = new URL(endpoint);
|
|
692
|
+
if (url.protocol === 'https:')
|
|
693
|
+
return endpoint;
|
|
694
|
+
if (url.protocol === 'http:') {
|
|
695
|
+
logger.warn('OTLP forward endpoint uses http:// instead of https:// — TLS not enabled', { endpoint });
|
|
696
|
+
return endpoint;
|
|
697
|
+
}
|
|
698
|
+
logger.warn('OTLP forward endpoint has unexpected protocol', {
|
|
571
699
|
endpoint,
|
|
700
|
+
protocol: url.protocol,
|
|
572
701
|
});
|
|
573
702
|
return endpoint;
|
|
574
703
|
}
|
|
575
|
-
|
|
576
|
-
endpoint,
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
704
|
+
catch (_err) {
|
|
705
|
+
logger.error('OTLP forward endpoint is not a valid URL', { endpoint });
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
})();
|
|
709
|
+
const forwardHeaders = (() => {
|
|
710
|
+
const envValue = process.env.NR_AI_OTLP_FORWARD_HEADERS;
|
|
711
|
+
if (envValue !== undefined)
|
|
712
|
+
return parseOtlpHeaders(envValue);
|
|
713
|
+
const fileValue = pickOtlpValue(typeof fileOtlp.forwardHeaders === 'object' && fileOtlp.forwardHeaders !== null
|
|
714
|
+
? fileOtlp.forwardHeaders
|
|
715
|
+
: undefined, typeof file.otlpForwardHeaders === 'object' && file.otlpForwardHeaders !== null
|
|
716
|
+
? file.otlpForwardHeaders
|
|
717
|
+
: undefined, 'otlpForwardHeaders', usedLegacyOtlpKeys);
|
|
718
|
+
if (fileValue !== undefined)
|
|
719
|
+
return fileValue;
|
|
720
|
+
// Don't synthesize a NR api-key header default in local mode — the licenseKey
|
|
721
|
+
// may be present in the env from another tool, and we must not leak it to a
|
|
722
|
+
// forward target the user didn't explicitly configure.
|
|
723
|
+
return mode !== 'local' && licenseKey !== undefined ? { 'api-key': licenseKey } : {};
|
|
724
|
+
})();
|
|
725
|
+
if (usedLegacyOtlpKeys.length > 0) {
|
|
726
|
+
logger.warn('Config file uses deprecated flat OTLP fields — move these under a nested "otlp" object',
|
|
727
|
+
// Field name deliberately avoids "key"/"keys" — the logger's redact()
|
|
728
|
+
// treats any key-shaped property name as secret-bearing and would
|
|
729
|
+
// replace the whole array with '***' (see SECRET_KEY_RE).
|
|
730
|
+
{ legacyFieldNames: usedLegacyOtlpKeys, path: configFilePath });
|
|
592
731
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
732
|
+
return {
|
|
733
|
+
endpoint,
|
|
734
|
+
headers,
|
|
735
|
+
transport: transportValue,
|
|
736
|
+
receiverEnabled,
|
|
737
|
+
receiverPort,
|
|
738
|
+
receiverBindAddress,
|
|
739
|
+
forwardEndpoint,
|
|
740
|
+
forwardHeaders,
|
|
741
|
+
};
|
|
597
742
|
})(),
|
|
598
743
|
personalAlertThresholds: (() => {
|
|
599
744
|
const fileThresholds = file.alerts?.personal;
|