@lanonasis/cli 3.3.15 → 3.5.15

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.
Files changed (38) hide show
  1. package/README.md +57 -13
  2. package/dist/commands/auth.js +11 -5
  3. package/dist/commands/completion.js +2 -0
  4. package/dist/commands/config.js +4 -4
  5. package/dist/commands/enhanced-memory.js +1 -1
  6. package/dist/commands/mcp.js +15 -9
  7. package/dist/core/achievements.js +1 -1
  8. package/dist/core/power-mode.js +5 -3
  9. package/dist/core/welcome.js +6 -6
  10. package/dist/enhanced-cli.js +6 -3
  11. package/dist/index-simple.js +5 -1
  12. package/dist/index.js +5 -1
  13. package/dist/mcp/access-control.d.ts +1 -1
  14. package/dist/mcp/access-control.js +4 -4
  15. package/dist/mcp/client/enhanced-client.js +5 -7
  16. package/dist/mcp/schemas/tool-schemas.d.ts +1 -1
  17. package/dist/mcp/schemas/tool-schemas.js +1 -1
  18. package/dist/mcp/server/lanonasis-server.d.ts +2 -1
  19. package/dist/mcp/server/lanonasis-server.js +7 -5
  20. package/dist/mcp/transports/transport-manager.js +3 -3
  21. package/dist/mcp-server.d.ts +1 -0
  22. package/dist/mcp-server.js +43 -9
  23. package/dist/utils/config.d.ts +10 -1
  24. package/dist/utils/config.js +97 -17
  25. package/dist/utils/mcp-client.d.ts +21 -2
  26. package/dist/utils/mcp-client.js +117 -46
  27. package/package.json +3 -3
  28. package/dist/__tests__/auth-persistence.test.d.ts +0 -1
  29. package/dist/__tests__/auth-persistence.test.js +0 -243
  30. package/dist/__tests__/cross-device-integration.test.d.ts +0 -1
  31. package/dist/__tests__/cross-device-integration.test.js +0 -305
  32. package/dist/__tests__/mcp-connection-reliability.test.d.ts +0 -1
  33. package/dist/__tests__/mcp-connection-reliability.test.js +0 -489
  34. package/dist/__tests__/setup.d.ts +0 -1
  35. package/dist/__tests__/setup.js +0 -26
  36. package/dist/mcp/server/mcp/server/lanonasis-server.js +0 -911
  37. package/dist/mcp/server/utils/api.js +0 -431
  38. package/dist/mcp/server/utils/config.js +0 -855
package/dist/index.js CHANGED
@@ -52,7 +52,11 @@ program
52
52
  }
53
53
  process.env.CLI_OUTPUT_FORMAT = opts.output;
54
54
  // Auto-initialize MCP unless disabled
