@lanonasis/cli 3.8.1 → 3.9.0
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 +195 -0
- package/README.md +65 -2
- package/dist/commands/init.js +12 -0
- package/dist/commands/mcp.js +27 -0
- 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/mcp-client.d.ts +2 -0
- package/dist/utils/mcp-client.js +33 -15
- package/dist/ux/implementations/ConnectionManagerImpl.d.ts +72 -0
- package/dist/ux/implementations/ConnectionManagerImpl.js +352 -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 +342 -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
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
|
|
@@ -152,6 +154,8 @@ export class MCPClient {
|
|
|
152
154
|
else {
|
|
153
155
|
console.log(chalk.yellow(`Retry ${this.retryAttempts}/${this.maxRetries}: Connecting to remote MCP server...`));
|
|
154
156
|
}
|
|
157
|
+
// Verify remote health before establishing SSE
|
|
158
|
+
await this.checkRemoteHealth(serverUrl);
|
|
155
159
|
// Initialize SSE connection for real-time updates
|
|
156
160
|
await this.initializeSSE(serverUrl);
|
|
157
161
|
this.isConnected = true;
|
|
@@ -174,7 +178,7 @@ export class MCPClient {
|
|
|
174
178
|
// Check if the server file exists
|
|
175
179
|
if (!fs.existsSync(serverPath)) {
|
|
176
180
|
console.log(chalk.yellow(`⚠️ Local MCP server not found at ${serverPath}`));
|
|
177
|
-
console.log(chalk.cyan('💡 For remote use
|
|
181
|
+
console.log(chalk.cyan('💡 For remote connection, use: lanonasis mcp connect --mode websocket --url wss://mcp.lanonasis.com/ws'));
|
|
178
182
|
throw new Error(`MCP server not found at ${serverPath}`);
|
|
179
183
|
}
|
|
180
184
|
if (this.retryAttempts === 0) {
|
|
@@ -248,8 +252,8 @@ export class MCPClient {
|
|
|
248
252
|
return false;
|
|
249
253
|
}
|
|
250
254
|
this.retryAttempts++;
|
|
251
|
-
if (this.retryAttempts
|
|
252
|
-
console.error(chalk.red(`Failed to connect after ${this.maxRetries} attempts`));
|
|
255
|
+
if (this.retryAttempts > this.maxRetries) {
|
|
256
|
+
console.error(chalk.red(`Failed to connect after ${this.maxRetries + 1} attempts`));
|
|
253
257
|
this.provideNetworkTroubleshootingGuidance(error);
|
|
254
258
|
this.isConnected = false;
|
|
255
259
|
return false;
|
|
@@ -356,7 +360,7 @@ export class MCPClient {
|
|
|
356
360
|
*/
|
|
357
361
|
async validateAuthBeforeConnect() {
|
|
358
362
|
const token = this.config.get('token');
|
|
359
|
-
const vendorKey = this.config.
|
|
363
|
+
const vendorKey = await this.config.getVendorKeyAsync();
|
|
360
364
|
// Check if we have any authentication credentials
|
|
361
365
|
if (!token && !vendorKey) {
|
|
362
366
|
throw new Error('AUTHENTICATION_REQUIRED: No authentication credentials found. Run "lanonasis auth login" first.');
|
|
@@ -456,7 +460,7 @@ export class MCPClient {
|
|
|
456
460
|
// Use the proper SSE endpoint from config
|
|
457
461
|
const sseUrl = this.config.getMCPSSEUrl() ?? `${serverUrl}/events`;
|
|
458
462
|
const token = this.config.get('token');
|
|
459
|
-
const vendorKey = this.config.
|
|
463
|
+
const vendorKey = await this.config.getVendorKeyAsync();
|
|
460
464
|
const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
|
|
461
465
|
if (authKey) {
|
|
462
466
|
// EventSource doesn't support headers directly, append token to URL
|
|
@@ -480,7 +484,7 @@ export class MCPClient {
|
|
|
480
484
|
*/
|
|
481
485
|
async initializeWebSocket(wsUrl) {
|
|
482
486
|
const token = this.config.get('token');
|
|
483
|
-
const vendorKey = this.config.
|
|
487
|
+
const vendorKey = await this.config.getVendorKeyAsync();
|
|
484
488
|
const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
|
|
485
489
|
if (!authKey) {
|
|
486
490
|
throw new Error('API key required for WebSocket mode. Set LANONASIS_API_KEY or login first.');
|
|
@@ -534,8 +538,11 @@ export class MCPClient {
|
|
|
534
538
|
this.wsConnection.on('close', (code, reason) => {
|
|
535
539
|
console.log(chalk.yellow(`WebSocket connection closed (${code}): ${reason}`));
|
|
536
540
|
// Auto-reconnect after delay
|
|
537
|
-
|
|
538
|
-
|
|
541
|
+
if (this.wsReconnectTimeout) {
|
|
542
|
+
clearTimeout(this.wsReconnectTimeout);
|
|
543
|
+
}
|
|
544
|
+
this.wsReconnectTimeout = setTimeout(() => {
|
|
545
|
+
if (this.isConnected && process.env.NODE_ENV !== 'test') {
|
|
539
546
|
console.log(chalk.blue('🔄 Attempting to reconnect to WebSocket...'));
|
|
540
547
|
this.initializeWebSocket(wsUrl).catch(err => {
|
|
541
548
|
console.error('Failed to reconnect:', err);
|
|
@@ -569,7 +576,8 @@ export class MCPClient {
|
|
|
569
576
|
await this.performHealthCheck();
|
|
570
577
|
}, 30000);
|
|
571
578
|
// Perform initial health check
|
|
572
|
-
|
|
579
|
+
const initialDelay = process.env.NODE_ENV === 'test' ? 50 : 5000;
|
|
580
|
+
this.healthCheckTimeout = setTimeout(() => this.performHealthCheck(), initialDelay);
|
|
573
581
|
}
|
|
574
582
|
/**
|
|
575
583
|
* Stop health monitoring
|
|
@@ -579,6 +587,10 @@ export class MCPClient {
|
|
|
579
587
|
clearInterval(this.healthCheckInterval);
|
|
580
588
|
this.healthCheckInterval = null;
|
|
581
589
|
}
|
|
590
|
+
if (this.healthCheckTimeout) {
|
|
591
|
+
clearTimeout(this.healthCheckTimeout);
|
|
592
|
+
this.healthCheckTimeout = null;
|
|
593
|
+
}
|
|
582
594
|
}
|
|
583
595
|
/**
|
|
584
596
|
* Perform a health check on the current connection
|
|
@@ -625,10 +637,10 @@ export class MCPClient {
|
|
|
625
637
|
/**
|
|
626
638
|
* Check remote connection health
|
|
627
639
|
*/
|
|
628
|
-
async checkRemoteHealth() {
|
|
629
|
-
const apiUrl = this.config.getMCPRestUrl() ?? 'https://mcp.lanonasis.com/api/v1';
|
|
640
|
+
async checkRemoteHealth(serverUrl) {
|
|
641
|
+
const apiUrl = serverUrl ?? this.config.getMCPRestUrl() ?? 'https://mcp.lanonasis.com/api/v1';
|
|
630
642
|
const token = this.config.get('token');
|
|
631
|
-
const vendorKey = this.config.
|
|
643
|
+
const vendorKey = await this.config.getVendorKeyAsync();
|
|
632
644
|
const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
|
|
633
645
|
if (!authKey) {
|
|
634
646
|
throw new Error('No authentication token available');
|
|
@@ -700,11 +712,14 @@ export class MCPClient {
|
|
|
700
712
|
*/
|
|
701
713
|
async disconnect() {
|
|
702
714
|
this.stopHealthMonitoring();
|
|
715
|
+
this.isConnected = false;
|
|
703
716
|
if (this.client) {
|
|
704
717
|
await this.client.close();
|
|
705
718
|
this.client = null;
|
|
706
719
|
}
|
|
707
720
|
if (this.sseConnection) {
|
|
721
|
+
this.sseConnection.onmessage = null;
|
|
722
|
+
this.sseConnection.onerror = null;
|
|
708
723
|
this.sseConnection.close();
|
|
709
724
|
this.sseConnection = null;
|
|
710
725
|
}
|
|
@@ -712,7 +727,10 @@ export class MCPClient {
|
|
|
712
727
|
this.wsConnection.close();
|
|
713
728
|
this.wsConnection = null;
|
|
714
729
|
}
|
|
715
|
-
this.
|
|
730
|
+
if (this.wsReconnectTimeout) {
|
|
731
|
+
clearTimeout(this.wsReconnectTimeout);
|
|
732
|
+
this.wsReconnectTimeout = null;
|
|
733
|
+
}
|
|
716
734
|
this.activeConnectionMode = 'websocket'; // Reset to default
|
|
717
735
|
}
|
|
718
736
|
/**
|
|
@@ -755,7 +773,7 @@ export class MCPClient {
|
|
|
755
773
|
async callRemoteTool(toolName, args) {
|
|
756
774
|
const apiUrl = this.config.getMCPRestUrl() ?? 'https://mcp.lanonasis.com/api/v1';
|
|
757
775
|
const token = this.config.get('token');
|
|
758
|
-
const vendorKey = this.config.
|
|
776
|
+
const vendorKey = await this.config.getVendorKeyAsync();
|
|
759
777
|
const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
|
|
760
778
|
if (!authKey) {
|
|
761
779
|
throw new Error('Authentication required. Run "lanonasis auth login" first.');
|
|
@@ -897,7 +915,7 @@ export class MCPClient {
|
|
|
897
915
|
break;
|
|
898
916
|
}
|
|
899
917
|
const connectionUptime = this.connectionStartTime > 0
|
|
900
|
-
? Date.now() - this.connectionStartTime
|
|
918
|
+
? Math.max(Date.now() - this.connectionStartTime, this.isConnected ? 1 : 0)
|
|
901
919
|
: undefined;
|
|
902
920
|
return {
|
|
903
921
|
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
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection Manager Implementation
|
|
3
|
+
*
|
|
4
|
+
* Manages MCP server discovery, configuration, and connection lifecycle
|
|
5
|
+
* Implementation of the ConnectionManager interface.
|
|
6
|
+
*/
|
|
7
|
+
import { promises as fs, createWriteStream } from 'fs';
|
|
8
|
+
import { join, dirname, resolve } from 'path';
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
/**
|
|
12
|
+
* Default MCP configuration
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_MCP_CONFIG = {
|
|
15
|
+
localServerPath: '',
|
|
16
|
+
serverPort: 3000,
|
|
17
|
+
autoStart: true,
|
|
18
|
+
connectionTimeout: 10000,
|
|
19
|
+
retryAttempts: 3,
|
|
20
|
+
logLevel: 'info',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* ConnectionManagerImpl manages MCP server discovery, configuration, and connection lifecycle
|
|
24
|
+
*
|
|
25
|
+
* This implementation automatically detects the embedded MCP server location within the CLI package,
|
|
26
|
+
* generates configuration files with correct server paths, and manages server processes.
|
|
27
|
+
*/
|
|
28
|
+
export class ConnectionManagerImpl {
|
|
29
|
+
config;
|
|
30
|
+
connectionStatus;
|
|
31
|
+
serverProcess = null;
|
|
32
|
+
configPath;
|
|
33
|
+
constructor(configPath) {
|
|
34
|
+
this.config = { ...DEFAULT_MCP_CONFIG };
|
|
35
|
+
this.configPath = configPath || join(process.cwd(), '.lanonasis', 'mcp-config.json');
|
|
36
|
+
this.connectionStatus = {
|
|
37
|
+
isConnected: false,
|
|
38
|
+
connectionAttempts: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Initialize the connection manager by loading persisted configuration
|
|
43
|
+
*/
|
|
44
|
+
async init() {
|
|
45
|
+
await this.loadConfig();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Connect to the local embedded MCP server
|
|
49
|
+
*/
|
|
50
|
+
async connectLocal() {
|
|
51
|
+
try {
|
|
52
|
+
// Load persisted configuration first
|
|
53
|
+
await this.loadConfig();
|
|
54
|
+
// First, try to detect the server path
|
|
55
|
+
const configuredPath = this.config.localServerPath?.trim();
|
|
56
|
+
const serverPath = configuredPath || (await this.detectServerPath());
|
|
57
|
+
if (!serverPath) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: 'Could not detect local MCP server path',
|
|
61
|
+
suggestions: [
|
|
62
|
+
'Ensure the CLI package is properly installed',
|
|
63
|
+
'Check that the MCP server files are present',
|
|
64
|
+
'Try running: lanonasis init',
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Update configuration with detected path
|
|
69
|
+
this.config.localServerPath = serverPath;
|
|
70
|
+
await this.saveConfig();
|
|
71
|
+
// Start the server if not already running
|
|
72
|
+
if (!this.isServerRunning()) {
|
|
73
|
+
const serverInstance = await this.startLocalServer();
|
|
74
|
+
this.connectionStatus.serverInstance = serverInstance;
|
|
75
|
+
}
|
|
76
|
+
// Verify the connection
|
|
77
|
+
const isConnected = await this.verifyConnection(serverPath);
|
|
78
|
+
if (isConnected) {
|
|
79
|
+
this.connectionStatus.isConnected = true;
|
|
80
|
+
this.connectionStatus.lastConnected = new Date();
|
|
81
|
+
this.connectionStatus.connectionAttempts++;
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
serverPath,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.connectionStatus.connectionAttempts++;
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
error: 'Failed to verify MCP server connection',
|
|
92
|
+
suggestions: [
|
|
93
|
+
'Check server logs for errors',
|
|
94
|
+
'Ensure no other process is using the port',
|
|
95
|
+
'Try restarting the CLI',
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.connectionStatus.connectionAttempts++;
|
|
102
|
+
this.connectionStatus.lastError = error instanceof Error ? error.message : String(error);
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
106
|
+
suggestions: [
|
|
107
|
+
'Check your network connection',
|
|
108
|
+
'Verify the MCP server is installed correctly',
|
|
109
|
+
'Try running: lanonasis mcp diagnose',
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Automatically configure the local MCP server with correct paths
|
|
116
|
+
*/
|
|
117
|
+
async autoConfigureLocalServer() {
|
|
118
|
+
try {
|
|
119
|
+
const serverPath = await this.detectServerPath();
|
|
120
|
+
if (!serverPath) {
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
error: 'Could not detect MCP server path for auto-configuration',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Update configuration
|
|
127
|
+
this.config.localServerPath = serverPath;
|
|
128
|
+
// Ensure config directory exists
|
|
129
|
+
await fs.mkdir(dirname(this.configPath), { recursive: true });
|
|
130
|
+
// Save configuration
|
|
131
|
+
await this.saveConfig();
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
configPath: this.configPath,
|
|
135
|
+
serverPath,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: `Auto-configuration failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Detect the embedded MCP server path within the CLI package
|
|
147
|
+
*/
|
|
148
|
+
async detectServerPath() {
|
|
149
|
+
try {
|
|
150
|
+
// Get the current module directory
|
|
151
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
152
|
+
// Common paths to check for the MCP server
|
|
153
|
+
const candidatePaths = [
|
|
154
|
+
// Relative to current CLI source
|
|
155
|
+
resolve(currentDir, '../../mcp-server-entry.js'),
|
|
156
|
+
resolve(currentDir, '../../../dist/mcp-server-entry.js'),
|
|
157
|
+
// Relative to CLI package root
|
|
158
|
+
resolve(currentDir, '../../../mcp-server-entry.js'),
|
|
159
|
+
resolve(currentDir, '../../dist/mcp-server-entry.js'),
|
|
160
|
+
// In node_modules (if installed as dependency)
|
|
161
|
+
resolve(process.cwd(), 'node_modules/@lanonasis/cli/dist/mcp-server-entry.js'),
|
|
162
|
+
// Global installation paths
|
|
163
|
+
resolve(process.cwd(), 'dist/mcp-server-entry.js'),
|
|
164
|
+
// Development paths
|
|
165
|
+
resolve(process.cwd(), 'cli/dist/mcp-server-entry.js'),
|
|
166
|
+
resolve(process.cwd(), '../cli/dist/mcp-server-entry.js'),
|
|
167
|
+
];
|
|
168
|
+
// Check each candidate path
|
|
169
|
+
for (const candidatePath of candidatePaths) {
|
|
170
|
+
try {
|
|
171
|
+
await fs.access(candidatePath);
|
|
172
|
+
// Verify it's actually the MCP server by checking file content
|
|
173
|
+
const content = await fs.readFile(candidatePath, 'utf-8');
|
|
174
|
+
if (content.includes('mcp') || content.includes('server')) {
|
|
175
|
+
return candidatePath;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Path doesn't exist or isn't accessible, continue
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
console.error('Error detecting server path:', error);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Start the local MCP server process
|
|
191
|
+
*/
|
|
192
|
+
async startLocalServer() {
|
|
193
|
+
if (!this.config.localServerPath) {
|
|
194
|
+
throw new Error('No local server path configured');
|
|
195
|
+
}
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
const serverProcess = spawn('node', [this.config.localServerPath], {
|
|
198
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
199
|
+
env: {
|
|
200
|
+
...process.env,
|
|
201
|
+
PORT: this.config.serverPort?.toString() || '3000',
|
|
202
|
+
LOG_LEVEL: this.config.logLevel,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
const serverInstance = {
|
|
206
|
+
pid: serverProcess.pid,
|
|
207
|
+
port: this.config.serverPort || 3000,
|
|
208
|
+
status: 'starting',
|
|
209
|
+
startTime: new Date(),
|
|
210
|
+
logPath: join(dirname(this.configPath), 'mcp-server.log'),
|
|
211
|
+
};
|
|
212
|
+
// Set up logging
|
|
213
|
+
const logStream = createWriteStream(serverInstance.logPath, { flags: 'a' });
|
|
214
|
+
serverProcess.stdout?.pipe(logStream);
|
|
215
|
+
serverProcess.stderr?.pipe(logStream);
|
|
216
|
+
// Handle server startup
|
|
217
|
+
const startupTimeout = setTimeout(() => {
|
|
218
|
+
serverInstance.status = 'error';
|
|
219
|
+
reject(new Error('Server startup timeout'));
|
|
220
|
+
}, this.config.connectionTimeout);
|
|
221
|
+
serverProcess.on('spawn', () => {
|
|
222
|
+
clearTimeout(startupTimeout);
|
|
223
|
+
serverInstance.status = 'running';
|
|
224
|
+
this.serverProcess = serverProcess;
|
|
225
|
+
resolve(serverInstance);
|
|
226
|
+
});
|
|
227
|
+
serverProcess.on('error', (error) => {
|
|
228
|
+
clearTimeout(startupTimeout);
|
|
229
|
+
serverInstance.status = 'error';
|
|
230
|
+
reject(error);
|
|
231
|
+
});
|
|
232
|
+
serverProcess.on('exit', (code) => {
|
|
233
|
+
serverInstance.status = code === 0 ? 'stopped' : 'error';
|
|
234
|
+
this.serverProcess = null;
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Verify that the MCP server connection is working
|
|
240
|
+
*/
|
|
241
|
+
async verifyConnection(serverPath) {
|
|
242
|
+
try {
|
|
243
|
+
// Simple verification - check if server path exists and is accessible
|
|
244
|
+
await fs.access(serverPath);
|
|
245
|
+
// If we have a running server instance, check if it's responsive
|
|
246
|
+
if (this.connectionStatus.serverInstance) {
|
|
247
|
+
const { status, pid } = this.connectionStatus.serverInstance;
|
|
248
|
+
// Explicitly check for error/stopped states
|
|
249
|
+
if (status === 'error' || status === 'stopped') {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
// Only verify process for running servers
|
|
253
|
+
if (status === 'running') {
|
|
254
|
+
try {
|
|
255
|
+
process.kill(pid, 0); // Signal 0 checks if process exists
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// Process doesn't exist despite status being 'running'
|
|
260
|
+
this.connectionStatus.serverInstance.status = 'stopped';
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Starting state is not yet ready
|
|
265
|
+
if (status === 'starting') {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// No server instance means we haven't started it yet
|
|
270
|
+
// This is okay for initial connection attempts
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get the current connection status
|
|
279
|
+
*/
|
|
280
|
+
getConnectionStatus() {
|
|
281
|
+
return { ...this.connectionStatus };
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Stop the local MCP server if running
|
|
285
|
+
*/
|
|
286
|
+
async stopLocalServer() {
|
|
287
|
+
if (this.serverProcess) {
|
|
288
|
+
return new Promise((resolve) => {
|
|
289
|
+
const forceKillTimeout = setTimeout(() => {
|
|
290
|
+
if (this.serverProcess) {
|
|
291
|
+
this.serverProcess.kill('SIGKILL');
|
|
292
|
+
}
|
|
293
|
+
}, 5000);
|
|
294
|
+
this.serverProcess.on('exit', () => {
|
|
295
|
+
clearTimeout(forceKillTimeout);
|
|
296
|
+
this.serverProcess = null;
|
|
297
|
+
if (this.connectionStatus.serverInstance) {
|
|
298
|
+
this.connectionStatus.serverInstance.status = 'stopped';
|
|
299
|
+
}
|
|
300
|
+
this.connectionStatus.isConnected = false;
|
|
301
|
+
resolve();
|
|
302
|
+
});
|
|
303
|
+
this.serverProcess.kill('SIGTERM');
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get the current MCP configuration
|
|
309
|
+
*/
|
|
310
|
+
getConfig() {
|
|
311
|
+
return { ...this.config };
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Update the MCP configuration
|
|
315
|
+
*/
|
|
316
|
+
async updateConfig(config) {
|
|
317
|
+
this.config = { ...this.config, ...config };
|
|
318
|
+
await this.saveConfig();
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Check if the server is currently running
|
|
322
|
+
*/
|
|
323
|
+
isServerRunning() {
|
|
324
|
+
return (this.serverProcess !== null && this.connectionStatus.serverInstance?.status === 'running');
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Save the current configuration to disk
|
|
328
|
+
*/
|
|
329
|
+
async saveConfig() {
|
|
330
|
+
try {
|
|
331
|
+
await fs.mkdir(dirname(this.configPath), { recursive: true });
|
|
332
|
+
await fs.writeFile(this.configPath, JSON.stringify(this.config, null, 2));
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
throw new Error(`Failed to save configuration: ${error instanceof Error ? error.message : String(error)}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Load configuration from disk
|
|
340
|
+
*/
|
|
341
|
+
async loadConfig() {
|
|
342
|
+
try {
|
|
343
|
+
const configData = await fs.readFile(this.configPath, 'utf-8');
|
|
344
|
+
const loadedConfig = JSON.parse(configData);
|
|
345
|
+
this.config = { ...DEFAULT_MCP_CONFIG, ...loadedConfig };
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Config file doesn't exist or is invalid, use defaults
|
|
349
|
+
this.config = { ...DEFAULT_MCP_CONFIG };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding Flow Implementation
|
|
3
|
+
*
|
|
4
|
+
* Guides new users through initial setup and configuration
|
|
5
|
+
* Implementation of the OnboardingFlow interface.
|
|
6
|
+
*/
|
|
7
|
+
import { OnboardingFlow, SetupResult, TestResult, UserPreferences, OnboardingState } from '../interfaces/OnboardingFlow.js';
|
|
8
|
+
/**
|
|
9
|
+
* OnboardingFlowImpl guides new users through initial setup and configuration
|
|
10
|
+
*
|
|
11
|
+
* This implementation detects first-run scenarios, creates working default configurations,
|
|
12
|
+
* tests all major functionality, and provides interactive demonstrations.
|
|
13
|
+
*/
|
|
14
|
+
export declare class OnboardingFlowImpl implements OnboardingFlow {
|
|
15
|
+
private onboardingState;
|
|
16
|
+
private configPath;
|
|
17
|
+
private connectionManager;
|
|
18
|
+
private textInputHandler;
|
|
19
|
+
constructor(configPath?: string);
|
|
20
|
+
/**
|
|
21
|
+
* Run the complete initial setup process for new users
|
|
22
|
+
*/
|
|
23
|
+
runInitialSetup(): Promise<SetupResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Detect if this is a first-run scenario
|
|
26
|
+
*/
|
|
27
|
+
detectFirstRun(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Configure working default settings for immediate productivity
|
|
30
|
+
*/
|
|
31
|
+
configureDefaults(): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Test connectivity and functionality of all major components
|
|
34
|
+
*/
|
|
35
|
+
testConnectivity(): Promise<TestResult[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Show welcome demonstration of key features
|
|
38
|
+
*/
|
|
39
|
+
showWelcomeDemo(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Get the current onboarding state
|
|
42
|
+
*/
|
|
43
|
+
getOnboardingState(): OnboardingState;
|
|
44
|
+
/**
|
|
45
|
+
* Update user preferences during onboarding
|
|
46
|
+
*/
|
|
47
|
+
updateUserPreferences(preferences: Partial<UserPreferences>): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Skip the current onboarding step
|
|
50
|
+
*/
|
|
51
|
+
skipCurrentStep(reason?: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Complete the onboarding process
|
|
54
|
+
*/
|
|
55
|
+
completeOnboarding(): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Reset onboarding state (for testing or re-running)
|
|
58
|
+
*/
|
|
59
|
+
resetOnboarding(): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Gather user preferences interactively
|
|
62
|
+
*/
|
|
63
|
+
private gatherUserPreferences;
|
|
64
|
+
/**
|
|
65
|
+
* Save the current onboarding state to disk
|
|
66
|
+
*/
|
|
67
|
+
private saveOnboardingState;
|
|
68
|
+
/**
|
|
69
|
+
* Load onboarding state from disk
|
|
70
|
+
*/
|
|
71
|
+
private loadOnboardingState;
|
|
72
|
+
}
|