@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.
- package/CHANGELOG.md +224 -0
- package/README.md +68 -3
- package/dist/commands/auth.js +10 -11
- package/dist/commands/init.js +12 -0
- package/dist/commands/mcp.js +62 -5
- package/dist/commands/memory.js +49 -23
- package/dist/index.js +20 -0
- package/dist/mcp/schemas/tool-schemas.d.ts +4 -4
- package/dist/utils/api.js +11 -1
- package/dist/utils/mcp-client.d.ts +7 -0
- package/dist/utils/mcp-client.js +52 -18
- package/dist/ux/implementations/ConnectionManagerImpl.d.ts +72 -0
- package/dist/ux/implementations/ConnectionManagerImpl.js +356 -0
- package/dist/ux/implementations/OnboardingFlowImpl.d.ts +72 -0
- package/dist/ux/implementations/OnboardingFlowImpl.js +415 -0
- package/dist/ux/implementations/TextInputHandlerImpl.d.ts +74 -0
- package/dist/ux/implementations/TextInputHandlerImpl.js +347 -0
- package/dist/ux/implementations/index.d.ts +11 -0
- package/dist/ux/implementations/index.js +11 -0
- package/dist/ux/index.d.ts +15 -0
- package/dist/ux/index.js +22 -0
- package/dist/ux/interfaces/ConnectionManager.d.ts +112 -0
- package/dist/ux/interfaces/ConnectionManager.js +7 -0
- package/dist/ux/interfaces/OnboardingFlow.d.ts +103 -0
- package/dist/ux/interfaces/OnboardingFlow.js +7 -0
- package/dist/ux/interfaces/TextInputHandler.d.ts +87 -0
- package/dist/ux/interfaces/TextInputHandler.js +7 -0
- package/dist/ux/interfaces/index.d.ts +10 -0
- package/dist/ux/interfaces/index.js +8 -0
- package/package.json +34 -4
|
@@ -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
|
-
|
|
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
|
*/
|
package/dist/utils/mcp-client.js
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
538
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
}
|