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.
@@ -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":"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"}
1
+ {"version":3,"file":"mcpManager.d.ts","sourceRoot":"","sources":["../../src/managers/mcpManager.ts"],"names":[],"mappings":"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"}
@@ -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] = config;
265
+ this.config.mcpServers[name] = resolvedConfig;
262
266
  }
263
267
  else {
264
268
  this.config = {
265
- mcpServers: { [name]: config },
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;IAErC,MAAM,CAAC,WAAW,IAAI,WAAW;IAOjC,WAAW,IAAI,MAAM;IAKrB,QAAQ,IAAI,UAAU;IAatB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAUlC,SAAS,IAAI,IAAI;IAajB,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;KACnC,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBnB;;;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"}
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,CAiRX"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "0.16.5",
3
+ "version": "0.16.7",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
@@ -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] = config;
374
+ this.config.mcpServers[name] = resolvedConfig;
367
375
  } else {
368
376
  this.config = {
369
- mcpServers: { [name]: config },
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