@lanonasis/cli 3.1.13 → 3.2.14
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/dist/__tests__/auth-persistence.test.d.ts +1 -0
- package/dist/__tests__/auth-persistence.test.js +243 -0
- package/dist/__tests__/cross-device-integration.test.d.ts +1 -0
- package/dist/__tests__/cross-device-integration.test.js +305 -0
- package/dist/__tests__/mcp-connection-reliability.test.d.ts +1 -0
- package/dist/__tests__/mcp-connection-reliability.test.js +489 -0
- package/dist/__tests__/setup.d.ts +1 -0
- package/dist/__tests__/setup.js +26 -0
- package/dist/commands/api-keys.js +12 -6
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +420 -50
- package/dist/commands/config.js +519 -1
- package/dist/commands/mcp.js +299 -0
- package/dist/index.js +5 -1
- package/dist/mcp/server/lanonasis-server.d.ts +161 -6
- package/dist/mcp/server/lanonasis-server.js +813 -17
- package/dist/mcp/server/mcp/server/lanonasis-server.js +911 -0
- package/dist/mcp/server/utils/api.js +431 -0
- package/dist/mcp/server/utils/config.js +855 -0
- package/dist/utils/config.d.ts +40 -1
- package/dist/utils/config.js +252 -36
- package/dist/utils/mcp-client.d.ts +83 -2
- package/dist/utils/mcp-client.js +414 -15
- package/package.json +8 -4
package/dist/utils/config.d.ts
CHANGED
|
@@ -4,12 +4,44 @@ interface UserProfile {
|
|
|
4
4
|
role: string;
|
|
5
5
|
plan: string;
|
|
6
6
|
}
|
|
7
|
+
interface CLIConfigData {
|
|
8
|
+
version?: string;
|
|
9
|
+
apiUrl?: string;
|
|
10
|
+
token?: string | undefined;
|
|
11
|
+
user?: UserProfile | undefined;
|
|
12
|
+
lastUpdated?: string;
|
|
13
|
+
mcpServerPath?: string;
|
|
14
|
+
mcpServerUrl?: string;
|
|
15
|
+
mcpUseRemote?: boolean;
|
|
16
|
+
mcpPreference?: 'local' | 'remote' | 'auto';
|
|
17
|
+
discoveredServices?: {
|
|
18
|
+
auth_base: string;
|
|
19
|
+
memory_base: string;
|
|
20
|
+
mcp_base?: string;
|
|
21
|
+
mcp_ws_base: string;
|
|
22
|
+
mcp_sse_base?: string;
|
|
23
|
+
project_scope: string;
|
|
24
|
+
};
|
|
25
|
+
lastServiceDiscovery?: string;
|
|
26
|
+
manualEndpointOverrides?: boolean;
|
|
27
|
+
lastManualEndpointUpdate?: string;
|
|
28
|
+
vendorKey?: string | undefined;
|
|
29
|
+
authMethod?: 'jwt' | 'vendor_key' | 'oauth' | undefined;
|
|
30
|
+
tokenExpiry?: number | undefined;
|
|
31
|
+
lastValidated?: string | undefined;
|
|
32
|
+
deviceId?: string;
|
|
33
|
+
authFailureCount?: number;
|
|
34
|
+
lastAuthFailure?: string | undefined;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
7
37
|
export declare class CLIConfig {
|
|
8
38
|
private configDir;
|
|
9
39
|
private configPath;
|
|
10
40
|
private config;
|
|
11
41
|
private lockFile;
|
|
12
42
|
private static readonly CONFIG_VERSION;
|
|
43
|
+
private authCheckCache;
|
|
44
|
+
private readonly AUTH_CACHE_TTL;
|
|
13
45
|
constructor();
|
|
14
46
|
init(): Promise<void>;
|
|
15
47
|
load(): Promise<void>;
|
|
@@ -20,9 +52,15 @@ export declare class CLIConfig {
|
|
|
20
52
|
private acquireLock;
|
|
21
53
|
private releaseLock;
|
|
22
54
|
getApiUrl(): string;
|
|
23
|
-
discoverServices(): Promise<void>;
|
|
55
|
+
discoverServices(verbose?: boolean): Promise<void>;
|
|
56
|
+
private handleServiceDiscoveryFailure;
|
|
57
|
+
private categorizeServiceDiscoveryError;
|
|
58
|
+
setManualEndpoints(endpoints: Partial<CLIConfigData['discoveredServices']>): Promise<void>;
|
|
59
|
+
hasManualEndpointOverrides(): boolean;
|
|
60
|
+
clearManualEndpointOverrides(): Promise<void>;
|
|
24
61
|
getDiscoveredApiUrl(): string;
|
|
25
62
|
setVendorKey(vendorKey: string): Promise<void>;
|
|
63
|
+
validateVendorKeyFormat(vendorKey: string): string | boolean;
|
|
26
64
|
private validateVendorKeyWithServer;
|
|
27
65
|
getVendorKey(): string | undefined;
|
|
28
66
|
hasVendorKey(): boolean;
|
|
@@ -44,6 +82,7 @@ export declare class CLIConfig {
|
|
|
44
82
|
getLastAuthFailure(): string | undefined;
|
|
45
83
|
shouldDelayAuth(): boolean;
|
|
46
84
|
getAuthDelayMs(): number;
|
|
85
|
+
getDeviceId(): Promise<string>;
|
|
47
86
|
get<T = unknown>(key: string): T;
|
|
48
87
|
set(key: string, value: unknown): void;
|
|
49
88
|
setAndSave(key: string, value: unknown): Promise<void>;
|
package/dist/utils/config.js
CHANGED
|
@@ -9,6 +9,8 @@ export class CLIConfig {
|
|
|
9
9
|
config = {};
|
|
10
10
|
lockFile;
|
|
11
11
|
static CONFIG_VERSION = '1.0.0';
|
|
12
|
+
authCheckCache = null;
|
|
13
|
+
AUTH_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
12
14
|
constructor() {
|
|
13
15
|
this.configDir = path.join(os.homedir(), '.maas');
|
|
14
16
|
this.configPath = path.join(this.configDir, 'config.json');
|
|
@@ -151,13 +153,21 @@ export class CLIConfig {
|
|
|
151
153
|
this.config.apiUrl ||
|
|
152
154
|
'https://api.lanonasis.com/api/v1';
|
|
153
155
|
}
|
|
154
|
-
// Service Discovery Integration
|
|
155
|
-
async discoverServices() {
|
|
156
|
+
// Enhanced Service Discovery Integration
|
|
157
|
+
async discoverServices(verbose = false) {
|
|
158
|
+
const discoveryUrl = 'https://mcp.lanonasis.com/.well-known/onasis.json';
|
|
156
159
|
try {
|
|
157
160
|
// Use axios instead of fetch for consistency
|
|
158
161
|
const axios = (await import('axios')).default;
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
if (verbose) {
|
|
163
|
+
console.log(`🔍 Discovering services from ${discoveryUrl}...`);
|
|
164
|
+
}
|
|
165
|
+
const response = await axios.get(discoveryUrl, {
|
|
166
|
+
timeout: 10000,
|
|
167
|
+
headers: {
|
|
168
|
+
'User-Agent': 'Lanonasis-CLI/3.0.13'
|
|
169
|
+
}
|
|
170
|
+
});
|
|
161
171
|
// Map discovery response to our config format
|
|
162
172
|
const discovered = response.data;
|
|
163
173
|
this.config.discoveredServices = {
|
|
@@ -168,34 +178,134 @@ export class CLIConfig {
|
|
|
168
178
|
mcp_sse_base: discovered.endpoints?.sse || 'https://mcp.lanonasis.com/api/v1/events',
|
|
169
179
|
project_scope: 'lanonasis-maas'
|
|
170
180
|
};
|
|
181
|
+
// Mark discovery as successful
|
|
182
|
+
this.config.lastServiceDiscovery = new Date().toISOString();
|
|
171
183
|
await this.save();
|
|
184
|
+
if (verbose) {
|
|
185
|
+
console.log('✓ Service discovery completed successfully');
|
|
186
|
+
console.log(` Auth: ${this.config.discoveredServices.auth_base}`);
|
|
187
|
+
console.log(` MCP: ${this.config.discoveredServices.mcp_base}`);
|
|
188
|
+
console.log(` WebSocket: ${this.config.discoveredServices.mcp_ws_base}`);
|
|
189
|
+
}
|
|
172
190
|
}
|
|
173
|
-
catch {
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
191
|
+
catch (error) {
|
|
192
|
+
// Enhanced error handling with user-visible messages
|
|
193
|
+
await this.handleServiceDiscoveryFailure(error, verbose);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async handleServiceDiscoveryFailure(error, verbose) {
|
|
197
|
+
const errorType = this.categorizeServiceDiscoveryError(error);
|
|
198
|
+
if (verbose || process.env.CLI_VERBOSE === 'true') {
|
|
199
|
+
console.log('⚠️ Service discovery failed, using cached/fallback endpoints');
|
|
200
|
+
switch (errorType) {
|
|
201
|
+
case 'network_error':
|
|
202
|
+
console.log(' Reason: Network connection failed');
|
|
203
|
+
console.log(' This is normal when offline or behind restrictive firewalls');
|
|
204
|
+
break;
|
|
205
|
+
case 'timeout':
|
|
206
|
+
console.log(' Reason: Request timed out');
|
|
207
|
+
console.log(' The discovery service may be temporarily slow');
|
|
208
|
+
break;
|
|
209
|
+
case 'server_error':
|
|
210
|
+
console.log(' Reason: Discovery service returned an error');
|
|
211
|
+
console.log(' The service may be temporarily unavailable');
|
|
212
|
+
break;
|
|
213
|
+
case 'invalid_response':
|
|
214
|
+
console.log(' Reason: Invalid response format from discovery service');
|
|
215
|
+
console.log(' Using known working endpoints instead');
|
|
216
|
+
break;
|
|
217
|
+
default:
|
|
218
|
+
console.log(` Reason: ${error.message || 'Unknown error'}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Use cached endpoints if available and recent (within 24 hours)
|
|
222
|
+
if (this.config.discoveredServices && this.config.lastServiceDiscovery) {
|
|
223
|
+
const lastDiscovery = new Date(this.config.lastServiceDiscovery);
|
|
224
|
+
const hoursSinceDiscovery = (Date.now() - lastDiscovery.getTime()) / (1000 * 60 * 60);
|
|
225
|
+
if (hoursSinceDiscovery < 24) {
|
|
226
|
+
if (verbose) {
|
|
227
|
+
console.log('✓ Using cached service endpoints (less than 24 hours old)');
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Set fallback service endpoints
|
|
233
|
+
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
|
|
240
|
+
};
|
|
241
|
+
// Mark as fallback (don't set lastServiceDiscovery)
|
|
242
|
+
await this.save();
|
|
243
|
+
if (verbose) {
|
|
244
|
+
console.log('✓ Using fallback service endpoints');
|
|
245
|
+
console.log(' These are the standard production endpoints');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
categorizeServiceDiscoveryError(error) {
|
|
249
|
+
if (error.code) {
|
|
250
|
+
switch (error.code) {
|
|
251
|
+
case 'ECONNREFUSED':
|
|
252
|
+
case 'ENOTFOUND':
|
|
253
|
+
case 'ECONNRESET':
|
|
254
|
+
case 'ENETUNREACH':
|
|
255
|
+
return 'network_error';
|
|
256
|
+
case 'ETIMEDOUT':
|
|
257
|
+
return 'timeout';
|
|
177
258
|
}
|
|
178
|
-
// Set fallback service endpoints to prevent double slash issues
|
|
179
|
-
// Use mcp.lanonasis.com for MCP services (proxied to port 3001)
|
|
180
|
-
this.config.discoveredServices = {
|
|
181
|
-
auth_base: 'https://api.lanonasis.com', // CLI auth goes to central auth system
|
|
182
|
-
memory_base: 'https://api.lanonasis.com/api/v1', // Memory via onasis-core
|
|
183
|
-
mcp_base: 'https://mcp.lanonasis.com/api/v1', // MCP HTTP/REST
|
|
184
|
-
mcp_ws_base: 'wss://mcp.lanonasis.com/ws', // MCP WebSocket
|
|
185
|
-
mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events', // MCP SSE
|
|
186
|
-
project_scope: 'lanonasis-maas' // Correct project scope
|
|
187
|
-
};
|
|
188
|
-
await this.save();
|
|
189
259
|
}
|
|
260
|
+
if (error.response?.status >= 500) {
|
|
261
|
+
return 'server_error';
|
|
262
|
+
}
|
|
263
|
+
if (error.response?.status === 404) {
|
|
264
|
+
return 'invalid_response';
|
|
265
|
+
}
|
|
266
|
+
const message = error.message?.toLowerCase() || '';
|
|
267
|
+
if (message.includes('timeout')) {
|
|
268
|
+
return 'timeout';
|
|
269
|
+
}
|
|
270
|
+
if (message.includes('network') || message.includes('connection')) {
|
|
271
|
+
return 'network_error';
|
|
272
|
+
}
|
|
273
|
+
return 'unknown';
|
|
274
|
+
}
|
|
275
|
+
// Manual endpoint override functionality
|
|
276
|
+
async setManualEndpoints(endpoints) {
|
|
277
|
+
if (!this.config.discoveredServices) {
|
|
278
|
+
// Initialize with defaults first
|
|
279
|
+
await this.discoverServices();
|
|
280
|
+
}
|
|
281
|
+
// Merge manual overrides with existing endpoints
|
|
282
|
+
this.config.discoveredServices = {
|
|
283
|
+
...this.config.discoveredServices,
|
|
284
|
+
...endpoints
|
|
285
|
+
};
|
|
286
|
+
// Mark as manually configured
|
|
287
|
+
this.config.manualEndpointOverrides = true;
|
|
288
|
+
this.config.lastManualEndpointUpdate = new Date().toISOString();
|
|
289
|
+
await this.save();
|
|
290
|
+
}
|
|
291
|
+
hasManualEndpointOverrides() {
|
|
292
|
+
return !!this.config.manualEndpointOverrides;
|
|
293
|
+
}
|
|
294
|
+
async clearManualEndpointOverrides() {
|
|
295
|
+
this.config.manualEndpointOverrides = undefined;
|
|
296
|
+
this.config.lastManualEndpointUpdate = undefined;
|
|
297
|
+
// Rediscover services
|
|
298
|
+
await this.discoverServices();
|
|
190
299
|
}
|
|
191
300
|
getDiscoveredApiUrl() {
|
|
192
301
|
return this.config.discoveredServices?.auth_base || this.getApiUrl();
|
|
193
302
|
}
|
|
194
303
|
// Enhanced authentication support
|
|
195
304
|
async setVendorKey(vendorKey) {
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
305
|
+
// Enhanced format validation with detailed error messages
|
|
306
|
+
const formatValidation = this.validateVendorKeyFormat(vendorKey);
|
|
307
|
+
if (formatValidation !== true) {
|
|
308
|
+
throw new Error(typeof formatValidation === 'string' ? formatValidation : 'Invalid vendor key format');
|
|
199
309
|
}
|
|
200
310
|
// Server-side validation
|
|
201
311
|
await this.validateVendorKeyWithServer(vendorKey);
|
|
@@ -205,6 +315,44 @@ export class CLIConfig {
|
|
|
205
315
|
await this.resetFailureCount(); // Reset failure count on successful auth
|
|
206
316
|
await this.save();
|
|
207
317
|
}
|
|
318
|
+
validateVendorKeyFormat(vendorKey) {
|
|
319
|
+
if (!vendorKey || vendorKey.trim().length === 0) {
|
|
320
|
+
return 'Vendor key is required';
|
|
321
|
+
}
|
|
322
|
+
const trimmed = vendorKey.trim();
|
|
323
|
+
// Check basic format
|
|
324
|
+
if (!trimmed.includes('.')) {
|
|
325
|
+
return 'Invalid vendor key format: Must contain a dot (.) separator. Expected format: pk_xxx.sk_xxx';
|
|
326
|
+
}
|
|
327
|
+
const parts = trimmed.split('.');
|
|
328
|
+
if (parts.length !== 2) {
|
|
329
|
+
return 'Invalid vendor key format: Must have exactly two parts separated by a dot. Expected format: pk_xxx.sk_xxx';
|
|
330
|
+
}
|
|
331
|
+
const [publicPart, secretPart] = parts;
|
|
332
|
+
// Validate public key part
|
|
333
|
+
if (!publicPart.startsWith('pk_')) {
|
|
334
|
+
return 'Invalid vendor key format: First part must start with "pk_". Expected format: pk_xxx.sk_xxx';
|
|
335
|
+
}
|
|
336
|
+
if (publicPart.length < 11) { // pk_ + minimum 8 chars
|
|
337
|
+
return 'Invalid vendor key format: Public key part is too short. Expected format: pk_xxx.sk_xxx (minimum 8 characters after "pk_")';
|
|
338
|
+
}
|
|
339
|
+
const publicKeyContent = publicPart.substring(3); // Remove 'pk_'
|
|
340
|
+
if (!/^[a-zA-Z0-9]+$/.test(publicKeyContent)) {
|
|
341
|
+
return 'Invalid vendor key format: Public key part contains invalid characters. Only letters and numbers are allowed after "pk_"';
|
|
342
|
+
}
|
|
343
|
+
// Validate secret key part
|
|
344
|
+
if (!secretPart.startsWith('sk_')) {
|
|
345
|
+
return 'Invalid vendor key format: Second part must start with "sk_". Expected format: pk_xxx.sk_xxx';
|
|
346
|
+
}
|
|
347
|
+
if (secretPart.length < 19) { // sk_ + minimum 16 chars
|
|
348
|
+
return 'Invalid vendor key format: Secret key part is too short. Expected format: pk_xxx.sk_xxx (minimum 16 characters after "sk_")';
|
|
349
|
+
}
|
|
350
|
+
const secretKeyContent = secretPart.substring(3); // Remove 'sk_'
|
|
351
|
+
if (!/^[a-zA-Z0-9]+$/.test(secretKeyContent)) {
|
|
352
|
+
return 'Invalid vendor key format: Secret key part contains invalid characters. Only letters and numbers are allowed after "sk_"';
|
|
353
|
+
}
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
208
356
|
async validateVendorKeyWithServer(vendorKey) {
|
|
209
357
|
try {
|
|
210
358
|
// Import axios dynamically to avoid circular dependency
|
|
@@ -223,14 +371,45 @@ export class CLIConfig {
|
|
|
223
371
|
});
|
|
224
372
|
}
|
|
225
373
|
catch (error) {
|
|
226
|
-
|
|
227
|
-
|
|
374
|
+
// Provide specific error messages based on response
|
|
375
|
+
if (error.response?.status === 401) {
|
|
376
|
+
const errorData = error.response.data;
|
|
377
|
+
if (errorData?.error?.includes('expired') || errorData?.message?.includes('expired')) {
|
|
378
|
+
throw new Error('Vendor key has expired. Please generate a new key from your dashboard.');
|
|
379
|
+
}
|
|
380
|
+
else if (errorData?.error?.includes('revoked') || errorData?.message?.includes('revoked')) {
|
|
381
|
+
throw new Error('Vendor key has been revoked. Please generate a new key from your dashboard.');
|
|
382
|
+
}
|
|
383
|
+
else if (errorData?.error?.includes('invalid') || errorData?.message?.includes('invalid')) {
|
|
384
|
+
throw new Error('Vendor key is invalid. Please check the key format and ensure it was copied correctly.');
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
throw new Error('Vendor key authentication failed. The key may be invalid, expired, or revoked.');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
else if (error.response?.status === 403) {
|
|
391
|
+
throw new Error('Vendor key access denied. The key may not have sufficient permissions for this operation.');
|
|
228
392
|
}
|
|
229
|
-
else if (error.
|
|
230
|
-
throw new Error('
|
|
393
|
+
else if (error.response?.status === 429) {
|
|
394
|
+
throw new Error('Too many validation attempts. Please wait a moment before trying again.');
|
|
395
|
+
}
|
|
396
|
+
else if (error.response?.status >= 500) {
|
|
397
|
+
throw new Error('Server error during validation. Please try again in a few moments.');
|
|
398
|
+
}
|
|
399
|
+
else if (error.code === 'ECONNREFUSED') {
|
|
400
|
+
throw new Error('Cannot connect to authentication server. Please check your internet connection and try again.');
|
|
401
|
+
}
|
|
402
|
+
else if (error.code === 'ENOTFOUND') {
|
|
403
|
+
throw new Error('Authentication server not found. Please check your internet connection.');
|
|
404
|
+
}
|
|
405
|
+
else if (error.code === 'ETIMEDOUT') {
|
|
406
|
+
throw new Error('Validation request timed out. Please check your internet connection and try again.');
|
|
407
|
+
}
|
|
408
|
+
else if (error.code === 'ECONNRESET') {
|
|
409
|
+
throw new Error('Connection was reset during validation. Please try again.');
|
|
231
410
|
}
|
|
232
411
|
else {
|
|
233
|
-
throw new Error(`Vendor key validation failed: ${error.message}`);
|
|
412
|
+
throw new Error(`Vendor key validation failed: ${error.message || 'Unknown error'}`);
|
|
234
413
|
}
|
|
235
414
|
}
|
|
236
415
|
}
|
|
@@ -280,6 +459,12 @@ export class CLIConfig {
|
|
|
280
459
|
const token = this.getToken();
|
|
281
460
|
if (!token)
|
|
282
461
|
return false;
|
|
462
|
+
// Check cache first
|
|
463
|
+
if (this.authCheckCache && (Date.now() - this.authCheckCache.timestamp) < this.AUTH_CACHE_TTL) {
|
|
464
|
+
return this.authCheckCache.isValid;
|
|
465
|
+
}
|
|
466
|
+
// Local expiry check first (fast)
|
|
467
|
+
let locallyValid = false;
|
|
283
468
|
// Handle simple CLI tokens (format: cli_xxx_timestamp)
|
|
284
469
|
if (token.startsWith('cli_')) {
|
|
285
470
|
// Extract timestamp from CLI token
|
|
@@ -290,21 +475,44 @@ export class CLIConfig {
|
|
|
290
475
|
if (!isNaN(timestamp)) {
|
|
291
476
|
// CLI tokens are valid for 30 days
|
|
292
477
|
const thirtyDaysInMs = 30 * 24 * 60 * 60 * 1000;
|
|
293
|
-
|
|
478
|
+
locallyValid = (Date.now() - timestamp) < thirtyDaysInMs;
|
|
294
479
|
}
|
|
295
480
|
}
|
|
296
|
-
|
|
297
|
-
|
|
481
|
+
else {
|
|
482
|
+
locallyValid = true; // Fallback for old format
|
|
483
|
+
}
|
|
298
484
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
485
|
+
else {
|
|
486
|
+
// Handle JWT tokens
|
|
487
|
+
try {
|
|
488
|
+
const decoded = jwtDecode(token);
|
|
489
|
+
const now = Date.now() / 1000;
|
|
490
|
+
locallyValid = typeof decoded.exp === 'number' && decoded.exp > now;
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
locallyValid = false;
|
|
494
|
+
}
|
|
304
495
|
}
|
|
305
|
-
|
|
496
|
+
// If expired locally, no need to check server
|
|
497
|
+
if (!locallyValid) {
|
|
498
|
+
this.authCheckCache = { isValid: false, timestamp: Date.now() };
|
|
306
499
|
return false;
|
|
307
500
|
}
|
|
501
|
+
// Verify with server (security check)
|
|
502
|
+
try {
|
|
503
|
+
const axios = (await import('axios')).default;
|
|
504
|
+
const response = await axios.post('https://api.lanonasis.com/auth/verify', { token }, { timeout: 5000 });
|
|
505
|
+
const isValid = response.data.valid === true;
|
|
506
|
+
this.authCheckCache = { isValid, timestamp: Date.now() };
|
|
507
|
+
return isValid;
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
// If server check fails, fall back to local validation
|
|
511
|
+
// This allows offline usage but is less secure
|
|
512
|
+
console.warn('⚠️ Unable to verify token with server, using local validation');
|
|
513
|
+
this.authCheckCache = { isValid: locallyValid, timestamp: Date.now() };
|
|
514
|
+
return locallyValid;
|
|
515
|
+
}
|
|
308
516
|
}
|
|
309
517
|
async logout() {
|
|
310
518
|
this.config.token = undefined;
|
|
@@ -447,6 +655,14 @@ export class CLIConfig {
|
|
|
447
655
|
const delay = Math.min(baseDelay * Math.pow(2, failureCount - 3), maxDelay);
|
|
448
656
|
return delay;
|
|
449
657
|
}
|
|
658
|
+
async getDeviceId() {
|
|
659
|
+
if (!this.config.deviceId) {
|
|
660
|
+
// Generate a new device ID
|
|
661
|
+
this.config.deviceId = randomUUID();
|
|
662
|
+
await this.save();
|
|
663
|
+
}
|
|
664
|
+
return this.config.deviceId;
|
|
665
|
+
}
|
|
450
666
|
// Generic get/set methods for MCP and other dynamic config
|
|
451
667
|
get(key) {
|
|
452
668
|
return this.config[key];
|
|
@@ -49,15 +49,64 @@ export declare class MCPClient {
|
|
|
49
49
|
private isConnected;
|
|
50
50
|
private sseConnection;
|
|
51
51
|
private wsConnection;
|
|
52
|
+
private retryAttempts;
|
|
53
|
+
private maxRetries;
|
|
54
|
+
private healthCheckInterval;
|
|
55
|
+
private connectionStartTime;
|
|
56
|
+
private lastHealthCheck;
|
|
52
57
|
constructor();
|
|
53
58
|
/**
|
|
54
59
|
* Initialize the MCP client configuration
|
|
55
60
|
*/
|
|
56
61
|
init(): Promise<void>;
|
|
57
62
|
/**
|
|
58
|
-
* Connect to MCP server
|
|
63
|
+
* Connect to MCP server with retry logic
|
|
59
64
|
*/
|
|
60
65
|
connect(options?: MCPConnectionOptions): Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Connect to MCP server with retry logic and exponential backoff
|
|
68
|
+
*/
|
|
69
|
+
private connectWithRetry;
|
|
70
|
+
/**
|
|
71
|
+
* Handle connection failures with retry logic and specific error messages
|
|
72
|
+
*/
|
|
73
|
+
private handleConnectionFailure;
|
|
74
|
+
/**
|
|
75
|
+
* Check if error is authentication-related
|
|
76
|
+
*/
|
|
77
|
+
private isAuthenticationError;
|
|
78
|
+
/**
|
|
79
|
+
* Provide authentication-specific guidance
|
|
80
|
+
*/
|
|
81
|
+
private provideAuthenticationGuidance;
|
|
82
|
+
/**
|
|
83
|
+
* Provide network troubleshooting guidance
|
|
84
|
+
*/
|
|
85
|
+
private provideNetworkTroubleshootingGuidance;
|
|
86
|
+
/**
|
|
87
|
+
* Calculate exponential backoff delay with jitter
|
|
88
|
+
*/
|
|
89
|
+
private exponentialBackoff;
|
|
90
|
+
/**
|
|
91
|
+
* Validate authentication credentials before attempting MCP connection
|
|
92
|
+
*/
|
|
93
|
+
private validateAuthBeforeConnect;
|
|
94
|
+
/**
|
|
95
|
+
* Validate vendor key format
|
|
96
|
+
*/
|
|
97
|
+
private validateVendorKeyFormat;
|
|
98
|
+
/**
|
|
99
|
+
* Validate and refresh token if needed
|
|
100
|
+
*/
|
|
101
|
+
private validateAndRefreshToken;
|
|
102
|
+
/**
|
|
103
|
+
* Refresh token if needed
|
|
104
|
+
*/
|
|
105
|
+
private refreshTokenIfNeeded;
|
|
106
|
+
/**
|
|
107
|
+
* Validate token with server
|
|
108
|
+
*/
|
|
109
|
+
private validateTokenWithServer;
|
|
61
110
|
/**
|
|
62
111
|
* Initialize SSE connection for real-time updates
|
|
63
112
|
*/
|
|
@@ -70,6 +119,34 @@ export declare class MCPClient {
|
|
|
70
119
|
* Send a message over the WebSocket connection
|
|
71
120
|
*/
|
|
72
121
|
private sendWebSocketMessage;
|
|
122
|
+
/**
|
|
123
|
+
* Start health monitoring for the connection
|
|
124
|
+
*/
|
|
125
|
+
private startHealthMonitoring;
|
|
126
|
+
/**
|
|
127
|
+
* Stop health monitoring
|
|
128
|
+
*/
|
|
129
|
+
private stopHealthMonitoring;
|
|
130
|
+
/**
|
|
131
|
+
* Perform a health check on the current connection
|
|
132
|
+
*/
|
|
133
|
+
private performHealthCheck;
|
|
134
|
+
/**
|
|
135
|
+
* Check WebSocket connection health
|
|
136
|
+
*/
|
|
137
|
+
private checkWebSocketHealth;
|
|
138
|
+
/**
|
|
139
|
+
* Check remote connection health
|
|
140
|
+
*/
|
|
141
|
+
private checkRemoteHealth;
|
|
142
|
+
/**
|
|
143
|
+
* Check local connection health
|
|
144
|
+
*/
|
|
145
|
+
private checkLocalHealth;
|
|
146
|
+
/**
|
|
147
|
+
* Handle health check failure by attempting reconnection
|
|
148
|
+
*/
|
|
149
|
+
private handleHealthCheckFailure;
|
|
73
150
|
/**
|
|
74
151
|
* Disconnect from MCP server
|
|
75
152
|
*/
|
|
@@ -94,12 +171,16 @@ export declare class MCPClient {
|
|
|
94
171
|
*/
|
|
95
172
|
isConnectedToServer(): boolean;
|
|
96
173
|
/**
|
|
97
|
-
* Get connection status details
|
|
174
|
+
* Get connection status details with health information
|
|
98
175
|
*/
|
|
99
176
|
getConnectionStatus(): {
|
|
100
177
|
connected: boolean;
|
|
101
178
|
mode: string;
|
|
102
179
|
server?: string;
|
|
180
|
+
latency?: number;
|
|
181
|
+
lastHealthCheck?: Date;
|
|
182
|
+
connectionUptime?: number;
|
|
183
|
+
failureCount: number;
|
|
103
184
|
};
|
|
104
185
|
}
|
|
105
186
|
export declare function getMCPClient(): MCPClient;
|