@lanonasis/cli 3.7.3 → 3.7.5
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 +7 -7
- package/dist/mcp/access-control.js +10 -2
- package/dist/mcp/client/enhanced-client.js +6 -6
- package/dist/mcp/schemas/tool-schemas.d.ts +4 -4
- package/dist/mcp/server/lanonasis-server.js +8 -7
- package/dist/mcp/transports/transport-manager.js +2 -2
- package/dist/mcp-server.js +7 -3
- package/dist/utils/api.js +16 -15
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.js +64 -75
- package/dist/utils/mcp-client.js +19 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,9 +26,9 @@ lanonasis --version # or onasis --version
|
|
|
26
26
|
onasis guide
|
|
27
27
|
|
|
28
28
|
# Quick manual setup
|
|
29
|
-
onasis init
|
|
30
|
-
onasis login --vendor-key
|
|
31
|
-
onasis health
|
|
29
|
+
onasis init # Initialize configuration
|
|
30
|
+
onasis login --vendor-key <your-vendor-key> # Authenticate with vendor key
|
|
31
|
+
onasis health # Verify system health
|
|
32
32
|
|
|
33
33
|
# Create your first memory
|
|
34
34
|
onasis memory create --title "Welcome" --content "My first memory"
|
|
@@ -85,10 +85,10 @@ onasis login --vendor-key pk_xxxxx.sk_xxxxx // ✅ Automatically hashed
|
|
|
85
85
|
|
|
86
86
|
### 1. Vendor Key Authentication (Recommended)
|
|
87
87
|
|
|
88
|
-
Best for API integrations and automation:
|
|
88
|
+
Best for API integrations and automation. Copy the vendor key value exactly as shown in your LanOnasis dashboard (keys may vary in format):
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
|
-
onasis login --vendor-key
|
|
91
|
+
onasis login --vendor-key <your-vendor-key>
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
### 2. OAuth Browser Authentication
|
|
@@ -288,7 +288,7 @@ Location: `~/.maas/config.json`
|
|
|
288
288
|
"apiUrl": "https://api.lanonasis.com/api/v1",
|
|
289
289
|
"defaultOutputFormat": "table",
|
|
290
290
|
"mcpPreference": "auto",
|
|
291
|
-
"vendorKey": "
|
|
291
|
+
"vendorKey": "<your-vendor-key>"
|
|
292
292
|
}
|
|
293
293
|
```
|
|
294
294
|
|
|
@@ -374,7 +374,7 @@ onasis auth status
|
|
|
374
374
|
|
|
375
375
|
# Re-authenticate
|
|
376
376
|
onasis auth logout
|
|
377
|
-
onasis login --vendor-key
|
|
377
|
+
onasis login --vendor-key <your-vendor-key>
|
|
378
378
|
```
|
|
379
379
|
|
|
380
380
|
#### Connection Issues
|
|
@@ -64,8 +64,16 @@ export class MemoryAccessControl {
|
|
|
64
64
|
// Get shared memories based on permissions
|
|
65
65
|
const sharedMemories = await this.getSharedMemories(userId, appId);
|
|
66
66
|
// Combine and deduplicate
|
|
67
|
-
const
|
|
68
|
-
|
|
67
|
+
const combined = [...ownMemories, ...sharedMemories];
|
|
68
|
+
const deduped = [];
|
|
69
|
+
const seen = new Set();
|
|
70
|
+
combined.forEach(memoryId => {
|
|
71
|
+
if (!seen.has(memoryId)) {
|
|
72
|
+
seen.add(memoryId);
|
|
73
|
+
deduped.push(memoryId);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return deduped;
|
|
69
77
|
}
|
|
70
78
|
catch (error) {
|
|
71
79
|
logger.error('Failed to get accessible memories', { error, userId, appId });
|
|
@@ -45,11 +45,11 @@ export class EnhancedMCPClient extends EventEmitter {
|
|
|
45
45
|
}));
|
|
46
46
|
await Promise.allSettled(connectionPromises);
|
|
47
47
|
// Start health monitoring for connected servers
|
|
48
|
-
|
|
48
|
+
results.forEach((success, name) => {
|
|
49
49
|
if (success) {
|
|
50
50
|
this.startHealthMonitoring(name);
|
|
51
51
|
}
|
|
52
|
-
}
|
|
52
|
+
});
|
|
53
53
|
return results;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
@@ -327,12 +327,12 @@ export class EnhancedMCPClient extends EventEmitter {
|
|
|
327
327
|
*/
|
|
328
328
|
async disconnectAll() {
|
|
329
329
|
// Stop all health monitoring
|
|
330
|
-
|
|
330
|
+
this.healthCheckIntervals.forEach(interval => {
|
|
331
331
|
clearInterval(interval);
|
|
332
|
-
}
|
|
332
|
+
});
|
|
333
333
|
this.healthCheckIntervals.clear();
|
|
334
334
|
// Disconnect all clients
|
|
335
|
-
|
|
335
|
+
await Promise.all(Array.from(this.clients.entries()).map(async ([name, client]) => {
|
|
336
336
|
try {
|
|
337
337
|
await client.close();
|
|
338
338
|
console.log(chalk.gray(`Disconnected from ${name}`));
|
|
@@ -340,7 +340,7 @@ export class EnhancedMCPClient extends EventEmitter {
|
|
|
340
340
|
catch {
|
|
341
341
|
console.log(chalk.yellow(`Warning: Error disconnecting from ${name}`));
|
|
342
342
|
}
|
|
343
|
-
}
|
|
343
|
+
}));
|
|
344
344
|
this.clients.clear();
|
|
345
345
|
this.transports.clear();
|
|
346
346
|
this.connectionStatus.clear();
|
|
@@ -202,13 +202,13 @@ export declare const SystemConfigSchema: z.ZodObject<{
|
|
|
202
202
|
}, "strip", z.ZodTypeAny, {
|
|
203
203
|
value?: any;
|
|
204
204
|
action?: "get" | "set" | "reset";
|
|
205
|
-
key?: string;
|
|
206
205
|
scope?: "user" | "global";
|
|
206
|
+
key?: string;
|
|
207
207
|
}, {
|
|
208
208
|
value?: any;
|
|
209
209
|
action?: "get" | "set" | "reset";
|
|
210
|
-
key?: string;
|
|
211
210
|
scope?: "user" | "global";
|
|
211
|
+
key?: string;
|
|
212
212
|
}>;
|
|
213
213
|
export declare const BulkOperationSchema: z.ZodObject<{
|
|
214
214
|
operation: z.ZodEnum<["create", "update", "delete"]>;
|
|
@@ -580,13 +580,13 @@ export declare const MCPSchemas: {
|
|
|
580
580
|
}, "strip", z.ZodTypeAny, {
|
|
581
581
|
value?: any;
|
|
582
582
|
action?: "get" | "set" | "reset";
|
|
583
|
-
key?: string;
|
|
584
583
|
scope?: "user" | "global";
|
|
584
|
+
key?: string;
|
|
585
585
|
}, {
|
|
586
586
|
value?: any;
|
|
587
587
|
action?: "get" | "set" | "reset";
|
|
588
|
-
key?: string;
|
|
589
588
|
scope?: "user" | "global";
|
|
589
|
+
key?: string;
|
|
590
590
|
}>;
|
|
591
591
|
};
|
|
592
592
|
operations: {
|
|
@@ -916,7 +916,7 @@ Please choose an option (1-4):`
|
|
|
916
916
|
connectionsByTransport: {}
|
|
917
917
|
};
|
|
918
918
|
const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
|
|
919
|
-
|
|
919
|
+
this.connectionPool.forEach(connection => {
|
|
920
920
|
// Count as active if there was activity in the last 5 minutes
|
|
921
921
|
if (connection.lastActivity.getTime() > fiveMinutesAgo) {
|
|
922
922
|
stats.activeConnections++;
|
|
@@ -926,7 +926,7 @@ Please choose an option (1-4):`
|
|
|
926
926
|
}
|
|
927
927
|
stats.connectionsByTransport[connection.transport] =
|
|
928
928
|
(stats.connectionsByTransport[connection.transport] || 0) + 1;
|
|
929
|
-
}
|
|
929
|
+
});
|
|
930
930
|
return stats;
|
|
931
931
|
}
|
|
932
932
|
/**
|
|
@@ -953,12 +953,13 @@ Please choose an option (1-4):`
|
|
|
953
953
|
cleanupStaleConnections() {
|
|
954
954
|
const tenMinutesAgo = Date.now() - (10 * 60 * 1000);
|
|
955
955
|
const staleConnections = [];
|
|
956
|
-
|
|
956
|
+
this.connectionPool.forEach((connection, clientId) => {
|
|
957
957
|
if (connection.lastActivity.getTime() < tenMinutesAgo) {
|
|
958
958
|
staleConnections.push(clientId);
|
|
959
959
|
}
|
|
960
|
-
}
|
|
961
|
-
for (
|
|
960
|
+
});
|
|
961
|
+
for (let i = 0; i < staleConnections.length; i++) {
|
|
962
|
+
const clientId = staleConnections[i];
|
|
962
963
|
this.removeConnection(clientId);
|
|
963
964
|
if (this.options.verbose) {
|
|
964
965
|
console.log(chalk.yellow(`🧹 Cleaned up stale connection: ${clientId}`));
|
|
@@ -1385,12 +1386,12 @@ Please choose an option (1-4):`
|
|
|
1385
1386
|
*/
|
|
1386
1387
|
getTransportStatus() {
|
|
1387
1388
|
const failures = {};
|
|
1388
|
-
|
|
1389
|
+
this.transportFailures.forEach((failure, transport) => {
|
|
1389
1390
|
failures[transport] = {
|
|
1390
1391
|
count: failure.count,
|
|
1391
1392
|
lastFailure: failure.lastFailure.toISOString()
|
|
1392
1393
|
};
|
|
1393
|
-
}
|
|
1394
|
+
});
|
|
1394
1395
|
return {
|
|
1395
1396
|
supportedTransports: this.supportedTransports,
|
|
1396
1397
|
preferredTransport: this.options.preferredTransport || 'stdio',
|
|
@@ -411,9 +411,9 @@ export class MCPTransportManager {
|
|
|
411
411
|
*/
|
|
412
412
|
getStatuses() {
|
|
413
413
|
const statuses = {};
|
|
414
|
-
|
|
414
|
+
this.transports.forEach((transport, name) => {
|
|
415
415
|
statuses[name] = transport.isConnected();
|
|
416
|
-
}
|
|
416
|
+
});
|
|
417
417
|
return statuses;
|
|
418
418
|
}
|
|
419
419
|
/**
|
package/dist/mcp-server.js
CHANGED
|
@@ -51,10 +51,14 @@ export class CLIMCPServer {
|
|
|
51
51
|
}
|
|
52
52
|
candidates.add(join(process.cwd(), 'mcp-server/dist/cli-aligned-mcp-server.js'));
|
|
53
53
|
candidates.add(join(__dirname, '../../../mcp-server/dist/cli-aligned-mcp-server.js'));
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
let resolvedPath = null;
|
|
55
|
+
candidates.forEach(candidate => {
|
|
56
|
+
if (!resolvedPath && candidate && existsSync(candidate)) {
|
|
57
|
+
resolvedPath = candidate;
|
|
57
58
|
}
|
|
59
|
+
});
|
|
60
|
+
if (resolvedPath) {
|
|
61
|
+
return resolvedPath;
|
|
58
62
|
}
|
|
59
63
|
throw new Error('Unable to locate the CLI-aligned MCP server. Set MCP_SERVER_PATH or install @lanonasis/mcp-server.');
|
|
60
64
|
}
|
package/dist/utils/api.js
CHANGED
|
@@ -2,7 +2,6 @@ import axios from 'axios';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
4
|
import { CLIConfig } from './config.js';
|
|
5
|
-
import { ensureApiKeyHash } from './hash-utils.js';
|
|
6
5
|
export class APIClient {
|
|
7
6
|
client;
|
|
8
7
|
config;
|
|
@@ -31,7 +30,8 @@ export class APIClient {
|
|
|
31
30
|
const vendorKey = this.config.getVendorKey();
|
|
32
31
|
if (vendorKey) {
|
|
33
32
|
// Vendor key authentication (validated server-side)
|
|
34
|
-
|
|
33
|
+
// Send raw key - server handles hashing for comparison
|
|
34
|
+
config.headers['X-API-Key'] = vendorKey;
|
|
35
35
|
config.headers['X-Auth-Method'] = 'vendor_key';
|
|
36
36
|
}
|
|
37
37
|
else if (token) {
|
|
@@ -100,61 +100,62 @@ export class APIClient {
|
|
|
100
100
|
return response.data;
|
|
101
101
|
}
|
|
102
102
|
// Memory operations - aligned with existing schema
|
|
103
|
+
// All memory endpoints use /api/v1/memory path
|
|
103
104
|
async createMemory(data) {
|
|
104
|
-
const response = await this.client.post('/memory', data);
|
|
105
|
+
const response = await this.client.post('/api/v1/memory', data);
|
|
105
106
|
return response.data;
|
|
106
107
|
}
|
|
107
108
|
async getMemories(params = {}) {
|
|
108
|
-
const response = await this.client.get('/memory', { params });
|
|
109
|
+
const response = await this.client.get('/api/v1/memory', { params });
|
|
109
110
|
return response.data;
|
|
110
111
|
}
|
|
111
112
|
async getMemory(id) {
|
|
112
|
-
const response = await this.client.get(`/memory/${id}`);
|
|
113
|
+
const response = await this.client.get(`/api/v1/memory/${id}`);
|
|
113
114
|
return response.data;
|
|
114
115
|
}
|
|
115
116
|
async updateMemory(id, data) {
|
|
116
|
-
const response = await this.client.put(`/memory/${id}`, data);
|
|
117
|
+
const response = await this.client.put(`/api/v1/memory/${id}`, data);
|
|
117
118
|
return response.data;
|
|
118
119
|
}
|
|
119
120
|
async deleteMemory(id) {
|
|
120
|
-
await this.client.delete(`/memory/${id}`);
|
|
121
|
+
await this.client.delete(`/api/v1/memory/${id}`);
|
|
121
122
|
}
|
|
122
123
|
async searchMemories(query, options = {}) {
|
|
123
|
-
const response = await this.client.post('/memory/search', {
|
|
124
|
+
const response = await this.client.post('/api/v1/memory/search', {
|
|
124
125
|
query,
|
|
125
126
|
...options
|
|
126
127
|
});
|
|
127
128
|
return response.data;
|
|
128
129
|
}
|
|
129
130
|
async getMemoryStats() {
|
|
130
|
-
const response = await this.client.get('/memory/stats');
|
|
131
|
+
const response = await this.client.get('/api/v1/memory/stats');
|
|
131
132
|
return response.data;
|
|
132
133
|
}
|
|
133
134
|
async bulkDeleteMemories(memoryIds) {
|
|
134
|
-
const response = await this.client.post('/memory/bulk/delete', {
|
|
135
|
+
const response = await this.client.post('/api/v1/memory/bulk/delete', {
|
|
135
136
|
memory_ids: memoryIds
|
|
136
137
|
});
|
|
137
138
|
return response.data;
|
|
138
139
|
}
|
|
139
140
|
// Topic operations - working with existing memory_topics table
|
|
140
141
|
async createTopic(data) {
|
|
141
|
-
const response = await this.client.post('/topics', data);
|
|
142
|
+
const response = await this.client.post('/api/v1/topics', data);
|
|
142
143
|
return response.data;
|
|
143
144
|
}
|
|
144
145
|
async getTopics() {
|
|
145
|
-
const response = await this.client.get('/topics');
|
|
146
|
+
const response = await this.client.get('/api/v1/topics');
|
|
146
147
|
return response.data;
|
|
147
148
|
}
|
|
148
149
|
async getTopic(id) {
|
|
149
|
-
const response = await this.client.get(`/topics/${id}`);
|
|
150
|
+
const response = await this.client.get(`/api/v1/topics/${id}`);
|
|
150
151
|
return response.data;
|
|
151
152
|
}
|
|
152
153
|
async updateTopic(id, data) {
|
|
153
|
-
const response = await this.client.put(`/topics/${id}`, data);
|
|
154
|
+
const response = await this.client.put(`/api/v1/topics/${id}`, data);
|
|
154
155
|
return response.data;
|
|
155
156
|
}
|
|
156
157
|
async deleteTopic(id) {
|
|
157
|
-
await this.client.delete(`/topics/${id}`);
|
|
158
|
+
await this.client.delete(`/api/v1/topics/${id}`);
|
|
158
159
|
}
|
|
159
160
|
// Health check
|
|
160
161
|
async getHealth() {
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -63,6 +63,7 @@ export declare class CLIConfig {
|
|
|
63
63
|
getApiUrl(): string;
|
|
64
64
|
getApiUrlsWithFallbacks(): string[];
|
|
65
65
|
discoverServices(verbose?: boolean): Promise<void>;
|
|
66
|
+
private normalizeServiceError;
|
|
66
67
|
private handleServiceDiscoveryFailure;
|
|
67
68
|
private categorizeServiceDiscoveryError;
|
|
68
69
|
private resolveFallbackEndpoints;
|
package/dist/utils/config.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as os from 'os';
|
|
|
4
4
|
import { jwtDecode } from 'jwt-decode';
|
|
5
5
|
import { randomUUID } from 'crypto';
|
|
6
6
|
import { ApiKeyStorage } from '@lanonasis/oauth-client';
|
|
7
|
+
import axios from 'axios';
|
|
7
8
|
export class CLIConfig {
|
|
8
9
|
configDir;
|
|
9
10
|
configPath;
|
|
@@ -167,9 +168,11 @@ export class CLIConfig {
|
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
getApiUrl() {
|
|
170
|
-
|
|
171
|
+
const baseUrl = process.env.MEMORY_API_URL ||
|
|
171
172
|
this.config.apiUrl ||
|
|
172
|
-
'https://
|
|
173
|
+
'https://mcp.lanonasis.com';
|
|
174
|
+
// Ensure we don't double-append /api/v1 - strip it if present since APIClient adds it
|
|
175
|
+
return baseUrl.replace(/\/api\/v1\/?$/, '');
|
|
173
176
|
}
|
|
174
177
|
// Get API URLs with fallbacks - try multiple endpoints
|
|
175
178
|
getApiUrlsWithFallbacks() {
|
|
@@ -183,13 +186,14 @@ export class CLIConfig {
|
|
|
183
186
|
}
|
|
184
187
|
// Enhanced Service Discovery Integration
|
|
185
188
|
async discoverServices(verbose = false) {
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
const isTestEnvironment = process.env.NODE_ENV === 'test';
|
|
190
|
+
const forceDiscovery = process.env.FORCE_SERVICE_DISCOVERY === 'true';
|
|
191
|
+
if ((isTestEnvironment && !forceDiscovery) || process.env.SKIP_SERVICE_DISCOVERY === 'true') {
|
|
188
192
|
if (!this.config.discoveredServices) {
|
|
189
193
|
this.config.discoveredServices = {
|
|
190
194
|
auth_base: 'https://auth.lanonasis.com',
|
|
191
|
-
memory_base: 'https://mcp.lanonasis.com/api/v1
|
|
192
|
-
mcp_base: 'https://mcp.lanonasis.com/api/v1',
|
|
195
|
+
memory_base: 'https://mcp.lanonasis.com', // Base URL without /api/v1
|
|
196
|
+
mcp_base: 'https://mcp.lanonasis.com/api/v1', // Full MCP REST path
|
|
193
197
|
mcp_ws_base: 'wss://mcp.lanonasis.com/ws',
|
|
194
198
|
mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events',
|
|
195
199
|
project_scope: 'lanonasis-maas'
|
|
@@ -236,25 +240,24 @@ export class CLIConfig {
|
|
|
236
240
|
throw lastError || new Error('All service discovery URLs failed');
|
|
237
241
|
}
|
|
238
242
|
try {
|
|
239
|
-
// Map discovery response to our config format
|
|
240
243
|
const discovered = response.data;
|
|
241
|
-
// Extract auth base, but filter out localhost URLs
|
|
242
244
|
let authBase = discovered.auth?.base || discovered.auth?.login?.replace('/auth/login', '') || '';
|
|
243
|
-
// Override localhost with production auth endpoint
|
|
244
245
|
if (authBase.includes('localhost') || authBase.includes('127.0.0.1')) {
|
|
245
246
|
authBase = 'https://auth.lanonasis.com';
|
|
246
247
|
}
|
|
247
|
-
|
|
248
|
+
// Memory base should be the MCP base URL without /api/v1 suffix
|
|
249
|
+
// The API client will append the path as needed
|
|
250
|
+
const rawMemoryBase = discovered.endpoints?.http || 'https://mcp.lanonasis.com/api/v1';
|
|
251
|
+
const memoryBase = rawMemoryBase.replace(/\/api\/v1\/?$/, '') || 'https://mcp.lanonasis.com';
|
|
248
252
|
this.config.discoveredServices = {
|
|
249
253
|
auth_base: authBase || 'https://auth.lanonasis.com',
|
|
250
254
|
memory_base: memoryBase,
|
|
251
|
-
mcp_base: memoryBase
|
|
255
|
+
mcp_base: `${memoryBase}/api/v1`, // Full path for MCP REST calls
|
|
252
256
|
mcp_ws_base: discovered.endpoints?.websocket || 'wss://mcp.lanonasis.com/ws',
|
|
253
|
-
mcp_sse_base: discovered.endpoints?.sse ||
|
|
257
|
+
mcp_sse_base: discovered.endpoints?.sse || `${memoryBase}/api/v1/events`,
|
|
254
258
|
project_scope: 'lanonasis-maas'
|
|
255
259
|
};
|
|
256
260
|
this.config.apiUrl = memoryBase;
|
|
257
|
-
// Mark discovery as successful
|
|
258
261
|
this.config.lastServiceDiscovery = new Date().toISOString();
|
|
259
262
|
await this.save();
|
|
260
263
|
if (verbose) {
|
|
@@ -265,10 +268,24 @@ export class CLIConfig {
|
|
|
265
268
|
}
|
|
266
269
|
}
|
|
267
270
|
catch (error) {
|
|
268
|
-
|
|
269
|
-
await this.handleServiceDiscoveryFailure(
|
|
271
|
+
const normalizedError = this.normalizeServiceError(error);
|
|
272
|
+
await this.handleServiceDiscoveryFailure(normalizedError, verbose);
|
|
270
273
|
}
|
|
271
274
|
}
|
|
275
|
+
normalizeServiceError(error) {
|
|
276
|
+
if (error && typeof error === 'object') {
|
|
277
|
+
if (error instanceof Error) {
|
|
278
|
+
return {
|
|
279
|
+
...error,
|
|
280
|
+
message: error.message
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return error;
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
message: typeof error === 'string' ? error : JSON.stringify(error)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
272
289
|
async handleServiceDiscoveryFailure(error, verbose) {
|
|
273
290
|
const errorType = this.categorizeServiceDiscoveryError(error);
|
|
274
291
|
if (verbose || process.env.CLI_VERBOSE === 'true') {
|
|
@@ -294,7 +311,6 @@ export class CLIConfig {
|
|
|
294
311
|
console.log(` Reason: ${error.message || 'Unknown error'}`);
|
|
295
312
|
}
|
|
296
313
|
}
|
|
297
|
-
// Use cached endpoints if available and recent (within 24 hours)
|
|
298
314
|
if (this.config.discoveredServices && this.config.lastServiceDiscovery) {
|
|
299
315
|
const lastDiscovery = new Date(this.config.lastServiceDiscovery);
|
|
300
316
|
const hoursSinceDiscovery = (Date.now() - lastDiscovery.getTime()) / (1000 * 60 * 60);
|
|
@@ -311,7 +327,6 @@ export class CLIConfig {
|
|
|
311
327
|
project_scope: 'lanonasis-maas'
|
|
312
328
|
};
|
|
313
329
|
this.config.apiUrl = fallback.endpoints.memory_base;
|
|
314
|
-
// Mark as fallback (don't set lastServiceDiscovery)
|
|
315
330
|
await this.save();
|
|
316
331
|
this.logFallbackUsage(fallback.source, this.config.discoveredServices);
|
|
317
332
|
if (verbose) {
|
|
@@ -331,7 +346,7 @@ export class CLIConfig {
|
|
|
331
346
|
return 'timeout';
|
|
332
347
|
}
|
|
333
348
|
}
|
|
334
|
-
if (error.response?.status >= 500) {
|
|
349
|
+
if ((error.response?.status ?? 0) >= 500) {
|
|
335
350
|
return 'server_error';
|
|
336
351
|
}
|
|
337
352
|
if (error.response?.status === 404) {
|
|
@@ -356,8 +371,8 @@ export class CLIConfig {
|
|
|
356
371
|
const nodeEnv = (process.env.NODE_ENV ?? '').toLowerCase();
|
|
357
372
|
const isDevEnvironment = nodeEnv === 'development' || nodeEnv === 'test';
|
|
358
373
|
const defaultAuthBase = isDevEnvironment ? 'http://localhost:4000' : 'https://auth.lanonasis.com';
|
|
359
|
-
const defaultMemoryBase = isDevEnvironment ? 'http://localhost:4000
|
|
360
|
-
const defaultMcpBase = isDevEnvironment ? 'http://localhost:4100/api/v1' : 'https://mcp.lanonasis.com/api/v1';
|
|
374
|
+
const defaultMemoryBase = isDevEnvironment ? 'http://localhost:4000' : 'https://mcp.lanonasis.com'; // Base URL without /api/v1
|
|
375
|
+
const defaultMcpBase = isDevEnvironment ? 'http://localhost:4100/api/v1' : 'https://mcp.lanonasis.com/api/v1'; // Full MCP REST path
|
|
361
376
|
const defaultMcpWsBase = isDevEnvironment ? 'ws://localhost:4100/ws' : 'wss://mcp.lanonasis.com/ws';
|
|
362
377
|
const defaultMcpSseBase = isDevEnvironment ? 'http://localhost:4100/api/v1/events' : 'https://mcp.lanonasis.com/api/v1/events';
|
|
363
378
|
const endpoints = {
|
|
@@ -421,9 +436,17 @@ export class CLIConfig {
|
|
|
421
436
|
// Initialize with defaults first
|
|
422
437
|
await this.discoverServices();
|
|
423
438
|
}
|
|
439
|
+
const currentServices = this.config.discoveredServices ?? {
|
|
440
|
+
auth_base: 'https://auth.lanonasis.com',
|
|
441
|
+
memory_base: 'https://mcp.lanonasis.com', // Base URL without /api/v1
|
|
442
|
+
mcp_base: 'https://mcp.lanonasis.com/api/v1', // Full MCP REST path
|
|
443
|
+
mcp_ws_base: 'wss://mcp.lanonasis.com/ws',
|
|
444
|
+
mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events',
|
|
445
|
+
project_scope: 'lanonasis-maas'
|
|
446
|
+
};
|
|
424
447
|
// Merge manual overrides with existing endpoints
|
|
425
448
|
this.config.discoveredServices = {
|
|
426
|
-
...
|
|
449
|
+
...currentServices,
|
|
427
450
|
...endpoints
|
|
428
451
|
};
|
|
429
452
|
// Mark as manually configured
|
|
@@ -483,56 +506,26 @@ export class CLIConfig {
|
|
|
483
506
|
return true;
|
|
484
507
|
}
|
|
485
508
|
async validateVendorKeyWithServer(vendorKey) {
|
|
509
|
+
if (process.env.SKIP_SERVER_VALIDATION === 'true') {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
486
512
|
try {
|
|
487
513
|
// Import axios dynamically to avoid circular dependency
|
|
488
|
-
const axios = (await import('axios')).default;
|
|
489
514
|
// Ensure service discovery is done
|
|
490
515
|
await this.discoverServices();
|
|
491
516
|
const authBase = this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com';
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
];
|
|
499
|
-
let lastError;
|
|
500
|
-
let validated = false;
|
|
501
|
-
for (const endpoint of validationEndpoints) {
|
|
502
|
-
try {
|
|
503
|
-
const response = await axios.post(endpoint, { key: vendorKey }, {
|
|
504
|
-
headers: {
|
|
505
|
-
'X-API-Key': vendorKey,
|
|
506
|
-
'X-Auth-Method': 'vendor_key',
|
|
507
|
-
'X-Project-Scope': 'lanonasis-maas',
|
|
508
|
-
'Content-Type': 'application/json'
|
|
509
|
-
},
|
|
510
|
-
timeout: 10000,
|
|
511
|
-
proxy: false
|
|
512
|
-
});
|
|
513
|
-
// Check if response indicates validation success
|
|
514
|
-
if (response.data && (response.data.valid === true || response.data.success === true)) {
|
|
515
|
-
validated = true;
|
|
516
|
-
break;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
catch (error) {
|
|
520
|
-
lastError = error;
|
|
521
|
-
// Continue to next endpoint if this one fails
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
if (!validated && lastError) {
|
|
526
|
-
throw lastError;
|
|
527
|
-
}
|
|
528
|
-
else if (!validated) {
|
|
529
|
-
throw new Error('Vendor key validation failed: Unable to validate key with server');
|
|
530
|
-
}
|
|
517
|
+
// Use pingAuthHealth for validation (simpler and more reliable)
|
|
518
|
+
await this.pingAuthHealth(axios, authBase, {
|
|
519
|
+
'X-API-Key': vendorKey,
|
|
520
|
+
'X-Auth-Method': 'vendor_key',
|
|
521
|
+
'X-Project-Scope': 'lanonasis-maas'
|
|
522
|
+
}, { timeout: 10000, proxy: false });
|
|
531
523
|
}
|
|
532
524
|
catch (error) {
|
|
525
|
+
const normalizedError = this.normalizeServiceError(error);
|
|
533
526
|
// Provide specific error messages based on response
|
|
534
|
-
if (
|
|
535
|
-
const errorData =
|
|
527
|
+
if (normalizedError.response?.status === 401) {
|
|
528
|
+
const errorData = normalizedError.response.data;
|
|
536
529
|
if (errorData?.error?.includes('expired') || errorData?.message?.includes('expired')) {
|
|
537
530
|
throw new Error('Vendor key validation failed: Key has expired. Please generate a new key from your dashboard.');
|
|
538
531
|
}
|
|
@@ -546,29 +539,29 @@ export class CLIConfig {
|
|
|
546
539
|
throw new Error('Vendor key validation failed: Authentication failed. The key may be invalid, expired, or revoked.');
|
|
547
540
|
}
|
|
548
541
|
}
|
|
549
|
-
else if (
|
|
542
|
+
else if (normalizedError.response?.status === 403) {
|
|
550
543
|
throw new Error('Vendor key access denied. The key may not have sufficient permissions for this operation.');
|
|
551
544
|
}
|
|
552
|
-
else if (
|
|
545
|
+
else if (normalizedError.response?.status === 429) {
|
|
553
546
|
throw new Error('Too many validation attempts. Please wait a moment before trying again.');
|
|
554
547
|
}
|
|
555
|
-
else if (
|
|
548
|
+
else if ((normalizedError.response?.status ?? 0) >= 500) {
|
|
556
549
|
throw new Error('Server error during validation. Please try again in a few moments.');
|
|
557
550
|
}
|
|
558
|
-
else if (
|
|
551
|
+
else if (normalizedError.code === 'ECONNREFUSED') {
|
|
559
552
|
throw new Error('Cannot connect to authentication server. Please check your internet connection and try again.');
|
|
560
553
|
}
|
|
561
|
-
else if (
|
|
554
|
+
else if (normalizedError.code === 'ENOTFOUND') {
|
|
562
555
|
throw new Error('Authentication server not found. Please check your internet connection.');
|
|
563
556
|
}
|
|
564
|
-
else if (
|
|
557
|
+
else if (normalizedError.code === 'ETIMEDOUT') {
|
|
565
558
|
throw new Error('Validation request timed out. Please check your internet connection and try again.');
|
|
566
559
|
}
|
|
567
|
-
else if (
|
|
560
|
+
else if (normalizedError.code === 'ECONNRESET') {
|
|
568
561
|
throw new Error('Connection was reset during validation. Please try again.');
|
|
569
562
|
}
|
|
570
563
|
else {
|
|
571
|
-
throw new Error(`Vendor key validation failed: ${
|
|
564
|
+
throw new Error(`Vendor key validation failed: ${normalizedError.message || 'Unknown error'}`);
|
|
572
565
|
}
|
|
573
566
|
}
|
|
574
567
|
}
|
|
@@ -731,7 +724,6 @@ export class CLIConfig {
|
|
|
731
724
|
// If not locally valid, attempt server verification before failing
|
|
732
725
|
if (!locallyValid) {
|
|
733
726
|
try {
|
|
734
|
-
const axios = (await import('axios')).default;
|
|
735
727
|
const endpoints = [
|
|
736
728
|
'http://localhost:4000/v1/auth/verify-token',
|
|
737
729
|
'https://auth.lanonasis.com/v1/auth/verify-token'
|
|
@@ -768,7 +760,6 @@ export class CLIConfig {
|
|
|
768
760
|
}
|
|
769
761
|
// Verify with server (security check) for tokens that haven't been validated recently
|
|
770
762
|
try {
|
|
771
|
-
const axios = (await import('axios')).default;
|
|
772
763
|
// Try auth-gateway first (port 4000), then fall back to Netlify function
|
|
773
764
|
const endpoints = [
|
|
774
765
|
'http://localhost:4000/v1/auth/verify-token',
|
|
@@ -880,7 +871,6 @@ export class CLIConfig {
|
|
|
880
871
|
return false;
|
|
881
872
|
}
|
|
882
873
|
// Import axios dynamically to avoid circular dependency
|
|
883
|
-
const axios = (await import('axios')).default;
|
|
884
874
|
// Ensure service discovery is done
|
|
885
875
|
await this.discoverServices();
|
|
886
876
|
const authBase = this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com';
|
|
@@ -925,7 +915,6 @@ export class CLIConfig {
|
|
|
925
915
|
// Refresh if token expires within 5 minutes
|
|
926
916
|
if (exp > 0 && (exp - now) < 300) {
|
|
927
917
|
// Import axios dynamically
|
|
928
|
-
const axios = (await import('axios')).default;
|
|
929
918
|
await this.discoverServices();
|
|
930
919
|
const authBase = this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com';
|
|
931
920
|
// Attempt token refresh
|
package/dist/utils/mcp-client.js
CHANGED
|
@@ -456,9 +456,11 @@ export class MCPClient {
|
|
|
456
456
|
// Use the proper SSE endpoint from config
|
|
457
457
|
const sseUrl = this.config.getMCPSSEUrl() ?? `${serverUrl}/events`;
|
|
458
458
|
const token = this.config.get('token');
|
|
459
|
-
|
|
459
|
+
const vendorKey = this.config.get('vendorKey');
|
|
460
|
+
const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
|
|
461
|
+
if (authKey) {
|
|
460
462
|
// EventSource doesn't support headers directly, append token to URL
|
|
461
|
-
this.sseConnection = new EventSource(`${sseUrl}?token=${encodeURIComponent(
|
|
463
|
+
this.sseConnection = new EventSource(`${sseUrl}?token=${encodeURIComponent(authKey)}`);
|
|
462
464
|
this.sseConnection.onmessage = (event) => {
|
|
463
465
|
try {
|
|
464
466
|
const data = JSON.parse(event.data);
|
|
@@ -478,7 +480,9 @@ export class MCPClient {
|
|
|
478
480
|
*/
|
|
479
481
|
async initializeWebSocket(wsUrl) {
|
|
480
482
|
const token = this.config.get('token');
|
|
481
|
-
|
|
483
|
+
const vendorKey = this.config.get('vendorKey');
|
|
484
|
+
const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
|
|
485
|
+
if (!authKey) {
|
|
482
486
|
throw new Error('API key required for WebSocket mode. Set LANONASIS_API_KEY or login first.');
|
|
483
487
|
}
|
|
484
488
|
return new Promise((resolve, reject) => {
|
|
@@ -491,8 +495,8 @@ export class MCPClient {
|
|
|
491
495
|
// Create new WebSocket connection with authentication
|
|
492
496
|
this.wsConnection = new WebSocket(wsUrl, [], {
|
|
493
497
|
headers: {
|
|
494
|
-
'Authorization': `Bearer ${
|
|
495
|
-
'X-API-Key':
|
|
498
|
+
'Authorization': `Bearer ${authKey}`,
|
|
499
|
+
'X-API-Key': authKey
|
|
496
500
|
}
|
|
497
501
|
});
|
|
498
502
|
this.wsConnection.on('open', () => {
|
|
@@ -624,15 +628,17 @@ export class MCPClient {
|
|
|
624
628
|
async checkRemoteHealth() {
|
|
625
629
|
const apiUrl = this.config.getMCPRestUrl() ?? 'https://mcp.lanonasis.com/api/v1';
|
|
626
630
|
const token = this.config.get('token');
|
|
627
|
-
|
|
631
|
+
const vendorKey = this.config.get('vendorKey');
|
|
632
|
+
const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
|
|
633
|
+
if (!authKey) {
|
|
628
634
|
throw new Error('No authentication token available');
|
|
629
635
|
}
|
|
630
636
|
try {
|
|
631
637
|
const axios = (await import('axios')).default;
|
|
632
638
|
await axios.get(`${apiUrl}/health`, {
|
|
633
639
|
headers: {
|
|
634
|
-
'Authorization': `Bearer ${
|
|
635
|
-
'
|
|
640
|
+
'Authorization': `Bearer ${authKey}`,
|
|
641
|
+
'X-API-Key': authKey
|
|
636
642
|
},
|
|
637
643
|
timeout: 5000
|
|
638
644
|
});
|
|
@@ -749,7 +755,9 @@ export class MCPClient {
|
|
|
749
755
|
async callRemoteTool(toolName, args) {
|
|
750
756
|
const apiUrl = this.config.getMCPRestUrl() ?? 'https://mcp.lanonasis.com/api/v1';
|
|
751
757
|
const token = this.config.get('token');
|
|
752
|
-
|
|
758
|
+
const vendorKey = this.config.get('vendorKey');
|
|
759
|
+
const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
|
|
760
|
+
if (!authKey) {
|
|
753
761
|
throw new Error('Authentication required. Run "lanonasis auth login" first.');
|
|
754
762
|
}
|
|
755
763
|
// Map MCP tool names to REST API endpoints
|
|
@@ -805,8 +813,8 @@ export class MCPClient {
|
|
|
805
813
|
method: mapping.method,
|
|
806
814
|
url: `${apiUrl}${endpoint}`,
|
|
807
815
|
headers: {
|
|
808
|
-
'Authorization': `Bearer ${
|
|
809
|
-
'
|
|
816
|
+
'Authorization': `Bearer ${authKey}`,
|
|
817
|
+
'X-API-Key': authKey,
|
|
810
818
|
'Content-Type': 'application/json'
|
|
811
819
|
},
|
|
812
820
|
data: mapping.transform ? mapping.transform(args) : undefined,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lanonasis/cli",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@jest/globals": "^29.7.0",
|
|
47
47
|
"@types/cli-progress": "^3.11.6",
|
|
48
48
|
"@types/inquirer": "^9.0.7",
|
|
49
|
-
"@types/node": "^22.
|
|
49
|
+
"@types/node": "^22.19.3",
|
|
50
50
|
"@types/ws": "^8.5.12",
|
|
51
51
|
"jest": "^29.7.0",
|
|
52
52
|
"rimraf": "^5.0.7",
|