@lanonasis/cli 3.8.1 → 3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -201,14 +201,14 @@ export declare const SystemConfigSchema: z.ZodObject<{
201
201
  scope: z.ZodDefault<z.ZodEnum<["user", "global"]>>;
202
202
  }, "strip", z.ZodTypeAny, {
203
203
  value?: any;
204
+ key?: string;
204
205
  action?: "get" | "set" | "reset";
205
206
  scope?: "user" | "global";
206
- key?: string;
207
207
  }, {
208
208
  value?: any;
209
+ key?: string;
209
210
  action?: "get" | "set" | "reset";
210
211
  scope?: "user" | "global";
211
- key?: string;
212
212
  }>;
213
213
  export declare const BulkOperationSchema: z.ZodObject<{
214
214
  operation: z.ZodEnum<["create", "update", "delete"]>;
@@ -579,14 +579,14 @@ export declare const MCPSchemas: {
579
579
  scope: z.ZodDefault<z.ZodEnum<["user", "global"]>>;
580
580
  }, "strip", z.ZodTypeAny, {
581
581
  value?: any;
582
+ key?: string;
582
583
  action?: "get" | "set" | "reset";
583
584
  scope?: "user" | "global";
584
- key?: string;
585
585
  }, {
586
586
  value?: any;
587
+ key?: string;
587
588
  action?: "get" | "set" | "reset";
588
589
  scope?: "user" | "global";
589
- key?: string;
590
590
  }>;
591
591
  };
