@lanonasis/cli 2.0.6 → 2.0.9

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 CHANGED
@@ -1,11 +1,11 @@
1
- # @lanonasis/cli v2.0.1 - Enhanced Interactive CLI Experience
1
+ # @lanonasis/cli v2.0.9 - Enhanced MCP & Interactive CLI Experience
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/@lanonasis/cli)](https://www.npmjs.com/package/@lanonasis/cli)
4
4
  [![Downloads](https://img.shields.io/npm/dt/@lanonasis/cli)](https://www.npmjs.com/package/@lanonasis/cli)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![Golden Contract](https://img.shields.io/badge/Onasis--Core-v0.1%20Compliant-gold)](https://api.lanonasis.com/.well-known/onasis.json)
7
7
 
8
- 🚀 **NEW IN v2.0**: Revolutionary interactive CLI experience with guided workflows, smart suggestions, achievement system, and power user mode. Professional command-line interface for LanOnasis Memory as a Service (MaaS) platform with **Golden Contract compliance**.
8
+ 🚀 **NEW IN v2.0.9**: Advanced Model Context Protocol (MCP) support with multi-server connections, enhanced error handling, and enterprise-grade transport protocols. Revolutionary interactive CLI experience with guided workflows, smart suggestions, achievement system, and power user mode. Professional command-line interface for LanOnasis Memory as a Service (MaaS) platform with **Golden Contract compliance**.
9
9
 
10
10
  ## 🚀 Quick Start
11
11
 
@@ -152,13 +152,36 @@ onasis api-keys revoke <id> # Revoke API key
152
152
  onasis api-keys rotate <id> # Rotate API key
153
153
  ```
154
154
 
155
- ### MCP Integration
155
+ ### MCP Integration (Enhanced in v2.0.9)
156
156
  ```bash
157
- onasis mcp status # MCP server status
157
+ # Connection Management
158
+ onasis mcp status # MCP server status with health info
158
159
  onasis mcp connect --remote # Connect to remote MCP server
160
+ onasis mcp connect --local # Connect to local MCP server
159
161
  onasis mcp disconnect # Disconnect from MCP
162
+ onasis mcp list-servers # List all connected servers
163
+
164
+ # Tool & Resource Management
160
165
  onasis mcp tools # List available MCP tools
161
166
  onasis mcp resources # List MCP resources
167
+ onasis mcp call <tool> --args # Execute MCP tool directly
168
+
169
+ # Advanced Features (v2.0.9)
170
+ onasis mcp health # Detailed health check with latency
171
+ onasis mcp server start # Start local MCP server
172
+ onasis mcp server stop # Stop local MCP server
173
+ ```
174
+
175
+ ### 🆕 MCP Server Mode (v2.0.9)
176
+ Run the CLI as a standalone MCP server:
177
+ ```bash
178
+ # Start MCP server for IDE integrations
179
+ lanonasis-mcp-server --verbose
180
+
181
+ # Use with environment variables
182
+ LANONASIS_API_URL=https://api.lanonasis.com \
183
+ LANONASIS_TOKEN=your-token \
184
+ lanonasis-mcp-server
162
185
  ```
163
186
 
164
187
  ### Configuration Management
@@ -21,11 +21,24 @@ export async function loginCommand(options) {
21
21
  console.log(chalk.blue.bold('🔐 Onasis-Core Golden Contract Authentication'));
22
22
  console.log(colors.info('━'.repeat(50)));
23
23
  console.log();
24
+ // Debug: Check options
25
+ if (process.env.CLI_VERBOSE === 'true') {
26
+ console.log('Debug - Login options:', {
27
+ hasEmail: !!options.email,
28
+ hasPassword: !!options.password,
29
+ hasVendorKey: !!options.vendorKey
30
+ });
31
+ }
24
32
  // Enhanced authentication flow - check for vendor key first
25
33
  if (options.vendorKey) {
26
34
  await handleVendorKeyAuth(options.vendorKey, config);
27
35
  return;
28
36
  }
37
+ // Check for email/password for direct credentials flow
38
+ if (options.email && options.password) {
39
+ await handleCredentialsFlow(options, config);
40
+ return;
41
+ }
29
42
  // Show authentication options
30
43
  const authChoice = await inquirer.prompt([
31
44
  {
@@ -45,7 +45,7 @@ program
45
45
  .alias(isOnasisInvocation ? 'lanonasis' : 'memory')
46
46
  .alias(isOnasisInvocation ? 'memory' : 'maas')
47
47
  .description(colors.info(`🧠 ${isOnasisInvocation ? 'Onasis-Core Golden Contract CLI' : 'LanOnasis Enterprise CLI'} - Memory as a Service, API Management & Infrastructure Orchestration`))
48
- .version('2.0.1', '-v, --version', 'display version number')
48
+ .version('2.0.6', '-v, --version', 'display version number')
49
49
  .option('-V, --verbose', 'enable verbose logging')
50
50
  .option('--api-url <url>', 'override API URL')
51
51
  .option('--output <format>', 'output format (json, table, yaml)', 'table')
@@ -133,6 +133,8 @@ const healthCheck = async () => {
133
133
  console.log(colors.primary('🏥 LanOnasis System Health Check'));
134
134
  console.log(colors.info('═'.repeat(40)));
135
135
  console.log();
136
+ // Initialize config
137
+ await cliConfig.init();
136
138
  // Authentication status
137
139
  process.stdout.write('Authentication status: ');
138
140
  const isAuth = await cliConfig.isAuthenticated();
@@ -195,10 +197,11 @@ const healthCheck = async () => {
195
197
  // Check if user is authenticated for protected commands
196
198
  const requireAuth = (command) => {
197
199
  command.hook('preAction', async () => {
200
+ await cliConfig.init();
198
201
  const isAuthenticated = await cliConfig.isAuthenticated();
199
202
  if (!isAuthenticated) {
200
203
  console.error(chalk.red('✖ Authentication required'));
201
- console.log(chalk.yellow('Please run:'), chalk.white('memory login'));
204
+ console.log(chalk.yellow('Please run:'), chalk.white('lanonasis auth login'));
202
205
  process.exit(1);
203
206
  }
204
207
  });
@@ -646,7 +649,7 @@ async function main() {
646
649
  console.log(script);
647
650
  return;
648
651
  }
649
- catch (error) {
652
+ catch {
650
653
  console.error(colors.error('Failed to read completion script'));
651
654
  process.exit(1);
652
655
  }
package/dist/index.js CHANGED
@@ -104,6 +104,8 @@ const healthCheck = async () => {
104
104
  console.log(colors.primary('🏥 LanOnasis System Health Check'));
105
105
  console.log(colors.info('═'.repeat(40)));
106
106
  console.log();
107
+ // Initialize config
108
+ await cliConfig.init();
107
109
  // Authentication status
108
110
  process.stdout.write('Authentication status: ');
109
111
  const isAuth = await cliConfig.isAuthenticated();
@@ -166,10 +168,11 @@ const healthCheck = async () => {
166
168
  // Check if user is authenticated for protected commands
167
169
  const requireAuth = (command) => {
168
170
  command.hook('preAction', async () => {
171
+ await cliConfig.init();
169
172
  const isAuthenticated = await cliConfig.isAuthenticated();
170
173
  if (!isAuthenticated) {
171
174
  console.error(chalk.red('✖ Authentication required'));
172
- console.log(chalk.yellow('Please run:'), chalk.white('memory login'));
175
+ console.log(chalk.yellow('Please run:'), chalk.white('lanonasis auth login'));
173
176
  process.exit(1);
174
177
  }
175
178
  });
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Enhanced MCP Client with advanced features
3
+ * Provides multi-server support, connection pooling, and better error handling
4
+ */
5
+ import { EventEmitter } from 'events';
6
+ export interface MCPServerConfig {
7
+ name: string;
8
+ url?: string;
9
+ command?: string;
10
+ args?: string[];
11
+ type: 'stdio' | 'http' | 'websocket';
12
+ timeout?: number;
13
+ maxRetries?: number;
14
+ priority?: number;
15
+ }
16
+ export interface ToolChain {
17
+ tools: Array<{
18
+ name: string;
19
+ args: Record<string, any>;
20
+ waitForCompletion?: boolean;
21
+ }>;
22
+ mode: 'sequential' | 'parallel';
23
+ }
24
+ export interface ConnectionStatus {
25
+ server: string;
26
+ status: 'connected' | 'disconnected' | 'error' | 'connecting';
27
+ lastPing?: Date;
28
+ latency?: number;
29
+ error?: string;
30
+ }
31
+ export declare class EnhancedMCPClient extends EventEmitter {
32
+ private clients;
33
+ private transports;
34
+ private connectionStatus;
35
+ private retryAttempts;
36
+ private healthCheckIntervals;
37
+ private spinner;
38
+ constructor();
39
+ /**
40
+ * Setup internal event handlers
41
+ */
42
+ private setupEventHandlers;
43
+ /**
44
+ * Connect to multiple MCP servers
45
+ */
46
+ connectMultiple(servers: MCPServerConfig[]): Promise<Map<string, boolean>>;
47
+ /**
48
+ * Connect to a single MCP server with retry logic
49
+ */
50
+ connectSingle(config: MCPServerConfig): Promise<boolean>;
51
+ /**
52
+ * Create client with timeout
53
+ */
54
+ private createClientWithTimeout;
55
+ /**
56
+ * Create MCP client based on config
57
+ */
58
+ private createClient;
59
+ /**
60
+ * Create WebSocket transport
61
+ */
62
+ private createWebSocketTransport;
63
+ /**
64
+ * Execute a chain of tools
65
+ */
66
+ executeToolChain(chain: ToolChain): Promise<any[]>;
67
+ /**
68
+ * Execute a single tool with automatic server selection
69
+ */
70
+ executeTool(toolName: string, args: Record<string, any>): Promise<any>;
71
+ /**
72
+ * Select the best server for a tool based on availability and latency
73
+ */
74
+ private selectBestServer;
75
+ /**
76
+ * Select a failover server
77
+ */
78
+ private selectFailoverServer;
79
+ /**
80
+ * Wait for tool completion (for async operations)
81
+ */
82
+ private waitForToolCompletion;
83
+ /**
84
+ * Start health monitoring for a server
85
+ */
86
+ private startHealthMonitoring;
87
+ /**
88
+ * Perform health check for a server
89
+ */
90
+ private performHealthCheck;
91
+ /**
92
+ * Update connection status
93
+ */
94
+ private updateConnectionStatus;
95
+ /**
96
+ * Get server configuration (placeholder - should be stored during connect)
97
+ */
98
+ private getServerConfig;
99
+ /**
100
+ * Get all connection statuses
101
+ */
102
+ getConnectionStatuses(): ConnectionStatus[];
103
+ /**
104
+ * Disconnect from all servers
105
+ */
106
+ disconnectAll(): Promise<void>;
107
+ /**
108
+ * Disconnect from a specific server
109
+ */
110
+ disconnect(serverName: string): Promise<void>;
111
+ /**
112
+ * Utility delay function
113
+ */
114
+ private delay;
115
+ }
116
+ export declare const enhancedMCPClient: EnhancedMCPClient;
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Enhanced MCP Client with advanced features
3
+ * Provides multi-server support, connection pooling, and better error handling
4
+ */
5
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
6
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
7
+ import chalk from 'chalk';
8
+ import { EventEmitter } from 'events';
9
+ export class EnhancedMCPClient extends EventEmitter {
10
+ clients = new Map();
11
+ transports = new Map();
12
+ connectionStatus = new Map();
13
+ retryAttempts = new Map();
14
+ healthCheckIntervals = new Map();
15
+ spinner = null;
16
+ constructor() {
17
+ super();
18
+ this.setupEventHandlers();
19
+ }
20
+ /**
21
+ * Setup internal event handlers
22
+ */
23
+ setupEventHandlers() {
24
+ this.on('connection:established', (server) => {
25
+ console.log(chalk.green(`✅ Connected to ${server}`));
26
+ });
27
+ this.on('connection:lost', (server) => {
28
+ console.log(chalk.yellow(`⚠️ Lost connection to ${server}`));
29
+ });
30
+ this.on('connection:error', (server, error) => {
31
+ console.log(chalk.red(`❌ Connection error for ${server}: ${error.message}`));
32
+ });
33
+ }
34
+ /**
35
+ * Connect to multiple MCP servers
36
+ */
37
+ async connectMultiple(servers) {
38
+ const results = new Map();
39
+ // Sort servers by priority
40
+ const sortedServers = servers.sort((a, b) => (a.priority || 999) - (b.priority || 999));
41
+ // Connect in parallel with controlled concurrency
42
+ const connectionPromises = sortedServers.map(server => this.connectSingle(server).then(success => {
43
+ results.set(server.name, success);
44
+ return { server: server.name, success };
45
+ }));
46
+ await Promise.allSettled(connectionPromises);
47
+ // Start health monitoring for connected servers
48
+ for (const [name, success] of results) {
49
+ if (success) {
50
+ this.startHealthMonitoring(name);
51
+ }
52
+ }
53
+ return results;
54
+ }
55
+ /**
56
+ * Connect to a single MCP server with retry logic
57
+ */
58
+ async connectSingle(config) {
59
+ const maxRetries = config.maxRetries || 3;
60
+ const timeout = config.timeout || 30000;
61
+ let attempts = 0;
62
+ while (attempts < maxRetries) {
63
+ try {
64
+ this.updateConnectionStatus(config.name, 'connecting');
65
+ const client = await this.createClientWithTimeout(config, timeout);
66
+ this.clients.set(config.name, client);
67
+ this.updateConnectionStatus(config.name, 'connected');
68
+ this.emit('connection:established', config.name);
69
+ return true;
70
+ }
71
+ catch (error) {
72
+ attempts++;
73
+ this.retryAttempts.set(config.name, attempts);
74
+ if (attempts >= maxRetries) {
75
+ this.updateConnectionStatus(config.name, 'error', error);
76
+ this.emit('connection:error', config.name, error);
77
+ return false;
78
+ }
79
+ // Exponential backoff
80
+ const delay = Math.min(1000 * Math.pow(2, attempts), 10000);
81
+ console.log(chalk.yellow(`⏳ Retrying connection to ${config.name} in ${delay}ms...`));
82
+ await this.delay(delay);
83
+ }
84
+ }
85
+ return false;
86
+ }
87
+ /**
88
+ * Create client with timeout
89
+ */
90
+ async createClientWithTimeout(config, timeout) {
91
+ return Promise.race([
92
+ this.createClient(config),
93
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Connection timeout after ${timeout}ms`)), timeout))
94
+ ]);
95
+ }
96
+ /**
97
+ * Create MCP client based on config
98
+ */
99
+ async createClient(config) {
100
+ let transport;
101
+ switch (config.type) {
102
+ case 'stdio':
103
+ if (!config.command) {
104
+ throw new Error('Command required for stdio transport');
105
+ }
106
+ transport = new StdioClientTransport({
107
+ command: config.command,
108
+ args: config.args || []
109
+ });
110
+ break;
111
+ case 'http':
112
+ if (!config.url) {
113
+ throw new Error('URL required for http transport');
114
+ }
115
+ // HTTP transport is not directly supported by MCP SDK
116
+ // Use stdio or websocket instead
117
+ throw new Error('HTTP transport not directly supported. Use websocket or stdio transport.');
118
+ break;
119
+ case 'websocket':
120
+ if (!config.url) {
121
+ throw new Error('URL required for websocket transport');
122
+ }
123
+ transport = await this.createWebSocketTransport(config.url);
124
+ break;
125
+ default:
126
+ throw new Error(`Unsupported transport type: ${config.type}`);
127
+ }
128
+ this.transports.set(config.name, transport);
129
+ const client = new Client({
130
+ name: `lanonasis-cli-${config.name}`,
131
+ version: '2.0.8'
132
+ }, {
133
+ capabilities: {
134
+ tools: {},
135
+ resources: {},
136
+ prompts: {}
137
+ }
138
+ });
139
+ await client.connect(transport);
140
+ return client;
141
+ }
142
+ /**
143
+ * Create WebSocket transport
144
+ */
145
+ async createWebSocketTransport(url) {
146
+ // Custom WebSocket transport implementation
147
+ const WebSocket = (await import('ws')).default;
148
+ return new Promise((resolve, reject) => {
149
+ const ws = new WebSocket(url);
150
+ ws.on('open', () => {
151
+ resolve({
152
+ send: (data) => ws.send(JSON.stringify(data)),
153
+ on: (event, handler) => ws.on(event, handler),
154
+ close: () => ws.close()
155
+ });
156
+ });
157
+ ws.on('error', reject);
158
+ });
159
+ }
160
+ /**
161
+ * Execute a chain of tools
162
+ */
163
+ async executeToolChain(chain) {
164
+ const results = [];
165
+ if (chain.mode === 'sequential') {
166
+ for (const tool of chain.tools) {
167
+ const result = await this.executeTool(tool.name, tool.args);
168
+ results.push(result);
169
+ if (tool.waitForCompletion) {
170
+ await this.waitForToolCompletion(tool.name, result);
171
+ }
172
+ }
173
+ }
174
+ else {
175
+ // Parallel execution
176
+ const promises = chain.tools.map(tool => this.executeTool(tool.name, tool.args));
177
+ const parallelResults = await Promise.allSettled(promises);
178
+ parallelResults.forEach((result, index) => {
179
+ if (result.status === 'fulfilled') {
180
+ results.push(result.value);
181
+ }
182
+ else {
183
+ results.push({ error: result.reason, tool: chain.tools[index].name });
184
+ }
185
+ });
186
+ }
187
+ return results;
188
+ }
189
+ /**
190
+ * Execute a single tool with automatic server selection
191
+ */
192
+ async executeTool(toolName, args) {
193
+ // Find the best available server for this tool
194
+ const server = await this.selectBestServer(toolName);
195
+ if (!server) {
196
+ throw new Error(`No available server for tool: ${toolName}`);
197
+ }
198
+ const client = this.clients.get(server);
199
+ if (!client) {
200
+ throw new Error(`Client not found for server: ${server}`);
201
+ }
202
+ try {
203
+ const result = await client.callTool({
204
+ name: toolName,
205
+ arguments: args
206
+ });
207
+ return result;
208
+ }
209
+ catch (error) {
210
+ // Try failover to another server
211
+ const failoverServer = await this.selectFailoverServer(server, toolName);
212
+ if (failoverServer) {
213
+ console.log(chalk.yellow(`⚠️ Failing over to ${failoverServer}...`));
214
+ const failoverClient = this.clients.get(failoverServer);
215
+ if (failoverClient) {
216
+ return failoverClient.callTool({
217
+ name: toolName,
218
+ arguments: args
219
+ });
220
+ }
221
+ }
222
+ throw error;
223
+ }
224
+ }
225
+ /**
226
+ * Select the best server for a tool based on availability and latency
227
+ */
228
+ async selectBestServer(toolName) {
229
+ const availableServers = Array.from(this.clients.keys()).filter(name => {
230
+ const status = this.connectionStatus.get(name);
231
+ return status?.status === 'connected';
232
+ });
233
+ if (availableServers.length === 0) {
234
+ return null;
235
+ }
236
+ // For now, return the first available server
237
+ // TODO: Implement smarter selection based on tool availability and latency
238
+ return availableServers[0];
239
+ }
240
+ /**
241
+ * Select a failover server
242
+ */
243
+ async selectFailoverServer(excludeServer, toolName) {
244
+ const availableServers = Array.from(this.clients.keys()).filter(name => {
245
+ const status = this.connectionStatus.get(name);
246
+ return name !== excludeServer && status?.status === 'connected';
247
+ });
248
+ return availableServers.length > 0 ? availableServers[0] : null;
249
+ }
250
+ /**
251
+ * Wait for tool completion (for async operations)
252
+ */
253
+ async waitForToolCompletion(toolName, initialResult) {
254
+ // Implementation depends on the specific tool
255
+ // This is a placeholder for tools that return operation IDs
256
+ if (initialResult.operationId) {
257
+ let completed = false;
258
+ let attempts = 0;
259
+ const maxAttempts = 60; // 1 minute with 1 second intervals
260
+ while (!completed && attempts < maxAttempts) {
261
+ await this.delay(1000);
262
+ // Check operation status (implementation specific)
263
+ // completed = await this.checkOperationStatus(initialResult.operationId);
264
+ attempts++;
265
+ }
266
+ }
267
+ }
268
+ /**
269
+ * Start health monitoring for a server
270
+ */
271
+ startHealthMonitoring(serverName) {
272
+ // Clear existing interval if any
273
+ const existingInterval = this.healthCheckIntervals.get(serverName);
274
+ if (existingInterval) {
275
+ clearInterval(existingInterval);
276
+ }
277
+ const interval = setInterval(async () => {
278
+ await this.performHealthCheck(serverName);
279
+ }, 30000); // Check every 30 seconds
280
+ this.healthCheckIntervals.set(serverName, interval);
281
+ }
282
+ /**
283
+ * Perform health check for a server
284
+ */
285
+ async performHealthCheck(serverName) {
286
+ const client = this.clients.get(serverName);
287
+ if (!client)
288
+ return;
289
+ const startTime = Date.now();
290
+ try {
291
+ // Use a simple tool call as a health check
292
+ await client.listTools();
293
+ const latency = Date.now() - startTime;
294
+ this.updateConnectionStatus(serverName, 'connected', undefined, latency);
295
+ }
296
+ catch (error) {
297
+ this.updateConnectionStatus(serverName, 'error', error);
298
+ // Attempt reconnection
299
+ const config = this.getServerConfig(serverName);
300
+ if (config) {
301
+ console.log(chalk.yellow(`⚠️ Attempting to reconnect to ${serverName}...`));
302
+ await this.connectSingle(config);
303
+ }
304
+ }
305
+ }
306
+ /**
307
+ * Update connection status
308
+ */
309
+ updateConnectionStatus(server, status, error, latency) {
310
+ this.connectionStatus.set(server, {
311
+ server,
312
+ status,
313
+ lastPing: new Date(),
314
+ latency,
315
+ error: error?.message
316
+ });
317
+ }
318
+ /**
319
+ * Get server configuration (placeholder - should be stored during connect)
320
+ */
321
+ getServerConfig(serverName) {
322
+ // TODO: Store and retrieve server configs
323
+ return null;
324
+ }
325
+ /**
326
+ * Get all connection statuses
327
+ */
328
+ getConnectionStatuses() {
329
+ return Array.from(this.connectionStatus.values());
330
+ }
331
+ /**
332
+ * Disconnect from all servers
333
+ */
334
+ async disconnectAll() {
335
+ // Stop all health monitoring
336
+ for (const interval of this.healthCheckIntervals.values()) {
337
+ clearInterval(interval);
338
+ }
339
+ this.healthCheckIntervals.clear();
340
+ // Disconnect all clients
341
+ for (const [name, client] of this.clients) {
342
+ try {
343
+ await client.close();
344
+ console.log(chalk.gray(`Disconnected from ${name}`));
345
+ }
346
+ catch (error) {
347
+ console.log(chalk.yellow(`Warning: Error disconnecting from ${name}`));
348
+ }
349
+ }
350
+ this.clients.clear();
351
+ this.transports.clear();
352
+ this.connectionStatus.clear();
353
+ }
354
+ /**
355
+ * Disconnect from a specific server
356
+ */
357
+ async disconnect(serverName) {
358
+ const interval = this.healthCheckIntervals.get(serverName);
359
+ if (interval) {
360
+ clearInterval(interval);
361
+ this.healthCheckIntervals.delete(serverName);
362
+ }
363
+ const client = this.clients.get(serverName);
364
+ if (client) {
365
+ await client.close();
366
+ this.clients.delete(serverName);
367
+ }
368
+ this.transports.delete(serverName);
369
+ this.connectionStatus.delete(serverName);
370
+ }
371
+ /**
372
+ * Utility delay function
373
+ */
374
+ delay(ms) {
375
+ return new Promise(resolve => setTimeout(resolve, ms));
376
+ }
377
+ }
378
+ // Export singleton instance
379
+ export const enhancedMCPClient = new EnhancedMCPClient();