@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.
- package/dist/core/circuit-breaker.d.ts +4 -0
- package/dist/core/circuit-breaker.d.ts.map +1 -1
- package/dist/core/circuit-breaker.js +34 -11
- package/dist/core/embedding-service.d.ts +3 -0
- package/dist/core/embedding-service.d.ts.map +1 -1
- package/dist/core/embedding-service.js +16 -2
- package/dist/core/mcp-client-manager.d.ts +38 -1
- package/dist/core/mcp-client-manager.d.ts.map +1 -1
- package/dist/core/mcp-client-manager.js +191 -1
- package/dist/core/mcp-client-types.d.ts +65 -0
- package/dist/core/mcp-client-types.d.ts.map +1 -1
- package/dist/core/mcp-client-types.js +1 -0
- package/dist/mcp/server.js +3 -0
- package/package.json +1 -1
|
@@ -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":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAiB,MAAM,kBAAkB,CAAC;AAEzD;;GAEG;AACH,oBAAY,YAAY;IACtB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,SAAS,cAAc;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gGAAgG;IAChG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kCAAkC;IAClC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9E;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,YAAY,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,IAAI,CAAC;IACvB,eAAe,CAAC,EAAE,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAgB,WAAW,EAAE,MAAM,CAAC;IACpC,SAAgB,mBAAmB,EAAE,MAAM,CAAC;IAC5C,SAAgB,KAAK,EAAE,YAAY,CAAC;gBAExB,WAAW,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/core/circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAiB,MAAM,kBAAkB,CAAC;AAEzD;;GAEG;AACH,oBAAY,YAAY;IACtB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,SAAS,cAAc;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gGAAgG;IAChG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kCAAkC;IAClC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9E;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,YAAY,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,IAAI,CAAC;IACvB,eAAe,CAAC,EAAE,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAgB,WAAW,EAAE,MAAM,CAAC;IACpC,SAAgB,mBAAmB,EAAE,MAAM,CAAC;IAC5C,SAAgB,KAAK,EAAE,YAAY,CAAC;gBAExB,WAAW,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM;CAS7D;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAGuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,mBAAmB,CAAa;IACxC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,eAAe,CAAC,CAAO;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAO;IAC/B,OAAO,CAAC,QAAQ,CAAC,CAAO;IACxB,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,sBAAsB,CAAC,CAAO;IACtC,OAAO,CAAC,kBAAkB,CAAC,CAAO;IAClC,OAAO,CAAC,yBAAyB,CAAa;IAC9C,sDAAsD;IACtD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAS;gBAE5C,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,oBAAoB,EAAE,MAAM,CAAC,EAAE,MAAM;IAWxE;;;;OAIG;IACG,OAAO,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IA0CzD;;OAEG;IACH,aAAa,IAAI,IAAI;IAkBrB;;OAEG;IACH,aAAa,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI;IAiDlC;;OAEG;IACH,KAAK,IAAI,IAAI;IAoBb;;OAEG;IACH,QAAQ,IAAI,YAAY;IAaxB;;OAEG;IACH,QAAQ,IAAI,mBAAmB;IAa/B;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACH,OAAO,CAAC,UAAU;IAgBlB;;OAEG;IACH,OAAO,CAAC,0BAA0B;IASlC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAU5B;;OAEG;IACH,OAAO,CAAC,YAAY;CAKrB;AAED;;GAEG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAuB;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;gBAEvD,aAAa,CAAC,EAAE,oBAAoB,EAAE,MAAM,CAAC,EAAE,MAAM;IAKjE;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,cAAc;IAexE;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI7C;;OAEG;IACH,QAAQ,IAAI,IAAI;IAMhB;;OAEG;IACH,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC;CAOnD"}
|
|
@@ -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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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 &&
|
|
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;
|
|
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
|
-
//
|
|
308
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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"}
|
package/dist/mcp/server.js
CHANGED
|
@@ -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.
|
|
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",
|