592
592
  operations: {
package/dist/utils/api.js CHANGED
@@ -60,7 +60,17 @@ export class APIClient {
60
60
  const { status, data } = error.response;
61
61
  if (status === 401) {
62
62
  console.error(chalk.red('✖ Authentication failed'));
63
- console.log(chalk.yellow('Please run:'), chalk.white('memory login'));
63
+ // Check if user is using OAuth - OAuth tokens only work with MCP, not direct API
64
+ const authMethod = this.config.get('authMethod');
65
+ if (authMethod === 'oauth') {
66
+ console.log(chalk.yellow('\nNote: OAuth tokens are for MCP integration only.'));
67
+ console.log(chalk.gray('For direct API access, you have two options:'));
68
+ console.log(chalk.gray(' 1. Get a vendor key from the dashboard: ') + chalk.cyan('lanonasis auth login --vendor'));
69
+ console.log(chalk.gray(' 2. Login with username/password: ') + chalk.cyan('lanonasis auth login --credentials'));
70
+ }
71
+ else {
72
+ console.log(chalk.yellow('Please run:'), chalk.white('lanonasis auth login'));
73
+ }
64
74
  process.exit(1);
65
75
  }
66
76
  if (status === 403) {
@@ -53,6 +53,8 @@ export declare class MCPClient {
53
53
  private retryAttempts;
54
54
  private maxRetries;
55
55
  private healthCheckInterval;
56
+ private healthCheckTimeout;
57
+ private wsReconnectTimeout;
56
58
  private connectionStartTime;
57
59
  private lastHealthCheck;
58
60
  private activeConnectionMode;
@@ -189,6 +191,11 @@ export declare class MCPClient {
189
191
  * Check if connected to MCP server
190
192
  */
191
193
  isConnectedToServer(): boolean;
194
+ /**
195
+ * Determine whether tool operations should use the remote REST bridge.
196
+ * WebSocket mode uses the same bridge for tool list/call operations.
197
+ */
198
+ private shouldUseRemoteToolBridge;
192
199
  /**
193
200
  * Get connection status details with health information
194
201
  */
@@ -14,6 +14,8 @@ export class MCPClient {
14
14
  retryAttempts = 0;
15
15
  maxRetries = 3;
16
16
  healthCheckInterval = null;
17
+ healthCheckTimeout = null;
18
+ wsReconnectTimeout = null;
17
19
  connectionStartTime = 0;
18
20
  lastHealthCheck = null;
19
21
  activeConnectionMode = 'local'; // Track actual connection mode
@@ -66,6 +68,7 @@ export class MCPClient {
66
68
  // Save the successful connection mode as preference
67
69
  this.config.set('mcpConnectionMode', mode);
68
70
  this.config.set('mcpPreference', mode);
71
+ this.config.set('mcpUseRemote', mode === 'remote' || mode === 'websocket');
69
72
  // Save the specific URL that worked
70
73
  if (url) {
71
74
  if (mode === 'websocket') {
@@ -152,6 +155,8 @@ export class MCPClient {
152
155
  else {
153
156
  console.log(chalk.yellow(`Retry ${this.retryAttempts}/${this.maxRetries}: Connecting to remote MCP server...`));
154
157
  }
158
+ // Verify remote health before establishing SSE
159
+ await this.checkRemoteHealth(serverUrl);
155
160
  // Initialize SSE connection for real-time updates
156
161
  await this.initializeSSE(serverUrl);
157
162
  this.isConnected = true;
@@ -174,7 +179,7 @@ export class MCPClient {
174
179
  // Check if the server file exists
175
180
  if (!fs.existsSync(serverPath)) {
176
181
  console.log(chalk.yellow(`⚠️ Local MCP server not found at ${serverPath}`));
177
- console.log(chalk.cyan('💡 For remote use WebSocket: lanonasis mcp connect --mode websocket --url wss://mcp.lanonasis.com/ws'));
182
+ console.log(chalk.cyan('💡 For remote connection, use: lanonasis mcp connect --mode websocket --url wss://mcp.lanonasis.com/ws'));
178
183
  throw new Error(`MCP server not found at ${serverPath}`);
179
184
  }
180
185
  if (this.retryAttempts === 0) {
@@ -248,8 +253,8 @@ export class MCPClient {
248
253
  return false;
249
254
  }
250
255
  this.retryAttempts++;
251
- if (this.retryAttempts >= this.maxRetries) {
252
- console.error(chalk.red(`Failed to connect after ${this.maxRetries} attempts`));
256
+ if (this.retryAttempts > this.maxRetries) {
257
+ console.error(chalk.red(`Failed to connect after ${this.maxRetries + 1} attempts`));
253
258
  this.provideNetworkTroubleshootingGuidance(error);
254
259
  this.isConnected = false;
255
260
  return false;
@@ -356,7 +361,7 @@ export class MCPClient {
356
361
  */
357
362
  async validateAuthBeforeConnect() {
358
363
  const token = this.config.get('token');
359
- const vendorKey = this.config.get('vendorKey');
364
+ const vendorKey = await this.config.getVendorKeyAsync();
360
365
  // Check if we have any authentication credentials
361
366
  if (!token && !vendorKey) {
362
367
  throw new Error('AUTHENTICATION_REQUIRED: No authentication credentials found. Run "lanonasis auth login" first.');
@@ -456,7 +461,7 @@ export class MCPClient {
456
461
  // Use the proper SSE endpoint from config
457
462
  const sseUrl = this.config.getMCPSSEUrl() ?? `${serverUrl}/events`;
458
463
  const token = this.config.get('token');
459
- const vendorKey = this.config.get('vendorKey');
464
+ const vendorKey = await this.config.getVendorKeyAsync();
460
465
  const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
461
466
  if (authKey) {
462
467
  // EventSource doesn't support headers directly, append token to URL
@@ -480,7 +485,7 @@ export class MCPClient {
480
485
  */
481
486
  async initializeWebSocket(wsUrl) {
482
487
  const token = this.config.get('token');
483
- const vendorKey = this.config.get('vendorKey');
488
+ const vendorKey = await this.config.getVendorKeyAsync();
484
489
  const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
485
490
  if (!authKey) {
486
491
  throw new Error('API key required for WebSocket mode. Set LANONASIS_API_KEY or login first.');
@@ -521,7 +526,12 @@ export class MCPClient {
521
526
  this.wsConnection.on('message', (data) => {
522
527
  try {
523
528
  const message = JSON.parse(data.toString());
524
- console.log(chalk.blue('📡 MCP message:'), message.id, message.method || 'response');
529
+ const messageId = message.id ?? 'event';
530
+ const messageType = message.method
531
+ || (message.error ? 'error' : undefined)
532
+ || (message.result ? 'result' : undefined)
533
+ || 'response';
534
+ console.log(chalk.blue('📡 MCP message:'), messageId, messageType);
525
535
  }
526
536
  catch (error) {
527
537
  console.error('Failed to parse WebSocket message:', error);
@@ -534,8 +544,11 @@ export class MCPClient {
534
544
  this.wsConnection.on('close', (code, reason) => {
535
545
  console.log(chalk.yellow(`WebSocket connection closed (${code}): ${reason}`));
536
546
  // Auto-reconnect after delay
537
- setTimeout(() => {
538
- if (this.isConnected) {
547
+ if (this.wsReconnectTimeout) {
548
+ clearTimeout(this.wsReconnectTimeout);
549
+ }
550
+ this.wsReconnectTimeout = setTimeout(() => {
551
+ if (this.isConnected && process.env.NODE_ENV !== 'test') {
539
552
  console.log(chalk.blue('🔄 Attempting to reconnect to WebSocket...'));
540
553
  this.initializeWebSocket(wsUrl).catch(err => {
541
554
  console.error('Failed to reconnect:', err);
@@ -569,7 +582,8 @@ export class MCPClient {
569
582
  await this.performHealthCheck();
570
583
  }, 30000);
571
584
  // Perform initial health check
572
- setTimeout(() => this.performHealthCheck(), 5000);
585
+ const initialDelay = process.env.NODE_ENV === 'test' ? 50 : 5000;
586
+ this.healthCheckTimeout = setTimeout(() => this.performHealthCheck(), initialDelay);
573
587
  }
574
588
  /**
575
589
  * Stop health monitoring
@@ -579,6 +593,10 @@ export class MCPClient {
579
593
  clearInterval(this.healthCheckInterval);
580
594
  this.healthCheckInterval = null;
581
595
  }
596
+ if (this.healthCheckTimeout) {
597
+ clearTimeout(this.healthCheckTimeout);
598
+ this.healthCheckTimeout = null;
599
+ }
582
600
  }
583
601
  /**
584
602
  * Perform a health check on the current connection
@@ -625,10 +643,10 @@ export class MCPClient {
625
643
  /**
626
644
  * Check remote connection health
627
645
  */
628
- async checkRemoteHealth() {
629
- const apiUrl = this.config.getMCPRestUrl() ?? 'https://mcp.lanonasis.com/api/v1';
646
+ async checkRemoteHealth(serverUrl) {
647
+ const apiUrl = serverUrl ?? this.config.getMCPRestUrl() ?? 'https://mcp.lanonasis.com/api/v1';
630
648
  const token = this.config.get('token');
631
- const vendorKey = this.config.get('vendorKey');
649
+ const vendorKey = await this.config.getVendorKeyAsync();
632
650
  const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
633
651
  if (!authKey) {
634
652
  throw new Error('No authentication token available');
@@ -700,11 +718,14 @@ export class MCPClient {
700
718
  */
701
719
  async disconnect() {
702
720
  this.stopHealthMonitoring();
721
+ this.isConnected = false;
703
722
  if (this.client) {
704
723
  await this.client.close();
705
724
  this.client = null;
706
725
  }
707
726
  if (this.sseConnection) {
727
+ this.sseConnection.onmessage = null;
728
+ this.sseConnection.onerror = null;
708
729
  this.sseConnection.close();
709
730
  this.sseConnection = null;
710
731
  }
@@ -712,7 +733,10 @@ export class MCPClient {
712
733
  this.wsConnection.close();
713
734
  this.wsConnection = null;
714
735
  }
715
- this.isConnected = false;
736
+ if (this.wsReconnectTimeout) {
737
+ clearTimeout(this.wsReconnectTimeout);
738
+ this.wsReconnectTimeout = null;
739
+ }
716
740
  this.activeConnectionMode = 'websocket'; // Reset to default
717
741
  }
718
742
  /**
@@ -722,7 +746,7 @@ export class MCPClient {
722
746
  if (!this.isConnected) {
723
747
  throw new Error('Not connected to MCP server. Run "lanonasis mcp connect" first.');
724
748
  }
725
- const useRemote = this.config.get('mcpUseRemote') ?? false;
749
+ const useRemote = this.shouldUseRemoteToolBridge();
726
750
  if (useRemote) {
727
751
  // Remote MCP calls are translated to REST API calls
728
752
  return await this.callRemoteTool(toolName, args);
@@ -755,7 +779,7 @@ export class MCPClient {
755
779
  async callRemoteTool(toolName, args) {
756
780
  const apiUrl = this.config.getMCPRestUrl() ?? 'https://mcp.lanonasis.com/api/v1';
757
781
  const token = this.config.get('token');
758
- const vendorKey = this.config.get('vendorKey');
782
+ const vendorKey = await this.config.getVendorKeyAsync();
759
783
  const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
760
784
  if (!authKey) {
761
785
  throw new Error('Authentication required. Run "lanonasis auth login" first.');
@@ -836,7 +860,7 @@ export class MCPClient {
836
860
  if (!this.isConnected) {
837
861
  throw new Error('Not connected to MCP server');
838
862
  }
839
- const useRemote = this.config.get('mcpUseRemote') ?? false;
863
+ const useRemote = this.shouldUseRemoteToolBridge();
840
864
  if (useRemote) {
841
865
  // Return hardcoded list for remote mode
842
866
  return [
@@ -865,6 +889,16 @@ export class MCPClient {
865
889
  isConnectedToServer() {
866
890
  return this.isConnected;
867
891
  }
892
+ /**
893
+ * Determine whether tool operations should use the remote REST bridge.
894
+ * WebSocket mode uses the same bridge for tool list/call operations.
895
+ */
896
+ shouldUseRemoteToolBridge() {
897
+ if (this.activeConnectionMode === 'remote' || this.activeConnectionMode === 'websocket') {
898
+ return true;
899
+ }
900
+ return this.config.get('mcpUseRemote') ?? false;
901
+ }
868
902
  /**
869
903
  * Get connection status details with health information
870
904
  */
@@ -897,7 +931,7 @@ export class MCPClient {
897
931
  break;
898
932
  }
899
933
  const connectionUptime = this.connectionStartTime > 0
900
- ? Date.now() - this.connectionStartTime
934
+ ? Math.max(Date.now() - this.connectionStartTime, this.isConnected ? 1 : 0)
901
935
  : undefined;
902
936
  return {
903
937
  connected: this.isConnected,
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Connection Manager Implementation
3
+ *
4
+ * Manages MCP server discovery, configuration, and connection lifecycle
5
+ * Implementation of the ConnectionManager interface.
6
+ */
7
+ import { ConnectionManager, ConnectionResult, ConfigResult, ServerInstance, ConnectionStatus, MCPConfig } from '../interfaces/ConnectionManager.js';
8
+ /**
9
+ * ConnectionManagerImpl manages MCP server discovery, configuration, and connection lifecycle
10
+ *
11
+ * This implementation automatically detects the embedded MCP server location within the CLI package,
12
+ * generates configuration files with correct server paths, and manages server processes.
13
+ */
14
+ export declare class ConnectionManagerImpl implements ConnectionManager {
15
+ private config;
16
+ private connectionStatus;
17
+ private serverProcess;
18
+ private configPath;
19
+ constructor(configPath?: string);
20
+ /**
21
+ * Initialize the connection manager by loading persisted configuration
22
+ */
23
+ init(): Promise<void>;
24
+ /**
25
+ * Connect to the local embedded MCP server
26
+ */
27
+ connectLocal(): Promise<ConnectionResult>;
28
+ /**
29
+ * Automatically configure the local MCP server with correct paths
30
+ */
31
+ autoConfigureLocalServer(): Promise<ConfigResult>;
32
+ /**
33
+ * Detect the embedded MCP server path within the CLI package
34
+ */
35
+ detectServerPath(): Promise<string | null>;
36
+ /**
37
+ * Start the local MCP server process
38
+ */
39
+ startLocalServer(): Promise<ServerInstance>;
40
+ /**
41
+ * Verify that the MCP server connection is working
42
+ */
43
+ verifyConnection(serverPath: string): Promise<boolean>;
44
+ /**
45
+ * Get the current connection status
46
+ */
47
+ getConnectionStatus(): ConnectionStatus;
48
+ /**
49
+ * Stop the local MCP server if running
50
+ */
51
+ stopLocalServer(): Promise<void>;
52
+ /**
53
+ * Get the current MCP configuration
54
+ */
55
+ getConfig(): MCPConfig;
56
+ /**
57
+ * Update the MCP configuration
58
+ */
59
+ updateConfig(config: Partial<MCPConfig>): Promise<void>;
60
+ /**
61
+ * Check if the server is currently running
62
+ */
63
+ private isServerRunning;
64
+ /**
65
+ * Save the current configuration to disk
66
+ */
67
+ private saveConfig;
68
+ /**
69
+ * Load configuration from disk
70
+ */
71
+ private loadConfig;
72
+ }