@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.
- package/README.md +57 -13
- package/dist/commands/auth.js +11 -5
- package/dist/commands/completion.js +2 -0
- package/dist/commands/config.js +4 -4
- package/dist/commands/enhanced-memory.js +1 -1
- package/dist/commands/mcp.js +15 -9
- package/dist/core/achievements.js +1 -1
- package/dist/core/power-mode.js +5 -3
- package/dist/core/welcome.js +6 -6
- package/dist/enhanced-cli.js +6 -3
- package/dist/index-simple.js +5 -1
- package/dist/index.js +5 -1
- package/dist/mcp/access-control.d.ts +1 -1
- package/dist/mcp/access-control.js +4 -4
- package/dist/mcp/client/enhanced-client.js +5 -7
- package/dist/mcp/schemas/tool-schemas.d.ts +1 -1
- package/dist/mcp/schemas/tool-schemas.js +1 -1
- package/dist/mcp/server/lanonasis-server.d.ts +2 -1
- package/dist/mcp/server/lanonasis-server.js +7 -5
- package/dist/mcp/transports/transport-manager.js +3 -3
- package/dist/mcp-server.d.ts +1 -0
- package/dist/mcp-server.js +43 -9
- package/dist/utils/config.d.ts +10 -1
- package/dist/utils/config.js +97 -17
- package/dist/utils/mcp-client.d.ts +21 -2
- package/dist/utils/mcp-client.js +117 -46
- package/package.json +3 -3
- package/dist/__tests__/auth-persistence.test.d.ts +0 -1
- package/dist/__tests__/auth-persistence.test.js +0 -243
- package/dist/__tests__/cross-device-integration.test.d.ts +0 -1
- package/dist/__tests__/cross-device-integration.test.js +0 -305
- package/dist/__tests__/mcp-connection-reliability.test.d.ts +0 -1
- package/dist/__tests__/mcp-connection-reliability.test.js +0 -489
- package/dist/__tests__/setup.d.ts +0 -1
- package/dist/__tests__/setup.js +0 -26
- package/dist/mcp/server/mcp/server/lanonasis-server.js +0 -911
- package/dist/mcp/server/utils/api.js +0 -431
- 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
|
-
|
|
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(
|
|
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(
|
|
127
|
-
const key = `${
|
|
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(
|
|
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 (
|
|
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(
|
|
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,
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
336
|
+
static toJsonSchema(_schema) {
|
|
337
337
|
// This would require zodToJsonSchema package
|
|
338
338
|
// For now, return a simplified version
|
|
339
339
|
return {
|
|
@@ -600,9 +600,10 @@ Please choose an option (1-4):`
|
|
|
600
600
|
});
|
|
601
601
|
} /**
|
|
602
602
|
|
|
603
|
-
|
|
603
|
+
/**
|
|
604
|
+
* Handle tool calls
|
|
604
605
|
*/
|
|
605
|
-
async handleToolCall(name, args,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
}
|
package/dist/mcp-server.d.ts
CHANGED
package/dist/mcp-server.js
CHANGED
|
@@ -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 {
|
|
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
|
|
37
|
-
const mcpServerPath = join(__dirname, '../../../mcp-server/
|
|
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:
|
|
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
|
-
|
|
92
|
-
|
|
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(
|
|
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;
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -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>;
|
package/dist/utils/config.js
CHANGED
|
@@ -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
|
-
|
|
246
|
+
const fallback = this.resolveFallbackEndpoints();
|
|
233
247
|
this.config.discoveredServices = {
|
|
234
|
-
|
|
235
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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:
|
|
28
|
+
forEach?: (callback: (item: unknown, index: number) => void) => void;
|
|
28
29
|
code?: number;
|
|
29
30
|
message?: string;
|
|
30
|
-
response?:
|
|
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
|
*/
|