55
- if (opts.mcp !== false && !['init', 'auth', 'login', 'mcp', 'health', 'status'].includes(actionCommand.name())) {
55
+ const isMcpFlow = actionCommand.name() === 'mcp' ||
56
+ actionCommand.parent?.name?.() === 'mcp' ||
57
+ actionCommand.name() === 'mcp-server' ||
58
+ actionCommand.parent?.name?.() === 'mcp-server';
59
+ if (opts.mcp !== false && !isMcpFlow && !['init', 'auth', 'login', 'health', 'status'].includes(actionCommand.name())) {
56
60
  try {
57
61
  const client = getMCPClient();
58
62
  if (!client.isConnectedToServer()) {
@@ -50,7 +50,7 @@ export declare class MemoryAccessControl {
50
50
  /**
51
51
  * Revoke access to a memory or app
52
52
  */
53
- revokeAccess(userId: string, appId: string, memoryId?: string): Promise<void>;
53
+ revokeAccess(_userId: string, _appId: string, memoryId?: string): Promise<void>;
54
54
  /**
55
55
  * Get access logs for audit purposes
56
56
  */
@@ -123,8 +123,8 @@ export class MemoryAccessControl {
123
123
  /**
124
124
  * Revoke access to a memory or app
125
125
  */
126
- async revokeAccess(userId, appId, memoryId) {
127
- const key = `${userId}:${appId}`;
126
+ async revokeAccess(_userId, _appId, memoryId) {
127
+ const key = `${_userId}:${_appId}`;
128
128
  const existingRules = this.accessRules.get(key) || [];
129
129
  const updatedRules = existingRules.map(rule => {
130
130
  if (!memoryId || rule.memory_id === memoryId) {
@@ -133,7 +133,7 @@ export class MemoryAccessControl {
133
133
  return rule;
134
134
  });
135
135
  this.accessRules.set(key, updatedRules);
136
- logger.info('Access revoked', { userId, appId, memoryId });
136
+ logger.info('Access revoked', { userId: _userId, appId: _appId, memoryId });
137
137
  }
138
138
  /**
139
139
  * Get access logs for audit purposes
@@ -215,7 +215,7 @@ export class MemoryAccessControl {
215
215
  return [];
216
216
  }
217
217
  }
218
- async getSharedMemories(userId, appId) {
218
+ async getSharedMemories(_userId, _appId) {
219
219
  // This would implement logic to find memories shared with the user
220
220
  // through explicit permissions or app-level sharing
221
221
  return [];
@@ -59,7 +59,7 @@ export class EnhancedMCPClient extends EventEmitter {
59
59
  const maxRetries = config.maxRetries || 3;
60
60
  const timeout = config.timeout || 30000;
61
61
  let attempts = 0;
62
- while (attempts < maxRetries) {
62
+ while (true) {
63
63
  try {
64
64
  this.updateConnectionStatus(config.name, 'connecting');
65
65
  const client = await this.createClientWithTimeout(config, timeout);
@@ -82,7 +82,6 @@ export class EnhancedMCPClient extends EventEmitter {
82
82
  await this.delay(delay);
83
83
  }
84
84
  }
85
- return false;
86
85
  }
87
86
  /**
88
87
  * Create client with timeout
@@ -115,7 +114,6 @@ export class EnhancedMCPClient extends EventEmitter {
115
114
  // HTTP transport is not directly supported by MCP SDK
116
115
  // Use stdio or websocket instead
117
116
  throw new Error('HTTP transport not directly supported. Use websocket or stdio transport.');
118
- break;
119
117
  case 'websocket':
120
118
  if (!config.url) {
121
119
  throw new Error('URL required for websocket transport');
@@ -225,7 +223,7 @@ export class EnhancedMCPClient extends EventEmitter {
225
223
  /**
226
224
  * Select the best server for a tool based on availability and latency
227
225
  */
228
- async selectBestServer(toolName) {
226
+ async selectBestServer(_toolName) {
229
227
  const availableServers = Array.from(this.clients.keys()).filter(name => {
230
228
  const status = this.connectionStatus.get(name);
231
229
  return status?.status === 'connected';
@@ -240,7 +238,7 @@ export class EnhancedMCPClient extends EventEmitter {
240
238
  /**
241
239
  * Select a failover server
242
240
  */
243
- async selectFailoverServer(excludeServer, toolName) {
241
+ async selectFailoverServer(excludeServer, _toolName) {
244
242
  const availableServers = Array.from(this.clients.keys()).filter(name => {
245
243
  const status = this.connectionStatus.get(name);
246
244
  return name !== excludeServer && status?.status === 'connected';
@@ -318,7 +316,7 @@ export class EnhancedMCPClient extends EventEmitter {
318
316
  /**
319
317
  * Get server configuration (placeholder - should be stored during connect)
320
318
  */
321
- getServerConfig(serverName) {
319
+ getServerConfig(_serverName) {
322
320
  // TODO: Store and retrieve server configs
323
321
  return null;
324
322
  }
@@ -343,7 +341,7 @@ export class EnhancedMCPClient extends EventEmitter {
343
341
  await client.close();
344
342
  console.log(chalk.gray(`Disconnected from ${name}`));
345
343
  }
346
- catch (error) {
344
+ catch {
347
345
  console.log(chalk.yellow(`Warning: Error disconnecting from ${name}`));
348
346
  }
349
347
  }
@@ -371,7 +371,7 @@ export declare class SchemaValidator {
371
371
  /**
372
372
  * Get schema as JSON Schema for documentation
373
373
  */
374
- static toJsonSchema(schema: z.ZodSchema<any>): any;
374
+ static toJsonSchema(_schema: z.ZodSchema<any>): any;
375
375
  }
376
376
  export declare const MCPSchemas: {
377
377
  memory: {
@@ -333,7 +333,7 @@ export class SchemaValidator {
333
333
  /**
334
334
  * Get schema as JSON Schema for documentation
335
335
  */
336
- static toJsonSchema(schema) {
336
+ static toJsonSchema(_schema) {
337
337
  // This would require zodToJsonSchema package
338
338
  // For now, return a simplified version
339
339
  return {
@@ -59,7 +59,8 @@ export declare class LanonasisMCPServer {
59
59
  */
60
60
  private registerPrompts; /**
61
61
 
62
- * Handle tool calls
62
+ /**
63
+ * Handle tool calls
63
64
  */
64
65
  private handleToolCall;
65
66
  /**
@@ -600,9 +600,10 @@ Please choose an option (1-4):`
600
600
  });
601
601
  } /**
602
602
 
603
- * Handle tool calls
603
+ /**
604
+ * Handle tool calls
604
605
  */
605
- async handleToolCall(name, args, clientId) {
606
+ async handleToolCall(name, args, _clientId) {
606
607
  // Ensure we're initialized
607
608
  if (!this.apiClient) {
608
609
  await this.initialize();
@@ -666,7 +667,7 @@ Please choose an option (1-4):`
666
667
  // Transport management operations
667
668
  case 'transport_status':
668
669
  return this.getTransportStatus();
669
- case 'transport_test':
670
+ case 'transport_test': {
670
671
  if (!args.transport) {
671
672
  throw new Error('transport is required');
672
673
  }
@@ -676,6 +677,7 @@ Please choose an option (1-4):`
676
677
  available: isAvailable,
677
678
  tested_at: new Date().toISOString()
678
679
  };
680
+ }
679
681
  case 'transport_reset_failures':
680
682
  if (args.transport) {
681
683
  this.transportFailures.delete(args.transport);
@@ -1178,7 +1180,7 @@ Please choose an option (1-4):`
1178
1180
  }
1179
1181
  return true;
1180
1182
  }
1181
- catch (error) {
1183
+ catch {
1182
1184
  return false;
1183
1185
  }
1184
1186
  }
@@ -1199,7 +1201,7 @@ Please choose an option (1-4):`
1199
1201
  }
1200
1202
  return true;
1201
1203
  }
1202
- catch (error) {
1204
+ catch {
1203
1205
  return false;
1204
1206
  }
1205
1207
  }
@@ -162,7 +162,7 @@ class WebSocketTransport extends EventEmitter {
162
162
  const message = JSON.parse(data.toString());
163
163
  this.emit('message', message);
164
164
  }
165
- catch (error) {
165
+ catch {
166
166
  this.emit('error', new Error('Failed to parse WebSocket message'));
167
167
  }
168
168
  });
@@ -270,7 +270,7 @@ class SSETransport extends EventEmitter {
270
270
  const data = JSON.parse(event.data);
271
271
  this.emit('message', data);
272
272
  }
273
- catch (error) {
273
+ catch {
274
274
  this.emit('error', new Error('Failed to parse SSE message'));
275
275
  }
276
276
  };
@@ -282,7 +282,7 @@ class SSETransport extends EventEmitter {
282
282
  };
283
283
  });
284
284
  }
285
- async send(data) {
285
+ async send(_data) {
286
286
  // SSE is receive-only, but we can send data via HTTP POST
287
287
  throw new Error('SSE transport is read-only. Use HTTP for sending data.');
288
288
  }
@@ -17,6 +17,7 @@ export declare class CLIMCPServer {
17
17
  * Start MCP server using CLI configuration
18
18
  */
19
19
  start(options?: MCPServerOptions): Promise<void>;
20
+ private resolveMCPServerPath;
20
21
  /**
21
22
  * Start local MCP server using CLI auth config
22
23
  */
@@ -6,10 +6,13 @@
6
6
  */
7
7
  import { fileURLToPath } from 'url';
8
8
  import { dirname, join } from 'path';
9
+ import { existsSync } from 'fs';
10
+ import { createRequire } from 'module';
9
11
  import { spawn } from 'child_process';
10
12
  import { CLIConfig } from './utils/config.js';
11
13
  const __filename = fileURLToPath(import.meta.url);
12
14
  const __dirname = dirname(__filename);
15
+ const nodeRequire = createRequire(import.meta.url);
13
16
  export class CLIMCPServer {
14
17
  config;
15
18
  constructor() {
@@ -20,7 +23,7 @@ export class CLIMCPServer {
20
23
  */
21
24
  async start(options = {}) {
22
25
  await this.config.init();
23
- const { mode = 'stdio', port = 3001, verbose = false, useRemote = this.config.shouldUseRemoteMCP() } = options;
26
+ const { useRemote = this.config.shouldUseRemoteMCP() } = options;
24
27
  if (useRemote) {
25
28
  await this.startRemoteMCP(options);
26
29
  }
@@ -28,13 +31,40 @@ export class CLIMCPServer {
28
31
  await this.startLocalMCP(options);
29
32
  }
30
33
  }
34
+ resolveMCPServerPath() {
35
+ const candidates = new Set();
36
+ if (process.env.MCP_SERVER_PATH) {
37
+ candidates.add(process.env.MCP_SERVER_PATH);
38
+ }
39
+ const packageRequests = [
40
+ '@lanonasis/mcp-server/dist/cli-aligned-mcp-server.js',
41
+ 'lanonasis-mcp-server/dist/cli-aligned-mcp-server.js'
42
+ ];
43
+ for (const request of packageRequests) {
44
+ try {
45
+ const resolved = nodeRequire.resolve(request);
46
+ candidates.add(resolved);
47
+ }
48
+ catch {
49
+ // Ignore resolution failures and continue through fallbacks
50
+ }
51
+ }
52
+ candidates.add(join(process.cwd(), 'mcp-server/dist/cli-aligned-mcp-server.js'));
53
+ candidates.add(join(__dirname, '../../../mcp-server/dist/cli-aligned-mcp-server.js'));
54
+ for (const candidate of candidates) {
55
+ if (candidate && existsSync(candidate)) {
56
+ return candidate;
57
+ }
58
+ }
59
+ throw new Error('Unable to locate the CLI-aligned MCP server. Set MCP_SERVER_PATH or install @lanonasis/mcp-server.');
60
+ }
31
61
  /**
32
62
  * Start local MCP server using CLI auth config
33
63
  */
34
64
  async startLocalMCP(options) {
35
65
  const { mode, port, verbose } = options;
36
- // Path to CLI-aligned MCP server in submodule
37
- const mcpServerPath = join(__dirname, '../../../mcp-server/dist/cli-aligned-mcp-server.js');
66
+ // Path to production MCP server (uses CommonJS, no build needed)
67
+ const mcpServerPath = join(__dirname, '../../../mcp-server/src/production-mcp-server.cjs');
38
68
  const args = mode === 'http' ? ['--http'] : ['--stdio'];
39
69
  if (verbose) {
40
70
  console.error('🚀 Starting CLI-aligned MCP Server...');
@@ -42,10 +72,11 @@ export class CLIMCPServer {
42
72
  console.error(`Config: ~/.maas/config.json`);
43
73
  console.error(`Auth: ${this.config.hasVendorKey() ? 'Vendor Key' : 'JWT Token'}`);
44
74
  }
75
+ const resolvedPort = typeof port === 'number' && !Number.isNaN(port) ? port : 3001;
45
76
  // Set environment variables from CLI config
46
77
  const env = {
47
78
  ...process.env,
48
- PORT: port?.toString(),
79
+ PORT: resolvedPort.toString(),
49
80
  MEMORY_API_URL: this.config.getApiUrl(),
50
81
  LANONASIS_VENDOR_KEY: this.config.getVendorKey(),
51
82
  LANONASIS_TOKEN: this.config.getToken(),
@@ -84,14 +115,13 @@ export class CLIMCPServer {
84
115
  */
85
116
  async startRemoteMCP(options) {
86
117
  const { verbose } = options;
118
+ const message = 'Remote MCP not implemented; remove --remote or use local mode.';
87
119
  if (verbose) {
88
120
  console.error('🌐 Connecting to remote MCP server...');
89
121
  console.error(`URL: ${this.config.getMCPServerUrl()}`);
90
122
  }
91
- // For remote MCP, we'd need to implement a proxy or client
92
- // For now, fall back to local mode
93
- console.error('⚠️ Remote MCP not yet implemented, falling back to local mode');
94
- await this.startLocalMCP({ ...options, useRemote: false });
123
+ console.error(`❌ ${message}`);
124
+ throw new Error(message);
95
125
  }
96
126
  /**
97
127
  * Check if MCP server is available and configured
@@ -149,6 +179,10 @@ Examples:
149
179
  await server.start(options);
150
180
  }
151
181
  if (import.meta.url === `file://${process.argv[1]}`) {
152
- main().catch(console.error);
182
+ main().catch(error => {
183
+ const message = error instanceof Error ? error.message : String(error);
184
+ console.error(message);
185
+ process.exit(1);
186
+ });
153
187
  }
154
188
  export default CLIMCPServer;
@@ -43,6 +43,14 @@ export declare class CLIConfig {
43
43
  private authCheckCache;
44
44
  private readonly AUTH_CACHE_TTL;
45
45
  constructor();
46
+ /**
47
+ * Overrides the configuration storage directory. Primarily used for tests.
48
+ */
49
+ setConfigDirectory(configDir: string): void;
50
+ /**
51
+ * Exposes the current config path for tests and diagnostics.
52
+ */
53
+ getConfigPath(): string;
46
54
  init(): Promise<void>;
47
55
  load(): Promise<void>;
48
56
  private migrateConfigIfNeeded;
@@ -55,6 +63,8 @@ export declare class CLIConfig {
55
63
  discoverServices(verbose?: boolean): Promise<void>;
56
64
  private handleServiceDiscoveryFailure;
57
65
  private categorizeServiceDiscoveryError;
66
+ private resolveFallbackEndpoints;
67
+ private logFallbackUsage;
58
68
  setManualEndpoints(endpoints: Partial<CLIConfigData['discoveredServices']>): Promise<void>;
59
69
  hasManualEndpointOverrides(): boolean;
60
70
  clearManualEndpointOverrides(): Promise<void>;
@@ -71,7 +81,6 @@ export declare class CLIConfig {
71
81
  isAuthenticated(): Promise<boolean>;
72
82
  logout(): Promise<void>;
73
83
  clear(): Promise<void>;
74
- getConfigPath(): string;
75
84
  exists(): Promise<boolean>;
76
85
  validateStoredCredentials(): Promise<boolean>;
77
86
  refreshTokenIfNeeded(): Promise<void>;
@@ -16,6 +16,20 @@ export class CLIConfig {
16
16
  this.configPath = path.join(this.configDir, 'config.json');
17
17
  this.lockFile = path.join(this.configDir, 'config.lock');
18
18
  }
19
+ /**
20
+ * Overrides the configuration storage directory. Primarily used for tests.
21
+ */
22
+ setConfigDirectory(configDir) {
23
+ this.configDir = configDir;
24
+ this.configPath = path.join(configDir, 'config.json');
25
+ this.lockFile = path.join(configDir, 'config.lock');
26
+ }
27
+ /**
28
+ * Exposes the current config path for tests and diagnostics.
29
+ */
30
+ getConfigPath() {
31
+ return this.configPath;
32
+ }
19
33
  async init() {
20
34
  try {
21
35
  await fs.mkdir(this.configDir, { recursive: true });
@@ -229,20 +243,17 @@ export class CLIConfig {
229
243
  return;
230
244
  }
231
245
  }
232
- // Set fallback service endpoints
246
+ const fallback = this.resolveFallbackEndpoints();
233
247
  this.config.discoveredServices = {
234
- auth_base: 'https://api.lanonasis.com', // CLI auth goes to central auth system
235
- memory_base: 'https://api.lanonasis.com/api/v1', // Memory via onasis-core
236
- mcp_base: 'https://mcp.lanonasis.com/api/v1', // MCP HTTP/REST
237
- mcp_ws_base: 'wss://mcp.lanonasis.com/ws', // MCP WebSocket
238
- mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events', // MCP SSE
239
- project_scope: 'lanonasis-maas' // Correct project scope
248
+ ...fallback.endpoints,
249
+ project_scope: 'lanonasis-maas'
240
250
  };
241
251
  // Mark as fallback (don't set lastServiceDiscovery)
242
252
  await this.save();
253
+ this.logFallbackUsage(fallback.source, this.config.discoveredServices);
243
254
  if (verbose) {
244
255
  console.log('✓ Using fallback service endpoints');
245
- console.log(' These are the standard production endpoints');
256
+ console.log(` Source: ${fallback.source === 'environment' ? 'environment overrides' : 'built-in defaults'}`);
246
257
  }
247
258
  }
248
259
  categorizeServiceDiscoveryError(error) {
@@ -272,6 +283,47 @@ export class CLIConfig {
272
283
  }
273
284
  return 'unknown';
274
285
  }
286
+ resolveFallbackEndpoints() {
287
+ const envAuthBase = process.env.LANONASIS_FALLBACK_AUTH_BASE ?? process.env.AUTH_BASE;
288
+ const envMemoryBase = process.env.LANONASIS_FALLBACK_MEMORY_BASE ?? process.env.MEMORY_BASE;
289
+ const envMcpBase = process.env.LANONASIS_FALLBACK_MCP_BASE ?? process.env.MCP_BASE;
290
+ const envMcpWsBase = process.env.LANONASIS_FALLBACK_MCP_WS_BASE ?? process.env.MCP_WS_BASE;
291
+ const envMcpSseBase = process.env.LANONASIS_FALLBACK_MCP_SSE_BASE ?? process.env.MCP_SSE_BASE;
292
+ const hasEnvOverrides = Boolean(envAuthBase || envMemoryBase || envMcpBase || envMcpWsBase || envMcpSseBase);
293
+ const nodeEnv = (process.env.NODE_ENV ?? '').toLowerCase();
294
+ const isDevEnvironment = nodeEnv === 'development' || nodeEnv === 'test';
295
+ const defaultAuthBase = isDevEnvironment ? 'http://localhost:4000' : 'https://api.lanonasis.com';
296
+ const defaultMemoryBase = isDevEnvironment ? 'http://localhost:4000/api/v1' : 'https://api.lanonasis.com/api/v1';
297
+ const defaultMcpBase = isDevEnvironment ? 'http://localhost:4100/api/v1' : 'https://mcp.lanonasis.com/api/v1';
298
+ const defaultMcpWsBase = isDevEnvironment ? 'ws://localhost:4100/ws' : 'wss://mcp.lanonasis.com/ws';
299
+ const defaultMcpSseBase = isDevEnvironment ? 'http://localhost:4100/api/v1/events' : 'https://mcp.lanonasis.com/api/v1/events';
300
+ const endpoints = {
301
+ auth_base: envAuthBase ?? defaultAuthBase,
302
+ memory_base: envMemoryBase ?? defaultMemoryBase,
303
+ mcp_base: envMcpBase ?? defaultMcpBase,
304
+ mcp_ws_base: envMcpWsBase ?? defaultMcpWsBase,
305
+ mcp_sse_base: envMcpSseBase ?? defaultMcpSseBase
306
+ };
307
+ return {
308
+ endpoints,
309
+ source: hasEnvOverrides ? 'environment' : 'default'
310
+ };
311
+ }
312
+ logFallbackUsage(source, endpoints) {
313
+ const summary = {
314
+ auth: endpoints.auth_base,
315
+ mcp: endpoints.mcp_base,
316
+ websocket: endpoints.mcp_ws_base,
317
+ sse: endpoints.mcp_sse_base,
318
+ source
319
+ };
320
+ const message = `Service discovery fallback activated using ${source === 'environment' ? 'environment overrides' : 'built-in defaults'}`;
321
+ console.warn(`⚠️ ${message}`);
322
+ console.info('📊 service_discovery_fallback', summary);
323
+ if (typeof process.emitWarning === 'function') {
324
+ process.emitWarning(message, 'ServiceDiscoveryFallback');
325
+ }
326
+ }
275
327
  // Manual endpoint override functionality
276
328
  async setManualEndpoints(endpoints) {
277
329
  if (!this.config.discoveredServices) {
@@ -446,6 +498,8 @@ export class CLIConfig {
446
498
  catch {
447
499
  // Invalid token, don't store user info or expiry
448
500
  this.config.tokenExpiry = undefined;
501
+ // Mark as non-JWT (e.g., OAuth/CLI token)
502
+ this.config.authMethod = this.config.authMethod || 'oauth';
449
503
  }
450
504
  await this.save();
451
505
  }
@@ -493,8 +547,32 @@ export class CLIConfig {
493
547
  locallyValid = false;
494
548
  }
495
549
  }
496
- // If expired locally, no need to check server
550
+ // If not locally valid, attempt server verification before failing
497
551
  if (!locallyValid) {
552
+ try {
553
+ const axios = (await import('axios')).default;
554
+ const endpoints = [
555
+ 'http://localhost:4000/v1/auth/verify-token',
556
+ 'https://auth.lanonasis.com/v1/auth/verify-token',
557
+ 'https://api.lanonasis.com/auth/verify'
558
+ ];
559
+ for (const endpoint of endpoints) {
560
+ try {
561
+ const resp = await axios.post(endpoint, { token }, { timeout: 3000 });
562
+ if (resp.data?.valid === true) {
563
+ this.authCheckCache = { isValid: true, timestamp: Date.now() };
564
+ return true;
565
+ }
566
+ }
567
+ catch {
568
+ // try next endpoint
569
+ continue;
570
+ }
571
+ }
572
+ }
573
+ catch {
574
+ // ignore, will fall back to failure below
575
+ }
498
576
  this.authCheckCache = { isValid: false, timestamp: Date.now() };
499
577
  return false;
500
578
  }
@@ -515,7 +593,7 @@ export class CLIConfig {
515
593
  break;
516
594
  }
517
595
  }
518
- catch (err) {
596
+ catch {
519
597
  // Try next endpoint
520
598
  continue;
521
599
  }
@@ -527,7 +605,7 @@ export class CLIConfig {
527
605
  this.authCheckCache = { isValid: true, timestamp: Date.now() };
528
606
  return true;
529
607
  }
530
- catch (error) {
608
+ catch {
531
609
  // If all server checks fail, fall back to local validation
532
610
  // This allows offline usage but is less secure
533
611
  console.warn('⚠️ Unable to verify token with server, using local validation');
@@ -544,9 +622,6 @@ export class CLIConfig {
544
622
  this.config = {};
545
623
  await this.save();
546
624
  }
547
- getConfigPath() {
548
- return this.configPath;
549
- }
550
625
  async exists() {
551
626
  try {
552
627
  await fs.access(this.configPath);
@@ -591,7 +666,7 @@ export class CLIConfig {
591
666
  await this.save();
592
667
  return true;
593
668
  }
594
- catch (error) {
669
+ catch {
595
670
  // Increment failure count
596
671
  await this.incrementFailureCount();
597
672
  return false;
@@ -630,9 +705,12 @@ export class CLIConfig {
630
705
  }
631
706
  }
632
707
  }
633
- catch (error) {
708
+ catch (err) {
634
709
  // If refresh fails, mark credentials as potentially invalid
635
710
  await this.incrementFailureCount();
711
+ if (process.env.CLI_VERBOSE === 'true' || process.env.NODE_ENV !== 'production') {
712
+ console.debug('Token refresh failed:', err.message);
713
+ }
636
714
  }
637
715
  }
638
716
  async clearInvalidCredentials() {
@@ -697,7 +775,9 @@ export class CLIConfig {
697
775
  }
698
776
  // MCP-specific helpers
699
777
  getMCPServerPath() {
700
- return this.config.mcpServerPath || path.join(process.cwd(), 'onasis-gateway/mcp-server/server.js');
778
+ // Only return an explicitly configured path. No implicit bundled defaults.
779
+ // Returning an empty string if unset helps callers decide how to proceed safely.
780
+ return this.config.mcpServerPath || '';
701
781
  }
702
782
  getMCPServerUrl() {
703
783
  return this.config.discoveredServices?.mcp_ws_base ||
@@ -4,6 +4,7 @@ interface MCPConnectionOptions {
4
4
  useRemote?: boolean;
5
5
  useWebSocket?: boolean;
6
6
  connectionMode?: 'local' | 'remote' | 'websocket';
7
+ localArgs?: string[];
7
8
  }
8
9
  /**
9
10
  * Interface for MCP tool arguments
@@ -24,10 +25,10 @@ export interface MCPToolResponse {
24
25
  title?: string;
25
26
  memory_type?: string;
26
27
  length?: number;
27
- forEach?: (callback: (item: any, index: number) => void) => void;
28
+ forEach?: (callback: (item: unknown, index: number) => void) => void;
28
29
  code?: number;
29
30
  message?: string;
30
- response?: any;
31
+ response?: unknown;
31
32
  }
32
33
  /**
33
34
  * Interface for MCP WebSocket messages
@@ -54,7 +55,25 @@ export declare class MCPClient {
54
55
  private healthCheckInterval;
55
56
  private connectionStartTime;
56
57
  private lastHealthCheck;
58
+ private activeConnectionMode;
57
59
  constructor();
60
+ /**
61
+ * Overrides the configuration directory used by the underlying CLI config.
62
+ * Useful for tests that need isolated config state.
63
+ */
64
+ setConfigDirectory(configDir: string): void;
65
+ /**
66
+ * Returns the current config file path. Primarily used for test introspection.
67
+ */
68
+ getConfigPath(): string;
69
+ /**
70
+ * Helper for tests to seed authentication tokens without accessing internals.
71
+ */
72
+ setTokenForTesting(token: string): Promise<void>;
73
+ /**
74
+ * Helper for tests to seed vendor keys without accessing internals.
75
+ */
76
+ setVendorKeyForTesting(vendorKey: string): Promise<void>;
58
77
  /**
59
78
  * Initialize the MCP client configuration
60
79
  */