@vfarcic/dot-ai 1.14.1 → 1.15.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.
@@ -80,6 +80,10 @@ export declare class CircuitBreaker {
80
80
  private openedAt?;
81
81
  private halfOpenAttempts;
82
82
  private lastCircuitOpenLogTime?;
83
+ private lastFailureLogTime?;
84
+ private suppressedFailureLogCount;
85
+ /** Minimum interval between failure WARN logs (ms) */
86
+ private static readonly FAILURE_LOG_INTERVAL_MS;
83
87
  constructor(name: string, config?: CircuitBreakerConfig, logger?: Logger);
84
88
  /**
85
89
  * Execute an operation through the circuit breaker
@@ -1 +1 @@
1
- {"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/core/circuit-breaker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/core/circuit-breaker.ts"],"names":[],"mappings":"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"}
@@ -65,13 +65,17 @@ class CircuitBreaker {
65
65
  openedAt;
66
66
  halfOpenAttempts = 0;
67
67
  lastCircuitOpenLogTime;
68
+ lastFailureLogTime;
69
+ suppressedFailureLogCount = 0;
70
+ /** Minimum interval between failure WARN logs (ms) */
71
+ static FAILURE_LOG_INTERVAL_MS = 30000;
68
72
  constructor(name, config, logger) {
69
73
  this.name = name;
70
74
  this.config = {
71
75
  failureThreshold: config?.failureThreshold ?? 3,
72
76
  cooldownPeriodMs: config?.cooldownPeriodMs ?? 30000,
73
77
  halfOpenMaxAttempts: config?.halfOpenMaxAttempts ?? 1,
74
- onStateChange: config?.onStateChange
78
+ onStateChange: config?.onStateChange,
75
79
  };
76
80
  this.logger = logger ?? new error_handling_1.ConsoleLogger('CircuitBreaker');
77
81
  }
@@ -89,7 +93,7 @@ class CircuitBreaker {
89
93
  this.lastCircuitOpenLogTime < this.openedAt) {
90
94
  this.logger.warn(`Circuit '${this.name}' is open, blocking requests`, {
91
95
  remainingCooldownMs: remainingCooldown,
92
- willRetryAt: new Date(Date.now() + remainingCooldown).toISOString()
96
+ willRetryAt: new Date(Date.now() + remainingCooldown).toISOString(),
93
97
  });
94
98
  this.lastCircuitOpenLogTime = new Date();
95
99
  }
@@ -100,7 +104,7 @@ class CircuitBreaker {
100
104
  this.halfOpenAttempts++;
101
105
  this.logger.info(`Circuit '${this.name}' attempting request in half-open state`, {
102
106
  attempt: this.halfOpenAttempts,
103
- maxAttempts: this.config.halfOpenMaxAttempts
107
+ maxAttempts: this.config.halfOpenMaxAttempts,
104
108
  });
105
109
  }
106
110
  try {
@@ -125,7 +129,7 @@ class CircuitBreaker {
125
129
  this.transitionTo(CircuitState.CLOSED);
126
130
  this.halfOpenAttempts = 0;
127
131
  this.logger.info(`Circuit '${this.name}' recovered, transitioning to closed`, {
128
- totalSuccesses: this.totalSuccesses
132
+ totalSuccesses: this.totalSuccesses,
129
133
  });
130
134
  }
131
135
  }
@@ -136,11 +140,27 @@ class CircuitBreaker {
136
140
  this.consecutiveFailures++;
137
141
  this.totalFailures++;
138
142
  this.lastFailureTime = new Date();
139
- this.logger.warn(`Circuit '${this.name}' recorded failure`, {
140
- consecutiveFailures: this.consecutiveFailures,
141
- threshold: this.config.failureThreshold,
142
- error: error?.message
143
- });
143
+ // Rate-limit failure WARN logs to avoid log spam during sustained outages
144
+ const now = Date.now();
145
+ const shouldLogFailure = !this.lastFailureLogTime ||
146
+ now - this.lastFailureLogTime.getTime() >=
147
+ CircuitBreaker.FAILURE_LOG_INTERVAL_MS;
148
+ if (shouldLogFailure) {
149
+ const logData = {
150
+ consecutiveFailures: this.consecutiveFailures,
151
+ threshold: this.config.failureThreshold,
152
+ error: error?.message,
153
+ };
154
+ if (this.suppressedFailureLogCount > 0) {
155
+ logData.suppressedLogCount = this.suppressedFailureLogCount;
156
+ }
157
+ this.logger.warn(`Circuit '${this.name}' recorded failure`, logData);
158
+ this.lastFailureLogTime = new Date(now);
159
+ this.suppressedFailureLogCount = 0;
160
+ }
161
+ else {
162
+ this.suppressedFailureLogCount++;
163
+ }
144
164
  if (this.state === CircuitState.HALF_OPEN) {
145
165
  // Failure in half-open state opens the circuit again
146
166
  this.transitionTo(CircuitState.OPEN);
@@ -148,7 +168,8 @@ class CircuitBreaker {
148
168
  this.halfOpenAttempts = 0;
149
169
  this.logger.warn(`Circuit '${this.name}' failed in half-open state, reopening`);
150
170
  }
151
- else if (this.state === CircuitState.CLOSED && this.consecutiveFailures >= this.config.failureThreshold) {
171
+ else if (this.state === CircuitState.CLOSED &&
172
+ this.consecutiveFailures >= this.config.failureThreshold) {
152
173
  // Threshold reached, open the circuit
153
174
  this.transitionTo(CircuitState.OPEN);
154
175
  this.openedAt = new Date();
@@ -165,6 +186,8 @@ class CircuitBreaker {
165
186
  this.halfOpenAttempts = 0;
166
187
  this.openedAt = undefined;
167
188
  this.lastCircuitOpenLogTime = undefined;
189
+ this.lastFailureLogTime = undefined;
190
+ this.suppressedFailureLogCount = 0;
168
191
  if (previousState !== CircuitState.CLOSED) {
169
192
  this.logger.info(`Circuit '${this.name}' manually reset to closed`);
170
193
  this.config.onStateChange?.(previousState, CircuitState.CLOSED, this.name);
@@ -194,7 +217,7 @@ class CircuitBreaker {
194
217
  lastFailureTime: this.lastFailureTime,
195
218
  lastSuccessTime: this.lastSuccessTime,
196
219
  openedAt: this.openedAt,
197
- halfOpenAttempts: this.halfOpenAttempts
220
+ halfOpenAttempts: this.halfOpenAttempts,
198
221
  };
199
222
  }
200
223
  /**
@@ -50,6 +50,9 @@ export declare class VercelEmbeddingProvider implements EmbeddingProvider {
50
50
  */
51
51
  export declare class EmbeddingService {
52
52
  private provider;
53
+ private lastBatchFailureLogTime?;
54
+ private suppressedBatchFailureCount;
55
+ private static readonly BATCH_FAILURE_LOG_INTERVAL_MS;
53
56
  constructor(config?: EmbeddingConfig);
54
57
  /**
55
58
  * Generate embedding for text
@@ -1 +1 @@
1
- {"version":3,"file":"embedding-service.d.ts","sourceRoot":"","sources":["../../src/core/embedding-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAEL,mBAAmB,EAEpB,MAAM,mBAAmB,CAAC;AAc3B;;GAEG;AACH,eAAO,MAAM,mBAAmB,iDAItB,CAAC;AACX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACnD,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzD,WAAW,IAAI,OAAO,CAAC;IACvB,aAAa,IAAI,MAAM,CAAC;IACxB,QAAQ,IAAI,MAAM,CAAC;CACpB;AAgBD;;;GAGG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,aAAa,CAA6B;gBAEtC,MAAM,EAAE,eAAe,GAAG;QAAE,QAAQ,EAAE,qBAAqB,CAAA;KAAE;IAmFnE,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgElD,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IA2E9D,WAAW,IAAI,OAAO;IAItB,aAAa,IAAI,MAAM;IAIvB,QAAQ,IAAI,MAAM;IAIlB,eAAe,IAAI,MAAM;CAG1B;AAqCD;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAA2B;gBAE/B,MAAM,GAAE,eAAoB;IAKxC;;;OAGG;IACG,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgBxD;;;OAGG;IACG,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IAiB9D;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,aAAa,IAAI,MAAM;IAIvB;;OAEG;IACH,SAAS,IAAI;QACX,SAAS,EAAE,OAAO,CAAC;QACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB;IAqCD;;OAEG;IACH,uBAAuB,CAAC,OAAO,EAAE;QAC/B,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,kBAAkB,EAAE,MAAM,EAAE,CAAC;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,MAAM;IAYV;;;OAGG;IACH,sBAAsB,IAAI,mBAAmB,GAAG,IAAI;CAGrD"}
1
+ {"version":3,"file":"embedding-service.d.ts","sourceRoot":"","sources":["../../src/core/embedding-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAEL,mBAAmB,EAEpB,MAAM,mBAAmB,CAAC;AAc3B;;GAEG;AACH,eAAO,MAAM,mBAAmB,iDAItB,CAAC;AACX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACnD,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzD,WAAW,IAAI,OAAO,CAAC;IACvB,aAAa,IAAI,MAAM,CAAC;IACxB,QAAQ,IAAI,MAAM,CAAC;CACpB;AAgBD;;;GAGG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,aAAa,CAA6B;gBAEtC,MAAM,EAAE,eAAe,GAAG;QAAE,QAAQ,EAAE,qBAAqB,CAAA;KAAE;IAmFnE,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgElD,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IA2E9D,WAAW,IAAI,OAAO;IAItB,aAAa,IAAI,MAAM;IAIvB,QAAQ,IAAI,MAAM;IAIlB,eAAe,IAAI,MAAM;CAG1B;AAqCD;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,uBAAuB,CAAC,CAAS;IACzC,OAAO,CAAC,2BAA2B,CAAa;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,6BAA6B,CAAS;gBAElD,MAAM,GAAE,eAAoB;IAKxC;;;OAGG;IACG,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgBxD;;;OAGG;IACG,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IA8B9D;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,aAAa,IAAI,MAAM;IAIvB;;OAEG;IACH,SAAS,IAAI;QACX,SAAS,EAAE,OAAO,CAAC;QACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB;IAqCD;;OAEG;IACH,uBAAuB,CAAC,OAAO,EAAE;QAC/B,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,kBAAkB,EAAE,MAAM,EAAE,CAAC;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,MAAM;IAYV;;;OAGG;IACH,sBAAsB,IAAI,mBAAmB,GAAG,IAAI;CAGrD"}
@@ -272,6 +272,9 @@ function createEmbeddingProvider(config = {}) {
272
272
  */
273
273
  class EmbeddingService {
274
274
  provider;
275
+ lastBatchFailureLogTime;
276
+ suppressedBatchFailureCount = 0;
277
+ static BATCH_FAILURE_LOG_INTERVAL_MS = 30000;
275
278
  constructor(config = {}) {
276
279
  // Use factory to initialize appropriate provider
277
280
  this.provider = createEmbeddingProvider(config);
@@ -304,8 +307,19 @@ class EmbeddingService {
304
307
  return await this.provider.generateEmbeddings(texts);
305
308
  }
306
309
  catch (error) {
307
- // Log error but don't throw - allow graceful fallback
308
- console.warn('Batch embedding generation failed, falling back to keyword search:', error);
310
+ // Rate-limit fallback warnings to avoid log spam during sustained outages
311
+ const now = Date.now();
312
+ if (!this.lastBatchFailureLogTime ||
313
+ now - this.lastBatchFailureLogTime >=
314
+ EmbeddingService.BATCH_FAILURE_LOG_INTERVAL_MS) {
315
+ const suppressed = this.suppressedBatchFailureCount;
316
+ this.suppressedBatchFailureCount = 0;
317
+ this.lastBatchFailureLogTime = now;
318
+ console.warn('Batch embedding generation failed, falling back to keyword search:', error instanceof Error ? error.message : String(error), suppressed > 0 ? `(${suppressed} similar warnings suppressed)` : '');
319
+ }
320
+ else {
321
+ this.suppressedBatchFailureCount++;
322
+ }
309
323
  return [];
310
324
  }
311
325
  }
@@ -6,10 +6,47 @@
6
6
  * the attachTo routing mechanism.
7
7
  *
8
8
  * PRD #358: MCP Server Integration
9
+ * PRD #414: MCP Client Outbound Authentication
9
10
  */
10
- import { McpServerConfig, DiscoveredMcpServer, McpServerStats, McpAttachableOperation } from './mcp-client-types';
11
+ import type { StreamableHTTPClientTransportOptions } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
12
+ import type { OAuthClientProvider, OAuthDiscoveryState } from '@modelcontextprotocol/sdk/client/auth.js';
13
+ import type { OAuthTokens, OAuthClientMetadata } from '@modelcontextprotocol/sdk/shared/auth.js';
14
+ import { McpServerConfig, McpServerAuthConfig, DiscoveredMcpServer, McpServerStats, McpAttachableOperation } from './mcp-client-types';
11
15
  import { Logger } from './error-handling';
12
16
  import { AITool, ToolExecutor } from './ai-provider.interface';
17
+ /**
18
+ * Minimal OAuthClientProvider that returns a static bearer token.
19
+ *
20
+ * Used when the MCP server expects MCP-spec-compliant auth (authProvider)
21
+ * but the token is a pre-provisioned service account JWT or API key
22
+ * rather than an interactive OAuth flow.
23
+ *
24
+ * PRD #414: MCP Client Outbound Authentication (M1)
25
+ */
26
+ export declare class StaticTokenAuthProvider implements OAuthClientProvider {
27
+ private readonly token;
28
+ constructor(token: string);
29
+ get redirectUrl(): undefined;
30
+ get clientMetadata(): OAuthClientMetadata;
31
+ clientInformation(): undefined;
32
+ tokens(): Promise<OAuthTokens>;
33
+ saveTokens(): Promise<void>;
34
+ redirectToAuthorization(): Promise<void>;
35
+ saveCodeVerifier(): Promise<void>;
36
+ codeVerifier(): Promise<string>;
37
+ invalidateCredentials(_scope: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'): Promise<void>;
38
+ saveDiscoveryState(_state: OAuthDiscoveryState): Promise<void>;
39
+ discoveryState(): Promise<OAuthDiscoveryState | undefined>;
40
+ }
41
+ /**
42
+ * Resolve transport options (authProvider and/or requestInit) from auth config.
43
+ *
44
+ * Reads token/header values from environment variables (sourced from K8s Secrets).
45
+ * Returns partial options to merge into StreamableHTTPClientTransportOptions.
46
+ *
47
+ * PRD #414: MCP Client Outbound Authentication (M1 + M2 + M4)
48
+ */
49
+ export declare function resolveTransportAuth(auth: McpServerAuthConfig | undefined, serverName: string, logger: Logger): Pick<StreamableHTTPClientTransportOptions, 'authProvider' | 'requestInit'>;
13
50
  /**
14
51
  * Manages MCP server connections, tool discovery, and tool routing.
15
52
  *
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-client-manager.d.ts","sourceRoot":"","sources":["../../src/core/mcp-client-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EACL,eAAe,EAEf,mBAAmB,EACnB,cAAc,EAEd,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAW/D;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,oDAAoD;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAE1D,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyD;IAEpF,sDAAsD;IACtD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA+C;IAEjF,0DAA0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;gBAEnD,MAAM,EAAE,MAAM;IAI1B;;;;;;OAMG;IACH,MAAM,CAAC,oBAAoB,IAAI,eAAe,EAAE;IA+DhD;;;;;OAKG;IACG,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCnE;;OAEG;YACW,kBAAkB;IAmGhC;;;;OAIG;IACH,oBAAoB,CAAC,SAAS,EAAE,sBAAsB,GAAG,MAAM,EAAE;IAoBjE;;OAEG;IACH,qBAAqB,IAAI,MAAM,EAAE;IAejC;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIpC;;;;;;OAMG;IACH,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,YAAY,GAAG,YAAY;IA6DjE;;OAEG;IACH,QAAQ,IAAI,cAAc;IAQ1B;;OAEG;IACH,oBAAoB,IAAI,mBAAmB,EAAE;IAI7C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B;;OAEG;IACH,OAAO,CAAC,eAAe;CAYxB"}
1
+ {"version":3,"file":"mcp-client-manager.d.ts","sourceRoot":"","sources":["../../src/core/mcp-client-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,oDAAoD,CAAC;AAC/G,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAEzG,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AACjG,OAAO,EACL,eAAe,EACf,mBAAmB,EAEnB,mBAAmB,EACnB,cAAc,EAEd,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAW/D;;;;;;;;GAQG;AACH,qBAAa,uBAAwB,YAAW,mBAAmB;IACjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,KAAK,EAAE,MAAM;IAIzB,IAAI,WAAW,IAAI,SAAS,CAAsB;IAElD,IAAI,cAAc,IAAI,mBAAmB,CAKxC;IAED,iBAAiB;IAEX,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC;IAO9B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAC3B,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC;IACxC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAGjC,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAE/B,qBAAqB,CAAC,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpG,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAC9D,cAAc,IAAI,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;CACjE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,mBAAmB,GAAG,SAAS,EACrC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,IAAI,CAAC,oCAAoC,EAAE,cAAc,GAAG,aAAa,CAAC,CAsF5E;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,oDAAoD;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAE1D,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyD;IAEpF,sDAAsD;IACtD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA+C;IAEjF,0DAA0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;gBAEnD,MAAM,EAAE,MAAM;IAI1B;;;;;;OAMG;IACH,MAAM,CAAC,oBAAoB,IAAI,eAAe,EAAE;IAwIhD;;;;;OAKG;IACG,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCnE;;OAEG;YACW,kBAAkB;IAwGhC;;;;OAIG;IACH,oBAAoB,CAAC,SAAS,EAAE,sBAAsB,GAAG,MAAM,EAAE;IAoBjE;;OAEG;IACH,qBAAqB,IAAI,MAAM,EAAE;IAejC;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIpC;;;;;;OAMG;IACH,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,YAAY,GAAG,YAAY;IA6DjE;;OAEG;IACH,QAAQ,IAAI,cAAc;IAQ1B;;OAEG;IACH,oBAAoB,IAAI,mBAAmB,EAAE;IAI7C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B;;OAEG;IACH,OAAO,CAAC,eAAe;CAYxB"}
@@ -7,12 +7,15 @@
7
7
  * the attachTo routing mechanism.
8
8
  *
9
9
  * PRD #358: MCP Server Integration
10
+ * PRD #414: MCP Client Outbound Authentication
10
11
  */
11
12
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.McpClientManager = void 0;
13
+ exports.McpClientManager = exports.StaticTokenAuthProvider = void 0;
14
+ exports.resolveTransportAuth = resolveTransportAuth;
13
15
  const node_fs_1 = require("node:fs");
14
16
  const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
15
17
  const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
18
+ const auth_extensions_js_1 = require("@modelcontextprotocol/sdk/client/auth-extensions.js");
16
19
  const mcp_client_types_1 = require("./mcp-client-types");
17
20
  /** Path for MCP servers config file (mounted from ConfigMap in K8s) */
18
21
  const MCP_SERVERS_CONFIG_PATH = '/etc/dot-ai-mcp/mcp-servers.json';
@@ -20,6 +23,135 @@ const MCP_SERVERS_CONFIG_PATH = '/etc/dot-ai-mcp/mcp-servers.json';
20
23
  const TOOL_NAME_SEPARATOR = '__';
21
24
  /** Default timeout for MCP requests in milliseconds */
22
25
  const DEFAULT_TIMEOUT_MS = 30_000;
26
+ /**
27
+ * Minimal OAuthClientProvider that returns a static bearer token.
28
+ *
29
+ * Used when the MCP server expects MCP-spec-compliant auth (authProvider)
30
+ * but the token is a pre-provisioned service account JWT or API key
31
+ * rather than an interactive OAuth flow.
32
+ *
33
+ * PRD #414: MCP Client Outbound Authentication (M1)
34
+ */
35
+ class StaticTokenAuthProvider {
36
+ token;
37
+ constructor(token) {
38
+ this.token = token;
39
+ }
40
+ get redirectUrl() { return undefined; }
41
+ get clientMetadata() {
42
+ return {
43
+ redirect_uris: [],
44
+ client_name: 'dot-ai',
45
+ };
46
+ }
47
+ clientInformation() { return undefined; }
48
+ async tokens() {
49
+ return {
50
+ access_token: this.token,
51
+ token_type: 'bearer',
52
+ };
53
+ }
54
+ async saveTokens() { }
55
+ async redirectToAuthorization() { }
56
+ async saveCodeVerifier() { }
57
+ // Returns empty string because the SDK type requires string (not undefined).
58
+ // Static tokens do not use PKCE, so the verifier is never meaningful.
59
+ async codeVerifier() { return ''; }
60
+ async invalidateCredentials(_scope) {
61
+ // No-op for all scopes: static tokens are pre-provisioned and cannot be refreshed.
62
+ // 'client'/'verifier' are for interactive OAuth (authorization_code + PKCE).
63
+ // 'tokens'/'discovery' have no effect since the token is fixed at construction.
64
+ }
65
+ async saveDiscoveryState(_state) { }
66
+ async discoveryState() { return undefined; }
67
+ }
68
+ exports.StaticTokenAuthProvider = StaticTokenAuthProvider;
69
+ /**
70
+ * Resolve transport options (authProvider and/or requestInit) from auth config.
71
+ *
72
+ * Reads token/header values from environment variables (sourced from K8s Secrets).
73
+ * Returns partial options to merge into StreamableHTTPClientTransportOptions.
74
+ *
75
+ * PRD #414: MCP Client Outbound Authentication (M1 + M2 + M4)
76
+ */
77
+ function resolveTransportAuth(auth, serverName, logger) {
78
+ if (!auth)
79
+ return {};
80
+ const result = {};
81
+ // M1: Static token → authProvider
82
+ if (auth.tokenEnvVar) {
83
+ const token = process.env[auth.tokenEnvVar];
84
+ if (token) {
85
+ result.authProvider = new StaticTokenAuthProvider(token);
86
+ logger.info('MCP server auth configured via authProvider (static token)', {
87
+ server: serverName,
88
+ envVar: auth.tokenEnvVar,
89
+ });
90
+ }
91
+ else {
92
+ throw new Error(`MCP server '${serverName}' auth.tokenEnvVar references env var '${auth.tokenEnvVar}' but it is empty or unset — fix the K8s Secret or remove the auth config`);
93
+ }
94
+ }
95
+ // M2: Custom headers → requestInit
96
+ if (auth.headersEnvVar) {
97
+ const headersJson = process.env[auth.headersEnvVar];
98
+ if (headersJson) {
99
+ try {
100
+ const headers = JSON.parse(headersJson);
101
+ if (typeof headers !== 'object' || headers === null || Array.isArray(headers)) {
102
+ throw new Error('Headers must be a JSON object of key-value pairs');
103
+ }
104
+ // Validate all header values are strings — non-string values cause HTTP errors
105
+ for (const [key, value] of Object.entries(headers)) {
106
+ if (typeof value !== 'string') {
107
+ throw new Error(`Header "${key}" value must be a string, got ${typeof value}`);
108
+ }
109
+ }
110
+ result.requestInit = { headers };
111
+ logger.info('MCP server auth configured via requestInit headers', {
112
+ server: serverName,
113
+ envVar: auth.headersEnvVar,
114
+ headerCount: Object.keys(headers).length,
115
+ });
116
+ }
117
+ catch (err) {
118
+ throw new Error(`MCP server '${serverName}' auth.headersEnvVar env var '${auth.headersEnvVar}' contains invalid JSON: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
119
+ }
120
+ }
121
+ else {
122
+ throw new Error(`MCP server '${serverName}' auth.headersEnvVar references env var '${auth.headersEnvVar}' but it is empty or unset — fix the K8s Secret or remove the auth config`);
123
+ }
124
+ }
125
+ // M4: OAuth client_credentials → authProvider (takes precedence over tokenEnvVar)
126
+ // Uses SDK built-in ClientCredentialsProvider (@modelcontextprotocol/sdk ^1.27.1)
127
+ // instead of custom implementation. The SDK handles:
128
+ // - prepareTokenRequest() → sets grant_type=client_credentials + scope
129
+ // - client_secret_basic auth method (RFC 6749 §2.3.1)
130
+ // - Token caching and automatic refresh on 401
131
+ // No PKCE: client_credentials is non-interactive (RFC 6749 §4.4), PKCE is for
132
+ // authorization_code grants only (RFC 7636 §1).
133
+ if (auth.oauth) {
134
+ const clientSecret = process.env[auth.oauth.clientSecretEnvVar];
135
+ if (clientSecret) {
136
+ result.authProvider = new auth_extensions_js_1.ClientCredentialsProvider({
137
+ clientId: auth.oauth.clientId,
138
+ clientSecret,
139
+ clientName: 'dot-ai',
140
+ scope: auth.oauth.scope,
141
+ });
142
+ logger.info('MCP server auth configured via authProvider (OAuth client_credentials)', {
143
+ server: serverName,
144
+ clientId: auth.oauth.clientId,
145
+ clientSecretEnvVar: auth.oauth.clientSecretEnvVar,
146
+ scope: auth.oauth.scope,
147
+ });
148
+ }
149
+ else {
150
+ throw new Error(`MCP server '${serverName}' auth.oauth.clientSecretEnvVar references env var '${auth.oauth.clientSecretEnvVar}' but it is empty or unset — fix the K8s Secret or remove the auth config`);
151
+ }
152
+ }
153
+ return result;
154
+ }
23
155
  /**
24
156
  * Manages MCP server connections, tool discovery, and tool routing.
25
157
  *
@@ -83,11 +215,65 @@ class McpClientManager {
83
215
  throw new Error(`MCP server at index ${index} (${s.name || 'unnamed'}) has invalid attachTo value '${op}'. Must be one of: ${validOperations.join(', ')}`);
84
216
  }
85
217
  }
218
+ // Parse optional auth config (PRD #414)
219
+ // Fail-fast on malformed auth — silent degradation to unauthenticated is a security risk
220
+ let auth;
221
+ const serverLabel = s.name || 'unnamed';
222
+ if (s.auth !== undefined) {
223
+ if (!s.auth || typeof s.auth !== 'object' || Array.isArray(s.auth)) {
224
+ throw new Error(`MCP server at index ${index} (${serverLabel}) auth must be an object`);
225
+ }
226
+ const rawAuth = s.auth;
227
+ auth = {};
228
+ if ('tokenEnvVar' in rawAuth) {
229
+ if (typeof rawAuth.tokenEnvVar !== 'string' || rawAuth.tokenEnvVar.trim() === '') {
230
+ throw new Error(`MCP server at index ${index} (${serverLabel}) auth.tokenEnvVar must be a non-empty string`);
231
+ }
232
+ auth.tokenEnvVar = rawAuth.tokenEnvVar;
233
+ }
234
+ if ('headersEnvVar' in rawAuth) {
235
+ if (typeof rawAuth.headersEnvVar !== 'string' || rawAuth.headersEnvVar.trim() === '') {
236
+ throw new Error(`MCP server at index ${index} (${serverLabel}) auth.headersEnvVar must be a non-empty string`);
237
+ }
238
+ auth.headersEnvVar = rawAuth.headersEnvVar;
239
+ }
240
+ // M4: OAuth client_credentials config
241
+ if ('oauth' in rawAuth) {
242
+ if (!rawAuth.oauth || typeof rawAuth.oauth !== 'object' || Array.isArray(rawAuth.oauth)) {
243
+ throw new Error(`MCP server at index ${index} (${serverLabel}) auth.oauth must be an object`);
244
+ }
245
+ const rawOAuth = rawAuth.oauth;
246
+ if (!rawOAuth.clientId || typeof rawOAuth.clientId !== 'string') {
247
+ throw new Error(`MCP server at index ${index} (${serverLabel}) auth.oauth is missing required 'clientId' field`);
248
+ }
249
+ if (!rawOAuth.clientSecretEnvVar || typeof rawOAuth.clientSecretEnvVar !== 'string') {
250
+ throw new Error(`MCP server at index ${index} (${serverLabel}) auth.oauth is missing required 'clientSecretEnvVar' field`);
251
+ }
252
+ if ('scope' in rawOAuth && (typeof rawOAuth.scope !== 'string' || rawOAuth.scope.trim() === '')) {
253
+ throw new Error(`MCP server at index ${index} (${serverLabel}) auth.oauth.scope must be a non-empty string`);
254
+ }
255
+ auth.oauth = {
256
+ clientId: rawOAuth.clientId,
257
+ clientSecretEnvVar: rawOAuth.clientSecretEnvVar,
258
+ scope: typeof rawOAuth.scope === 'string' ? rawOAuth.scope : undefined,
259
+ };
260
+ }
261
+ // Fail-fast: auth block present but no valid fields configured
262
+ if (!auth.tokenEnvVar && !auth.headersEnvVar && !auth.oauth) {
263
+ throw new Error(`MCP server at index ${index} (${serverLabel}) auth is present but contains no valid auth fields (tokenEnvVar, headersEnvVar, or oauth)`);
264
+ }
265
+ // Mutual exclusivity: tokenEnvVar and oauth cannot both be specified
266
+ // because both set authProvider — oauth would silently overwrite the static token
267
+ if (auth.tokenEnvVar && auth.oauth) {
268
+ throw new Error(`MCP server at index ${index} (${serverLabel}) auth specifies both 'tokenEnvVar' and 'oauth' — these are mutually exclusive (both set authProvider)`);
269
+ }
270
+ }
86
271
  return {
87
272
  name: s.name || `mcp-server-${index}`,
88
273
  endpoint: s.endpoint,
89
274
  attachTo: s.attachTo,
90
275
  timeout: s.timeout,
276
+ auth,
91
277
  };
92
278
  });
93
279
  }
@@ -134,7 +320,10 @@ class McpClientManager {
134
320
  name: config.name,
135
321
  endpoint: config.endpoint,
136
322
  attachTo: config.attachTo,
323
+ hasAuth: !!config.auth,
137
324
  });
325
+ // Resolve authentication options from config + env vars (PRD #414)
326
+ const authOptions = resolveTransportAuth(config.auth, config.name, this.logger);
138
327
  const transport = new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(config.endpoint), {
139
328
  reconnectionOptions: {
140
329
  maxReconnectionDelay: 30_000,
@@ -142,6 +331,7 @@ class McpClientManager {
142
331
  reconnectionDelayGrowFactor: 1.5,
143
332
  maxRetries: 2,
144
333
  },
334
+ ...authOptions,
145
335
  });
146
336
  const client = new index_js_1.Client({ name: 'dot-ai', version: '1.0.0' }, { capabilities: {} });
147
337
  // Connect with timeout
@@ -6,11 +6,70 @@
6
6
  * that augment dot-ai's built-in kubectl/helm tools.
7
7
  *
8
8
  * PRD #358: MCP Server Integration
9
+ * PRD #414: MCP Client Outbound Authentication
9
10
  */
10
11
  /**
11
12
  * Valid dot-ai operations that MCP servers can attach to
12
13
  */
13
14
  export type McpAttachableOperation = 'remediate' | 'operate' | 'query';
15
+ /**
16
+ * Authentication configuration for an MCP server connection.
17
+ *
18
+ * Four modes are supported:
19
+ * 1. Static token via `tokenEnvVar` — reads bearer token from env var, passed as authProvider
20
+ * 2. Custom headers via `headersEnvVar` — reads JSON headers from env var, passed as requestInit
21
+ * 3. OAuth via `oauth` — client_credentials grant using the MCP SDK's OAuthClientProvider
22
+ * 4. No auth (omit `auth`) — current behavior, backward compatible
23
+ *
24
+ * Credentials are injected via environment variables sourced from K8s Secrets.
25
+ * Never store tokens in ConfigMaps or Helm values.
26
+ *
27
+ * PRD #414: MCP Client Outbound Authentication
28
+ */
29
+ export interface McpServerAuthConfig {
30
+ /**
31
+ * Environment variable name containing a bearer token for authProvider.
32
+ * The token is passed to StreamableHTTPClientTransport via a static authProvider
33
+ * that returns `{ access_token: tokenValue, token_type: 'bearer' }`.
34
+ *
35
+ * Example env var: MCP_AUTH_CONTEXT_FORGE=eyJhbGciOi...
36
+ */
37
+ tokenEnvVar?: string;
38
+ /**
39
+ * Environment variable name containing JSON-encoded HTTP headers for requestInit.
40
+ * Used for non-MCP-spec-compliant servers that require custom auth headers.
41
+ * The value must be a JSON object of header name → value pairs.
42
+ *
43
+ * Example env var: MCP_HEADERS_LEGACY_SERVER={"X-API-Key":"abc123"}
44
+ */
45
+ headersEnvVar?: string;
46
+ /**
47
+ * OAuth client_credentials configuration for MCP-spec-compliant servers.
48
+ * Uses the MCP SDK's OAuthClientProvider interface with client_credentials grant.
49
+ * The SDK handles discovery (RFC 9728), token exchange, and automatic refresh.
50
+ *
51
+ * PRD #414: MCP Client Outbound Authentication (M4)
52
+ */
53
+ oauth?: McpOAuthConfig;
54
+ }
55
+ /**
56
+ * OAuth client_credentials configuration for outbound MCP connections.
57
+ *
58
+ * dot-ai acts as an OAuth client authenticating to an MCP server's authorization
59
+ * server using the client_credentials grant (non-interactive, server-to-server).
60
+ * The MCP SDK discovers the authorization server via RFC 9728 metadata.
61
+ */
62
+ export interface McpOAuthConfig {
63
+ /** OAuth client ID for this MCP server connection */
64
+ clientId: string;
65
+ /**
66
+ * Environment variable name containing the OAuth client secret.
67
+ * Example: MCP_OAUTH_SECRET_DOT_AI_UPSTREAM
68
+ */
69
+ clientSecretEnvVar: string;
70
+ /** Optional OAuth scope to request (e.g., "mcp:tools mcp:read") */
71
+ scope?: string;
72
+ }
14
73
  /**
15
74
  * Configuration for an MCP server connection.
16
75
  * Loaded from /etc/dot-ai-mcp/mcp-servers.json (mounted via Helm ConfigMap).
@@ -24,6 +83,12 @@ export interface McpServerConfig {
24
83
  attachTo: McpAttachableOperation[];
25
84
  /** Optional timeout in milliseconds for MCP requests (default: 30000) */
26
85
  timeout?: number;
86
+ /**
87
+ * Optional authentication configuration for this MCP server.
88
+ * When omitted, no authentication is used (backward compatible).
89
+ * PRD #414: MCP Client Outbound Authentication
90
+ */
91
+ auth?: McpServerAuthConfig;
27
92
  }
28
93
  /**
29
94
  * Tool definition discovered from an MCP server via client.listTools()
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-client-types.d.ts","sourceRoot":"","sources":["../../src/core/mcp-client-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAEvE;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,WAAW,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,0BAA0B;IAC1B,YAAY,EAAE,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;aAGxB,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;gBADrE,OAAO,EAAE,MAAM,EACC,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAKxE"}
1
+ {"version":3,"file":"mcp-client-types.d.ts","sourceRoot":"","sources":["../../src/core/mcp-client-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAEvE;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAE3B,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,mBAAmB,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,WAAW,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,0BAA0B;IAC1B,YAAY,EAAE,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;aAGxB,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;gBADrE,OAAO,EAAE,MAAM,EACC,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAKxE"}
@@ -7,6 +7,7 @@
7
7
  * that augment dot-ai's built-in kubectl/helm tools.
8
8
  *
9
9
  * PRD #358: MCP Server Integration
10
+ * PRD #414: MCP Client Outbound Authentication
10
11
  */
11
12
  Object.defineProperty(exports, "__esModule", { value: true });
12
13
  exports.McpDiscoveryError = void 0;
@@ -180,6 +180,9 @@ async function main() {
180
180
  process.stderr.write(`MCP server discovery complete: ${stats.serverCount} server(s), ${stats.toolCount} tool(s)\n`);
181
181
  }
182
182
  catch (error) {
183
+ // Fail-fast: in Kubernetes, crash + backoff is easier to detect than a Running
184
+ // pod silently missing MCP tools. Config errors (bad endpoint, missing auth)
185
+ // surface immediately via CrashLoopBackOff events and alerts.
183
186
  process.stderr.write(`FATAL: MCP server discovery failed: ${error instanceof Error ? error.message : String(error)}\n`);
184
187
  process.exit(1);
185
188
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vfarcic/dot-ai",
3
- "version": "1.14.1",
3
+ "version": "1.15.1",
4
4
  "description": "AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance",
5
5
  "mcpName": "io.github.vfarcic/dot-ai",
6
6
  "main": "dist/index.js",