wave-agent-sdk 0.16.5 → 0.16.7
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/managers/mcpManager.d.ts +13 -0
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +111 -3
- package/dist/services/authService.d.ts +15 -0
- package/dist/services/authService.d.ts.map +1 -1
- package/dist/services/authService.js +34 -2
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +11 -0
- package/package.json +1 -1
- package/src/managers/mcpManager.ts +141 -3
- package/src/services/authService.ts +41 -2
- package/src/utils/containerSetup.ts +12 -0
|
@@ -46,6 +46,8 @@ export declare class McpManager {
|
|
|
46
46
|
private callbacks;
|
|
47
47
|
private mcpServers;
|
|
48
48
|
private resolverCtx;
|
|
49
|
+
private reconnectTimers;
|
|
50
|
+
private reconnectAttempts;
|
|
49
51
|
constructor(container: Container, options?: McpManagerOptions);
|
|
50
52
|
/**
|
|
51
53
|
* Initialize MCP manager with working directory and optionally auto-connect
|
|
@@ -61,6 +63,12 @@ export declare class McpManager {
|
|
|
61
63
|
addServer(name: string, config: McpServerConfig): boolean;
|
|
62
64
|
removeServer(name: string): boolean;
|
|
63
65
|
connectServer(name: string): Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Schedule auto-reconnect with exponential backoff.
|
|
68
|
+
* Delays: 1s, 2s, 4s, 8s, 16s, 30s, 30s, ...
|
|
69
|
+
*/
|
|
70
|
+
private scheduleReconnect;
|
|
71
|
+
private cancelReconnect;
|
|
64
72
|
disconnectServer(name: string): Promise<boolean>;
|
|
65
73
|
getAllConnectedTools(): McpTool[];
|
|
66
74
|
executeMcpTool(toolName: string, args: Record<string, unknown>, context?: ToolContext): Promise<{
|
|
@@ -74,6 +82,11 @@ export declare class McpManager {
|
|
|
74
82
|
}>;
|
|
75
83
|
private executeToolOnConnection;
|
|
76
84
|
cleanup(): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Update credentials and reconnect MCP servers that use template variables.
|
|
87
|
+
* Called after SSO login to refresh ${WAVE_SSO_TOKEN} and ${WAVE_SERVER_URL}.
|
|
88
|
+
*/
|
|
89
|
+
refreshCredentials(serverUrl?: string, ssoToken?: string): Promise<void>;
|
|
77
90
|
/**
|
|
78
91
|
* Get all currently available MCP tools as plugins
|
|
79
92
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpManager.d.ts","sourceRoot":"","sources":["../../src/managers/mcpManager.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EACV,MAAM,EACN,eAAe,EACf,SAAS,EACT,OAAO,EACP,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAQ3B,MAAM,WAAW,mBAAmB;IAClC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;CAC3D;AAID,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC7C,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUnD;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,kBAAkB,GACtB,eAAe,CA2CjB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EACjB,GAAG,CAAC,EAAE,kBAAkB,GACvB,SAAS,CA4CX;AAED,qBAAa,UAAU;
|
|
1
|
+
{"version":3,"file":"mcpManager.d.ts","sourceRoot":"","sources":["../../src/managers/mcpManager.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EACV,MAAM,EACN,eAAe,EACf,SAAS,EACT,OAAO,EACP,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAQ3B,MAAM,WAAW,mBAAmB;IAClC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;CAC3D;AAID,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC7C,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUnD;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,kBAAkB,GACtB,eAAe,CA2CjB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EACjB,GAAG,CAAC,EAAE,kBAAkB,GACvB,SAAS,CA4CX;AAED,qBAAa,UAAU;IAcnB,OAAO,CAAC,SAAS;IAbnB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,UAAU,CAA8C;IAEhE,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,iBAAiB,CAAkC;gBAGjD,SAAS,EAAE,SAAS,EAC5B,OAAO,GAAE,iBAAsB;IAUjC;;OAEG;IACG,UAAU,CACd,OAAO,EAAE,MAAM,EACf,WAAW,GAAE,OAAe,GAC3B,OAAO,CAAC,IAAI,CAAC;IAgDV,kBAAkB,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAO/C,UAAU,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IA0DvC,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAWrD,SAAS,IAAI,SAAS,GAAG,IAAI;IAI7B,aAAa,IAAI,eAAe,EAAE;IAIlC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAIpD,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI;IASzE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO;IA+BzD,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAgB7B,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwMnD;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,eAAe;IASjB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA8BtD,oBAAoB,IAAI,OAAO,EAAE;IAW3B,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACtD,CAAC;YAsDY,uBAAuB;IA8D/B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAW9B;;;OAGG;IACG,kBAAkB,CACtB,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAuEhB;;OAEG;IACH,iBAAiB,IAAI,UAAU,EAAE;IA6BjC;;OAEG;IACH,iBAAiB,IAAI,0BAA0B,EAAE;IAIjD;;OAEG;IACG,wBAAwB,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC;IActB;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAcjC"}
|
|
@@ -114,6 +114,8 @@ export class McpManager {
|
|
|
114
114
|
this.connections = new Map();
|
|
115
115
|
this.configPath = "";
|
|
116
116
|
this.workdir = "";
|
|
117
|
+
this.reconnectTimers = new Map();
|
|
118
|
+
this.reconnectAttempts = new Map();
|
|
117
119
|
this.callbacks = options.callbacks || {};
|
|
118
120
|
this.mcpServers = options.mcpServers;
|
|
119
121
|
this.resolverCtx = {
|
|
@@ -250,19 +252,21 @@ export class McpManager {
|
|
|
250
252
|
if (this.servers.has(name)) {
|
|
251
253
|
return false;
|
|
252
254
|
}
|
|
255
|
+
// Resolve template variables before storing
|
|
256
|
+
const resolvedConfig = resolveMcpTemplates(config, this.resolverCtx ?? { serverUrl: undefined, ssoToken: undefined });
|
|
253
257
|
const newServer = {
|
|
254
258
|
name,
|
|
255
|
-
config,
|
|
259
|
+
config: resolvedConfig,
|
|
256
260
|
status: "disconnected",
|
|
257
261
|
};
|
|
258
262
|
this.servers.set(name, newServer);
|
|
259
263
|
// Update config
|
|
260
264
|
if (this.config) {
|
|
261
|
-
this.config.mcpServers[name] =
|
|
265
|
+
this.config.mcpServers[name] = resolvedConfig;
|
|
262
266
|
}
|
|
263
267
|
else {
|
|
264
268
|
this.config = {
|
|
265
|
-
mcpServers: { [name]:
|
|
269
|
+
mcpServers: { [name]: resolvedConfig },
|
|
266
270
|
};
|
|
267
271
|
}
|
|
268
272
|
return true;
|
|
@@ -413,6 +417,10 @@ export class McpManager {
|
|
|
413
417
|
tools: [],
|
|
414
418
|
toolCount: 0,
|
|
415
419
|
});
|
|
420
|
+
// Auto-reconnect with exponential backoff (not triggered by explicit disconnect)
|
|
421
|
+
if (!this.reconnectTimers.has(name)) {
|
|
422
|
+
this.scheduleReconnect(name);
|
|
423
|
+
}
|
|
416
424
|
};
|
|
417
425
|
// Store connection
|
|
418
426
|
this.connections.set(name, {
|
|
@@ -441,7 +449,46 @@ export class McpManager {
|
|
|
441
449
|
return false;
|
|
442
450
|
}
|
|
443
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Schedule auto-reconnect with exponential backoff.
|
|
454
|
+
* Delays: 1s, 2s, 4s, 8s, 16s, 30s, 30s, ...
|
|
455
|
+
*/
|
|
456
|
+
scheduleReconnect(name) {
|
|
457
|
+
const attempts = this.reconnectAttempts.get(name) ?? 0;
|
|
458
|
+
const delay = Math.min(1000 * Math.pow(2, attempts), 30000);
|
|
459
|
+
this.reconnectAttempts.set(name, attempts + 1);
|
|
460
|
+
logger?.info(`Scheduling MCP server ${name} reconnect in ${delay}ms (attempt ${attempts + 1})`);
|
|
461
|
+
const timer = setTimeout(() => {
|
|
462
|
+
this.reconnectTimers.delete(name);
|
|
463
|
+
logger?.debug(`Auto-reconnecting MCP server: ${name}`);
|
|
464
|
+
this.connectServer(name)
|
|
465
|
+
.then((success) => {
|
|
466
|
+
if (success) {
|
|
467
|
+
logger?.info(`Auto-reconnected MCP server: ${name}`);
|
|
468
|
+
this.reconnectAttempts.delete(name);
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
logger?.warn(`Auto-reconnect failed for MCP server: ${name}`);
|
|
472
|
+
// Will be retried via onclose handler
|
|
473
|
+
}
|
|
474
|
+
})
|
|
475
|
+
.catch((error) => {
|
|
476
|
+
logger?.error(`Auto-reconnect error for MCP server ${name}:`, error);
|
|
477
|
+
});
|
|
478
|
+
}, delay);
|
|
479
|
+
this.reconnectTimers.set(name, timer);
|
|
480
|
+
}
|
|
481
|
+
cancelReconnect(name) {
|
|
482
|
+
const timer = this.reconnectTimers.get(name);
|
|
483
|
+
if (timer) {
|
|
484
|
+
clearTimeout(timer);
|
|
485
|
+
this.reconnectTimers.delete(name);
|
|
486
|
+
}
|
|
487
|
+
this.reconnectAttempts.delete(name);
|
|
488
|
+
}
|
|
444
489
|
async disconnectServer(name) {
|
|
490
|
+
// Cancel any pending reconnect attempts
|
|
491
|
+
this.cancelReconnect(name);
|
|
445
492
|
const connection = this.connections.get(name);
|
|
446
493
|
if (!connection)
|
|
447
494
|
return false;
|
|
@@ -550,9 +597,70 @@ export class McpManager {
|
|
|
550
597
|
}
|
|
551
598
|
// Cleanup all connections
|
|
552
599
|
async cleanup() {
|
|
600
|
+
// Cancel all pending reconnect timers
|
|
601
|
+
for (const name of this.reconnectTimers.keys()) {
|
|
602
|
+
this.cancelReconnect(name);
|
|
603
|
+
}
|
|
553
604
|
const disconnectPromises = Array.from(this.connections.keys()).map((name) => this.disconnectServer(name));
|
|
554
605
|
await Promise.all(disconnectPromises);
|
|
555
606
|
}
|
|
607
|
+
/**
|
|
608
|
+
* Update credentials and reconnect MCP servers that use template variables.
|
|
609
|
+
* Called after SSO login to refresh ${WAVE_SSO_TOKEN} and ${WAVE_SERVER_URL}.
|
|
610
|
+
*/
|
|
611
|
+
async refreshCredentials(serverUrl, ssoToken) {
|
|
612
|
+
// Update resolver context
|
|
613
|
+
this.resolverCtx = {
|
|
614
|
+
serverUrl: serverUrl ?? this.resolverCtx?.serverUrl,
|
|
615
|
+
ssoToken: ssoToken ?? this.resolverCtx?.ssoToken,
|
|
616
|
+
};
|
|
617
|
+
logger?.info(`MCP refreshCredentials: serverUrl=${serverUrl}, hasToken=${!!ssoToken}`);
|
|
618
|
+
// Collect servers that need reconnection
|
|
619
|
+
const serversToReconnect = [];
|
|
620
|
+
for (const [name, server] of this.servers) {
|
|
621
|
+
// Re-resolve config with new credentials
|
|
622
|
+
const originalConfig = server.config;
|
|
623
|
+
const resolvedConfig = resolveMcpTemplates(originalConfig, this.resolverCtx);
|
|
624
|
+
// Update the stored config
|
|
625
|
+
this.servers.set(name, {
|
|
626
|
+
...server,
|
|
627
|
+
config: resolvedConfig,
|
|
628
|
+
});
|
|
629
|
+
if (this.config && this.config.mcpServers[name]) {
|
|
630
|
+
this.config.mcpServers[name] = resolvedConfig;
|
|
631
|
+
}
|
|
632
|
+
// Determine if reconnection is needed
|
|
633
|
+
const wasConnected = this.connections.has(name);
|
|
634
|
+
const wasDisconnected = server.status === "disconnected" || server.status === "error";
|
|
635
|
+
if (wasConnected) {
|
|
636
|
+
// Disconnect first, then reconnect with new resolved config
|
|
637
|
+
await this.disconnectServer(name);
|
|
638
|
+
serversToReconnect.push(name);
|
|
639
|
+
}
|
|
640
|
+
else if (wasDisconnected) {
|
|
641
|
+
// Was disconnected or errored — try to reconnect now that we have credentials
|
|
642
|
+
serversToReconnect.push(name);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
// Reconnect servers
|
|
646
|
+
for (const name of serversToReconnect) {
|
|
647
|
+
logger?.debug(`Reconnecting MCP server after credential refresh: ${name}`);
|
|
648
|
+
this.connectServer(name)
|
|
649
|
+
.then((success) => {
|
|
650
|
+
if (success) {
|
|
651
|
+
logger?.info(`Successfully reconnected MCP server: ${name}`);
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
logger?.warn(`Failed to reconnect MCP server: ${name}`);
|
|
655
|
+
}
|
|
656
|
+
})
|
|
657
|
+
.catch((error) => {
|
|
658
|
+
logger?.error(`Reconnection to MCP server ${name} failed:`, error);
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
// Trigger state change callback
|
|
662
|
+
this.callbacks.onMcpServersChange?.(this.getAllServers());
|
|
663
|
+
}
|
|
556
664
|
// ========== Tools Registry Methods ==========
|
|
557
665
|
/**
|
|
558
666
|
* Get all currently available MCP tools as plugins
|
|
@@ -7,7 +7,20 @@
|
|
|
7
7
|
import type { AuthConfig, AuthUser } from "../types/auth.js";
|
|
8
8
|
export declare class AuthService {
|
|
9
9
|
private static instance;
|
|
10
|
+
private _serverUrl;
|
|
11
|
+
private onAuthChangeCallbacks;
|
|
10
12
|
static getInstance(): AuthService;
|
|
13
|
+
/**
|
|
14
|
+
* Set server URL programmatically (e.g. from AgentOptions.serverUrl).
|
|
15
|
+
* Takes priority over WAVE_SERVER_URL environment variable.
|
|
16
|
+
*/
|
|
17
|
+
setServerUrl(url: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Register a callback for auth state changes.
|
|
20
|
+
* Returns an unsubscribe function.
|
|
21
|
+
*/
|
|
22
|
+
onAuthChange(callback: (event: "login" | "logout") => void): () => void;
|
|
23
|
+
private notifyAuthChange;
|
|
11
24
|
getAuthPath(): string;
|
|
12
25
|
loadAuth(): AuthConfig;
|
|
13
26
|
saveAuth(config: AuthConfig): void;
|
|
@@ -19,6 +32,8 @@ export declare class AuthService {
|
|
|
19
32
|
onAuthUrl?: (url: string) => void;
|
|
20
33
|
/** Read authorization code manually (e.g. from stdin). Resolves with code or rejects on cancel. */
|
|
21
34
|
readToken?: () => Promise<string>;
|
|
35
|
+
/** Server URL override. Falls back to setServerUrl() or WAVE_SERVER_URL env var. */
|
|
36
|
+
serverUrl?: string;
|
|
22
37
|
}): Promise<string>;
|
|
23
38
|
/**
|
|
24
39
|
* Exchange a short-lived authorization code for a JWT token.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authService.d.ts","sourceRoot":"","sources":["../../src/services/authService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAI7D,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAc;
|
|
1
|
+
{"version":3,"file":"authService.d.ts","sourceRoot":"","sources":["../../src/services/authService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAI7D,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAc;IACrC,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,qBAAqB,CACxB;IAEL,MAAM,CAAC,WAAW,IAAI,WAAW;IAOjC;;;OAGG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI/B;;;OAGG;IACH,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI;IASvE,OAAO,CAAC,gBAAgB;IAUxB,WAAW,IAAI,MAAM;IAKrB,QAAQ,IAAI,UAAU;IAatB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAUlC,SAAS,IAAI,IAAI;IAcjB,WAAW,IAAI,MAAM,GAAG,SAAS;IAKjC,YAAY,IAAI,MAAM;IAUhB,KAAK,CAAC,OAAO,CAAC,EAAE;QACpB,6DAA6D;QAC7D,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAClC,mGAAmG;QACnG,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;QAClC,oFAAoF;QACpF,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBnB;;;OAGG;YACW,YAAY;IA0B1B,OAAO,CAAC,oBAAoB;YAoGd,WAAW;IAmBzB,kBAAkB,IAAI,OAAO;IAI7B,WAAW,IAAI,QAAQ,GAAG,SAAS;CAIpC;AAED,eAAO,MAAM,WAAW,aAA4B,CAAC"}
|
|
@@ -13,12 +13,42 @@ import { execFile } from "child_process";
|
|
|
13
13
|
import { promisify } from "util";
|
|
14
14
|
const execFileAsync = promisify(execFile);
|
|
15
15
|
export class AuthService {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.onAuthChangeCallbacks = [];
|
|
18
|
+
}
|
|
16
19
|
static getInstance() {
|
|
17
20
|
if (!AuthService.instance) {
|
|
18
21
|
AuthService.instance = new AuthService();
|
|
19
22
|
}
|
|
20
23
|
return AuthService.instance;
|
|
21
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Set server URL programmatically (e.g. from AgentOptions.serverUrl).
|
|
27
|
+
* Takes priority over WAVE_SERVER_URL environment variable.
|
|
28
|
+
*/
|
|
29
|
+
setServerUrl(url) {
|
|
30
|
+
this._serverUrl = url;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Register a callback for auth state changes.
|
|
34
|
+
* Returns an unsubscribe function.
|
|
35
|
+
*/
|
|
36
|
+
onAuthChange(callback) {
|
|
37
|
+
this.onAuthChangeCallbacks.push(callback);
|
|
38
|
+
return () => {
|
|
39
|
+
this.onAuthChangeCallbacks = this.onAuthChangeCallbacks.filter((cb) => cb !== callback);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
notifyAuthChange(event) {
|
|
43
|
+
for (const cb of this.onAuthChangeCallbacks) {
|
|
44
|
+
try {
|
|
45
|
+
cb(event);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Don't let callback errors break auth flow
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
22
52
|
getAuthPath() {
|
|
23
53
|
const homeDir = os.homedir();
|
|
24
54
|
return path.join(homeDir, ".wave", "auth.json");
|
|
@@ -57,20 +87,21 @@ export class AuthService {
|
|
|
57
87
|
else {
|
|
58
88
|
this.saveAuth(config);
|
|
59
89
|
}
|
|
90
|
+
this.notifyAuthChange("logout");
|
|
60
91
|
}
|
|
61
92
|
getSSOToken() {
|
|
62
93
|
const config = this.loadAuth();
|
|
63
94
|
return config.SSO_TOKEN;
|
|
64
95
|
}
|
|
65
96
|
getServerUrl() {
|
|
66
|
-
const url = process.env.WAVE_SERVER_URL;
|
|
97
|
+
const url = this._serverUrl || process.env.WAVE_SERVER_URL;
|
|
67
98
|
if (!url) {
|
|
68
99
|
throw new Error("WAVE_SERVER_URL environment variable is not set. SSO authentication requires this to be configured.");
|
|
69
100
|
}
|
|
70
101
|
return url;
|
|
71
102
|
}
|
|
72
103
|
async login(options) {
|
|
73
|
-
const serverUrl = this.getServerUrl();
|
|
104
|
+
const serverUrl = options?.serverUrl || this.getServerUrl();
|
|
74
105
|
// Start local server, open browser, wait for callback or manual input
|
|
75
106
|
const { code } = await this.startLocalAuthServer(serverUrl, {
|
|
76
107
|
onAuthUrl: options?.onAuthUrl,
|
|
@@ -81,6 +112,7 @@ export class AuthService {
|
|
|
81
112
|
// Save the token and user info (preserve existing keys)
|
|
82
113
|
const existing = this.loadAuth();
|
|
83
114
|
this.saveAuth({ ...existing, SSO_TOKEN: token, user });
|
|
115
|
+
this.notifyAuthChange("login");
|
|
84
116
|
return token;
|
|
85
117
|
}
|
|
86
118
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containerSetup.d.ts","sourceRoot":"","sources":["../../src/utils/containerSetup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAwB3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAM3E,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EACV,cAAc,EACd,KAAK,EACL,IAAI,EACJ,cAAc,EAEf,MAAM,mBAAmB,CAAC;AAK3B,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAGhB,uBAAuB,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;IAC3D,aAAa,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACvC,sBAAsB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACvD,wBAAwB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACzD,iBAAiB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAClD,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,0BAA0B,GACvC,SAAS,
|
|
1
|
+
{"version":3,"file":"containerSetup.d.ts","sourceRoot":"","sources":["../../src/utils/containerSetup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAwB3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAM3E,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EACV,cAAc,EACd,KAAK,EACL,IAAI,EACJ,cAAc,EAEf,MAAM,mBAAmB,CAAC;AAK3B,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAGhB,uBAAuB,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;IAC3D,aAAa,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACvC,sBAAsB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACvD,wBAAwB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACzD,iBAAiB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAClD,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,0BAA0B,GACvC,SAAS,CA6RX"}
|
|
@@ -91,6 +91,9 @@ export function setupAgentContainer(setupOptions) {
|
|
|
91
91
|
container.register("BackgroundTaskManager", backgroundTaskManager);
|
|
92
92
|
const ssoToken = authService.getSSOToken();
|
|
93
93
|
const serverUrl = options.serverUrl || process.env.WAVE_SERVER_URL;
|
|
94
|
+
if (options.serverUrl) {
|
|
95
|
+
authService.setServerUrl(options.serverUrl);
|
|
96
|
+
}
|
|
94
97
|
const mcpManager = new McpManager(container, {
|
|
95
98
|
callbacks,
|
|
96
99
|
mcpServers: options.mcpServers,
|
|
@@ -98,6 +101,14 @@ export function setupAgentContainer(setupOptions) {
|
|
|
98
101
|
ssoToken,
|
|
99
102
|
});
|
|
100
103
|
container.register("McpManager", mcpManager);
|
|
104
|
+
// Wire up auth change callback to reconnect MCP servers after SSO login
|
|
105
|
+
authService.onAuthChange((event) => {
|
|
106
|
+
if (event === "login") {
|
|
107
|
+
const newServerUrl = options.serverUrl || process.env.WAVE_SERVER_URL;
|
|
108
|
+
const newToken = authService.getSSOToken();
|
|
109
|
+
mcpManager.refreshCredentials(newServerUrl, newToken);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
101
112
|
const lspManager = options.lspManager || new LspManager(container);
|
|
102
113
|
container.register("LspManager", lspManager);
|
|
103
114
|
const permissionManager = new PermissionManager(container, {
|
package/package.json
CHANGED
|
@@ -184,6 +184,8 @@ export class McpManager {
|
|
|
184
184
|
private mcpServers: Record<string, McpServerConfig> | undefined;
|
|
185
185
|
|
|
186
186
|
private resolverCtx: McpResolverContext | undefined;
|
|
187
|
+
private reconnectTimers: Map<string, NodeJS.Timeout> = new Map();
|
|
188
|
+
private reconnectAttempts: Map<string, number> = new Map();
|
|
187
189
|
|
|
188
190
|
constructor(
|
|
189
191
|
private container: Container,
|
|
@@ -353,9 +355,15 @@ export class McpManager {
|
|
|
353
355
|
return false;
|
|
354
356
|
}
|
|
355
357
|
|
|
358
|
+
// Resolve template variables before storing
|
|
359
|
+
const resolvedConfig = resolveMcpTemplates(
|
|
360
|
+
config,
|
|
361
|
+
this.resolverCtx ?? { serverUrl: undefined, ssoToken: undefined },
|
|
362
|
+
);
|
|
363
|
+
|
|
356
364
|
const newServer: McpServerStatus = {
|
|
357
365
|
name,
|
|
358
|
-
config,
|
|
366
|
+
config: resolvedConfig,
|
|
359
367
|
status: "disconnected",
|
|
360
368
|
};
|
|
361
369
|
|
|
@@ -363,10 +371,10 @@ export class McpManager {
|
|
|
363
371
|
|
|
364
372
|
// Update config
|
|
365
373
|
if (this.config) {
|
|
366
|
-
this.config.mcpServers[name] =
|
|
374
|
+
this.config.mcpServers[name] = resolvedConfig;
|
|
367
375
|
} else {
|
|
368
376
|
this.config = {
|
|
369
|
-
mcpServers: { [name]:
|
|
377
|
+
mcpServers: { [name]: resolvedConfig },
|
|
370
378
|
};
|
|
371
379
|
}
|
|
372
380
|
|
|
@@ -554,6 +562,10 @@ export class McpManager {
|
|
|
554
562
|
tools: [],
|
|
555
563
|
toolCount: 0,
|
|
556
564
|
});
|
|
565
|
+
// Auto-reconnect with exponential backoff (not triggered by explicit disconnect)
|
|
566
|
+
if (!this.reconnectTimers.has(name)) {
|
|
567
|
+
this.scheduleReconnect(name);
|
|
568
|
+
}
|
|
557
569
|
};
|
|
558
570
|
|
|
559
571
|
// Store connection
|
|
@@ -585,7 +597,53 @@ export class McpManager {
|
|
|
585
597
|
}
|
|
586
598
|
}
|
|
587
599
|
|
|
600
|
+
/**
|
|
601
|
+
* Schedule auto-reconnect with exponential backoff.
|
|
602
|
+
* Delays: 1s, 2s, 4s, 8s, 16s, 30s, 30s, ...
|
|
603
|
+
*/
|
|
604
|
+
private scheduleReconnect(name: string): void {
|
|
605
|
+
const attempts = this.reconnectAttempts.get(name) ?? 0;
|
|
606
|
+
const delay = Math.min(1000 * Math.pow(2, attempts), 30000);
|
|
607
|
+
this.reconnectAttempts.set(name, attempts + 1);
|
|
608
|
+
|
|
609
|
+
logger?.info(
|
|
610
|
+
`Scheduling MCP server ${name} reconnect in ${delay}ms (attempt ${attempts + 1})`,
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
const timer = setTimeout(() => {
|
|
614
|
+
this.reconnectTimers.delete(name);
|
|
615
|
+
logger?.debug(`Auto-reconnecting MCP server: ${name}`);
|
|
616
|
+
this.connectServer(name)
|
|
617
|
+
.then((success) => {
|
|
618
|
+
if (success) {
|
|
619
|
+
logger?.info(`Auto-reconnected MCP server: ${name}`);
|
|
620
|
+
this.reconnectAttempts.delete(name);
|
|
621
|
+
} else {
|
|
622
|
+
logger?.warn(`Auto-reconnect failed for MCP server: ${name}`);
|
|
623
|
+
// Will be retried via onclose handler
|
|
624
|
+
}
|
|
625
|
+
})
|
|
626
|
+
.catch((error) => {
|
|
627
|
+
logger?.error(`Auto-reconnect error for MCP server ${name}:`, error);
|
|
628
|
+
});
|
|
629
|
+
}, delay);
|
|
630
|
+
|
|
631
|
+
this.reconnectTimers.set(name, timer);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
private cancelReconnect(name: string): void {
|
|
635
|
+
const timer = this.reconnectTimers.get(name);
|
|
636
|
+
if (timer) {
|
|
637
|
+
clearTimeout(timer);
|
|
638
|
+
this.reconnectTimers.delete(name);
|
|
639
|
+
}
|
|
640
|
+
this.reconnectAttempts.delete(name);
|
|
641
|
+
}
|
|
642
|
+
|
|
588
643
|
async disconnectServer(name: string): Promise<boolean> {
|
|
644
|
+
// Cancel any pending reconnect attempts
|
|
645
|
+
this.cancelReconnect(name);
|
|
646
|
+
|
|
589
647
|
const connection = this.connections.get(name);
|
|
590
648
|
if (!connection) return false;
|
|
591
649
|
|
|
@@ -749,12 +807,92 @@ export class McpManager {
|
|
|
749
807
|
|
|
750
808
|
// Cleanup all connections
|
|
751
809
|
async cleanup(): Promise<void> {
|
|
810
|
+
// Cancel all pending reconnect timers
|
|
811
|
+
for (const name of this.reconnectTimers.keys()) {
|
|
812
|
+
this.cancelReconnect(name);
|
|
813
|
+
}
|
|
752
814
|
const disconnectPromises = Array.from(this.connections.keys()).map((name) =>
|
|
753
815
|
this.disconnectServer(name),
|
|
754
816
|
);
|
|
755
817
|
await Promise.all(disconnectPromises);
|
|
756
818
|
}
|
|
757
819
|
|
|
820
|
+
/**
|
|
821
|
+
* Update credentials and reconnect MCP servers that use template variables.
|
|
822
|
+
* Called after SSO login to refresh ${WAVE_SSO_TOKEN} and ${WAVE_SERVER_URL}.
|
|
823
|
+
*/
|
|
824
|
+
async refreshCredentials(
|
|
825
|
+
serverUrl?: string,
|
|
826
|
+
ssoToken?: string,
|
|
827
|
+
): Promise<void> {
|
|
828
|
+
// Update resolver context
|
|
829
|
+
this.resolverCtx = {
|
|
830
|
+
serverUrl: serverUrl ?? this.resolverCtx?.serverUrl,
|
|
831
|
+
ssoToken: ssoToken ?? this.resolverCtx?.ssoToken,
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
logger?.info(
|
|
835
|
+
`MCP refreshCredentials: serverUrl=${serverUrl}, hasToken=${!!ssoToken}`,
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
// Collect servers that need reconnection
|
|
839
|
+
const serversToReconnect: string[] = [];
|
|
840
|
+
|
|
841
|
+
for (const [name, server] of this.servers) {
|
|
842
|
+
// Re-resolve config with new credentials
|
|
843
|
+
const originalConfig = server.config;
|
|
844
|
+
const resolvedConfig = resolveMcpTemplates(
|
|
845
|
+
originalConfig,
|
|
846
|
+
this.resolverCtx,
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
// Update the stored config
|
|
850
|
+
this.servers.set(name, {
|
|
851
|
+
...server,
|
|
852
|
+
config: resolvedConfig,
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
if (this.config && this.config.mcpServers[name]) {
|
|
856
|
+
this.config.mcpServers[name] = resolvedConfig;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Determine if reconnection is needed
|
|
860
|
+
const wasConnected = this.connections.has(name);
|
|
861
|
+
const wasDisconnected =
|
|
862
|
+
server.status === "disconnected" || server.status === "error";
|
|
863
|
+
|
|
864
|
+
if (wasConnected) {
|
|
865
|
+
// Disconnect first, then reconnect with new resolved config
|
|
866
|
+
await this.disconnectServer(name);
|
|
867
|
+
serversToReconnect.push(name);
|
|
868
|
+
} else if (wasDisconnected) {
|
|
869
|
+
// Was disconnected or errored — try to reconnect now that we have credentials
|
|
870
|
+
serversToReconnect.push(name);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Reconnect servers
|
|
875
|
+
for (const name of serversToReconnect) {
|
|
876
|
+
logger?.debug(
|
|
877
|
+
`Reconnecting MCP server after credential refresh: ${name}`,
|
|
878
|
+
);
|
|
879
|
+
this.connectServer(name)
|
|
880
|
+
.then((success) => {
|
|
881
|
+
if (success) {
|
|
882
|
+
logger?.info(`Successfully reconnected MCP server: ${name}`);
|
|
883
|
+
} else {
|
|
884
|
+
logger?.warn(`Failed to reconnect MCP server: ${name}`);
|
|
885
|
+
}
|
|
886
|
+
})
|
|
887
|
+
.catch((error) => {
|
|
888
|
+
logger?.error(`Reconnection to MCP server ${name} failed:`, error);
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Trigger state change callback
|
|
893
|
+
this.callbacks.onMcpServersChange?.(this.getAllServers());
|
|
894
|
+
}
|
|
895
|
+
|
|
758
896
|
// ========== Tools Registry Methods ==========
|
|
759
897
|
|
|
760
898
|
/**
|
|
@@ -25,6 +25,9 @@ const execFileAsync = promisify(execFile);
|
|
|
25
25
|
|
|
26
26
|
export class AuthService {
|
|
27
27
|
private static instance: AuthService;
|
|
28
|
+
private _serverUrl: string | undefined;
|
|
29
|
+
private onAuthChangeCallbacks: Array<(event: "login" | "logout") => void> =
|
|
30
|
+
[];
|
|
28
31
|
|
|
29
32
|
static getInstance(): AuthService {
|
|
30
33
|
if (!AuthService.instance) {
|
|
@@ -33,6 +36,37 @@ export class AuthService {
|
|
|
33
36
|
return AuthService.instance;
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Set server URL programmatically (e.g. from AgentOptions.serverUrl).
|
|
41
|
+
* Takes priority over WAVE_SERVER_URL environment variable.
|
|
42
|
+
*/
|
|
43
|
+
setServerUrl(url: string): void {
|
|
44
|
+
this._serverUrl = url;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Register a callback for auth state changes.
|
|
49
|
+
* Returns an unsubscribe function.
|
|
50
|
+
*/
|
|
51
|
+
onAuthChange(callback: (event: "login" | "logout") => void): () => void {
|
|
52
|
+
this.onAuthChangeCallbacks.push(callback);
|
|
53
|
+
return () => {
|
|
54
|
+
this.onAuthChangeCallbacks = this.onAuthChangeCallbacks.filter(
|
|
55
|
+
(cb) => cb !== callback,
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private notifyAuthChange(event: "login" | "logout"): void {
|
|
61
|
+
for (const cb of this.onAuthChangeCallbacks) {
|
|
62
|
+
try {
|
|
63
|
+
cb(event);
|
|
64
|
+
} catch {
|
|
65
|
+
// Don't let callback errors break auth flow
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
36
70
|
getAuthPath(): string {
|
|
37
71
|
const homeDir = os.homedir();
|
|
38
72
|
return path.join(homeDir, ".wave", "auth.json");
|
|
@@ -72,6 +106,7 @@ export class AuthService {
|
|
|
72
106
|
} else {
|
|
73
107
|
this.saveAuth(config);
|
|
74
108
|
}
|
|
109
|
+
this.notifyAuthChange("logout");
|
|
75
110
|
}
|
|
76
111
|
|
|
77
112
|
getSSOToken(): string | undefined {
|
|
@@ -80,7 +115,7 @@ export class AuthService {
|
|
|
80
115
|
}
|
|
81
116
|
|
|
82
117
|
getServerUrl(): string {
|
|
83
|
-
const url = process.env.WAVE_SERVER_URL;
|
|
118
|
+
const url = this._serverUrl || process.env.WAVE_SERVER_URL;
|
|
84
119
|
if (!url) {
|
|
85
120
|
throw new Error(
|
|
86
121
|
"WAVE_SERVER_URL environment variable is not set. SSO authentication requires this to be configured.",
|
|
@@ -94,8 +129,10 @@ export class AuthService {
|
|
|
94
129
|
onAuthUrl?: (url: string) => void;
|
|
95
130
|
/** Read authorization code manually (e.g. from stdin). Resolves with code or rejects on cancel. */
|
|
96
131
|
readToken?: () => Promise<string>;
|
|
132
|
+
/** Server URL override. Falls back to setServerUrl() or WAVE_SERVER_URL env var. */
|
|
133
|
+
serverUrl?: string;
|
|
97
134
|
}): Promise<string> {
|
|
98
|
-
const serverUrl = this.getServerUrl();
|
|
135
|
+
const serverUrl = options?.serverUrl || this.getServerUrl();
|
|
99
136
|
|
|
100
137
|
// Start local server, open browser, wait for callback or manual input
|
|
101
138
|
const { code } = await this.startLocalAuthServer(serverUrl, {
|
|
@@ -110,6 +147,8 @@ export class AuthService {
|
|
|
110
147
|
const existing = this.loadAuth();
|
|
111
148
|
this.saveAuth({ ...existing, SSO_TOKEN: token, user });
|
|
112
149
|
|
|
150
|
+
this.notifyAuthChange("login");
|
|
151
|
+
|
|
113
152
|
return token;
|
|
114
153
|
}
|
|
115
154
|
|
|
@@ -148,6 +148,9 @@ export function setupAgentContainer(
|
|
|
148
148
|
|
|
149
149
|
const ssoToken = authService.getSSOToken();
|
|
150
150
|
const serverUrl = options.serverUrl || process.env.WAVE_SERVER_URL;
|
|
151
|
+
if (options.serverUrl) {
|
|
152
|
+
authService.setServerUrl(options.serverUrl);
|
|
153
|
+
}
|
|
151
154
|
const mcpManager = new McpManager(container, {
|
|
152
155
|
callbacks,
|
|
153
156
|
mcpServers: options.mcpServers as
|
|
@@ -158,6 +161,15 @@ export function setupAgentContainer(
|
|
|
158
161
|
});
|
|
159
162
|
container.register("McpManager", mcpManager);
|
|
160
163
|
|
|
164
|
+
// Wire up auth change callback to reconnect MCP servers after SSO login
|
|
165
|
+
authService.onAuthChange((event) => {
|
|
166
|
+
if (event === "login") {
|
|
167
|
+
const newServerUrl = options.serverUrl || process.env.WAVE_SERVER_URL;
|
|
168
|
+
const newToken = authService.getSSOToken();
|
|
169
|
+
mcpManager.refreshCredentials(newServerUrl, newToken);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
161
173
|
const lspManager = options.lspManager || new LspManager(container);
|
|
162
174
|
container.register("LspManager", lspManager);
|
|
163
175
|
|