@vfarcic/dot-ai 1.14.1 → 1.15.0
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/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
|
@@ -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.0",
|
|
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",
|