@lanonasis/cli 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,249 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
+ import chalk from 'chalk';
4
+ import { CLIConfig } from './config.js';
5
+ import * as path from 'path';
6
+ import { EventSource } from 'eventsource';
7
+ import { fileURLToPath } from 'url';
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ export class MCPClient {
11
+ client = null;
12
+ config;
13
+ isConnected = false;
14
+ sseConnection = null;
15
+ constructor() {
16
+ this.config = new CLIConfig();
17
+ }
18
+ /**
19
+ * Connect to MCP server (local or remote)
20
+ */
21
+ async connect(options = {}) {
22
+ try {
23
+ const useRemote = options.useRemote ?? this.config.get('mcpUseRemote') ?? false;
24
+ if (useRemote) {
25
+ // For remote MCP, we'll use the REST API with MCP-style interface
26
+ const serverUrl = options.serverUrl ?? this.config.get('mcpServerUrl') ?? 'https://api.lanonasis.com';
27
+ console.log(chalk.cyan(`Connecting to remote MCP server at ${serverUrl}...`));
28
+ // Initialize SSE connection for real-time updates
29
+ await this.initializeSSE(serverUrl);
30
+ this.isConnected = true;
31
+ return true;
32
+ }
33
+ else {
34
+ // Local MCP server connection
35
+ const serverPath = options.serverPath ?? this.config.get('mcpServerPath') ?? path.join(__dirname, '../../../../onasis-gateway/mcp-server/server.js');
36
+ console.log(chalk.cyan(`Connecting to local MCP server at ${serverPath}...`));
37
+ const transport = new StdioClientTransport({
38
+ command: 'node',
39
+ args: [serverPath]
40
+ });
41
+ this.client = new Client({
42
+ name: '@lanonasis/cli',
43
+ version: '1.0.0'
44
+ }, {
45
+ capabilities: {}
46
+ });
47
+ await this.client.connect(transport);
48
+ this.isConnected = true;
49
+ console.log(chalk.green('✓ Connected to MCP server'));
50
+ return true;
51
+ }
52
+ }
53
+ catch (error) {
54
+ console.error(chalk.red('Failed to connect to MCP server:'), error);
55
+ this.isConnected = false;
56
+ return false;
57
+ }
58
+ }
59
+ /**
60
+ * Initialize SSE connection for real-time updates
61
+ */
62
+ async initializeSSE(serverUrl) {
63
+ const sseUrl = `${serverUrl}/sse`;
64
+ const token = this.config.get('token');
65
+ if (token) {
66
+ // EventSource doesn't support headers directly, append token to URL
67
+ this.sseConnection = new EventSource(`${sseUrl}?token=${encodeURIComponent(token)}`);
68
+ this.sseConnection.onmessage = (event) => {
69
+ try {
70
+ const data = JSON.parse(event.data);
71
+ console.log(chalk.blue('📡 Real-time update:'), data.type);
72
+ }
73
+ catch (error) {
74
+ // Ignore parse errors
75
+ }
76
+ };
77
+ this.sseConnection.onerror = (error) => {
78
+ console.error(chalk.yellow('⚠️ SSE connection error (will retry)'));
79
+ };
80
+ }
81
+ }
82
+ /**
83
+ * Disconnect from MCP server
84
+ */
85
+ async disconnect() {
86
+ if (this.client) {
87
+ await this.client.close();
88
+ this.client = null;
89
+ }
90
+ if (this.sseConnection) {
91
+ this.sseConnection.close();
92
+ this.sseConnection = null;
93
+ }
94
+ this.isConnected = false;
95
+ }
96
+ /**
97
+ * Call an MCP tool
98
+ */
99
+ async callTool(toolName, args) {
100
+ if (!this.isConnected) {
101
+ throw new Error('Not connected to MCP server. Run "lanonasis mcp connect" first.');
102
+ }
103
+ const useRemote = this.config.get('mcpUseRemote') ?? false;
104
+ if (useRemote) {
105
+ // Remote MCP calls are translated to REST API calls
106
+ return await this.callRemoteTool(toolName, args);
107
+ }
108
+ else {
109
+ // Local MCP server call
110
+ if (!this.client) {
111
+ throw new Error('MCP client not initialized');
112
+ }
113
+ try {
114
+ const result = await this.client.callTool({
115
+ name: toolName,
116
+ arguments: args
117
+ });
118
+ return result;
119
+ }
120
+ catch (error) {
121
+ throw new Error(`MCP tool call failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
122
+ }
123
+ }
124
+ }
125
+ /**
126
+ * Call remote tool via REST API with MCP interface
127
+ */
128
+ async callRemoteTool(toolName, args) {
129
+ const apiUrl = this.config.get('apiUrl') ?? 'https://api.lanonasis.com';
130
+ const token = this.config.get('token');
131
+ if (!token) {
132
+ throw new Error('Authentication required. Run "lanonasis auth login" first.');
133
+ }
134
+ // Map MCP tool names to REST API endpoints
135
+ const toolMappings = {
136
+ 'memory_create_memory': {
137
+ method: 'POST',
138
+ endpoint: '/api/v1/memory',
139
+ transform: (args) => args
140
+ },
141
+ 'memory_search_memories': {
142
+ method: 'POST',
143
+ endpoint: '/api/v1/memory/search',
144
+ transform: (args) => args
145
+ },
146
+ 'memory_get_memory': {
147
+ method: 'GET',
148
+ endpoint: `/api/v1/memory/${args.memory_id}`,
149
+ transform: () => undefined
150
+ },
151
+ 'memory_update_memory': {
152
+ method: 'PUT',
153
+ endpoint: `/api/v1/memory/${args.memory_id}`,
154
+ transform: (args) => {
155
+ const { memory_id, ...data } = args;
156
+ return data;
157
+ }
158
+ },
159
+ 'memory_delete_memory': {
160
+ method: 'DELETE',
161
+ endpoint: `/api/v1/memory/${args.memory_id}`,
162
+ transform: () => undefined
163
+ },
164
+ 'memory_list_memories': {
165
+ method: 'GET',
166
+ endpoint: '/api/v1/memory',
167
+ transform: (args) => args
168
+ }
169
+ };
170
+ const mapping = toolMappings[toolName];
171
+ if (!mapping) {
172
+ throw new Error(`Unknown tool: ${toolName}`);
173
+ }
174
+ try {
175
+ const axios = (await import('axios')).default;
176
+ const response = await axios({
177
+ method: mapping.method,
178
+ url: `${apiUrl}${mapping.endpoint}`,
179
+ headers: {
180
+ 'Authorization': `Bearer ${token}`,
181
+ 'Content-Type': 'application/json'
182
+ },
183
+ data: mapping.transform ? mapping.transform(args) : undefined,
184
+ params: mapping.method === 'GET' ? args : undefined
185
+ });
186
+ return response.data;
187
+ }
188
+ catch (error) {
189
+ throw new Error(`Remote tool call failed: ${error.response?.data?.error || error.message}`);
190
+ }
191
+ }
192
+ /**
193
+ * List available tools
194
+ */
195
+ async listTools() {
196
+ if (!this.isConnected) {
197
+ throw new Error('Not connected to MCP server');
198
+ }
199
+ const useRemote = this.config.get('mcpUseRemote') ?? false;
200
+ if (useRemote) {
201
+ // Return hardcoded list for remote mode
202
+ return [
203
+ { name: 'memory_create_memory', description: 'Create a new memory entry' },
204
+ { name: 'memory_search_memories', description: 'Search memories using semantic search' },
205
+ { name: 'memory_get_memory', description: 'Get a specific memory by ID' },
206
+ { name: 'memory_update_memory', description: 'Update an existing memory' },
207
+ { name: 'memory_delete_memory', description: 'Delete a memory' },
208
+ { name: 'memory_list_memories', description: 'List all memories with pagination' }
209
+ ];
210
+ }
211
+ else {
212
+ if (!this.client) {
213
+ throw new Error('MCP client not initialized');
214
+ }
215
+ const tools = await this.client.listTools();
216
+ return tools.tools.map(tool => ({
217
+ name: tool.name,
218
+ description: tool.description || 'No description available'
219
+ }));
220
+ }
221
+ }
222
+ /**
223
+ * Check if connected to MCP server
224
+ */
225
+ isConnectedToServer() {
226
+ return this.isConnected;
227
+ }
228
+ /**
229
+ * Get connection status details
230
+ */
231
+ getConnectionStatus() {
232
+ const useRemote = this.config.get('mcpUseRemote') ?? false;
233
+ return {
234
+ connected: this.isConnected,
235
+ mode: useRemote ? 'remote' : 'local',
236
+ server: useRemote
237
+ ? (this.config.get('mcpServerUrl') ?? 'https://api.lanonasis.com')
238
+ : (this.config.get('mcpServerPath') ?? 'local MCP server')
239
+ };
240
+ }
241
+ }
242
+ // Singleton instance
243
+ let mcpClientInstance = null;
244
+ export function getMCPClient() {
245
+ if (!mcpClientInstance) {
246
+ mcpClientInstance = new MCPClient();
247
+ }
248
+ return mcpClientInstance;
249
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanonasis/cli",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Lanonasis Enterprise CLI - Memory as a Service and Infrastructure Management",
5
5
  "main": "dist/index-simple.js",
6
6
  "bin": {
@@ -34,11 +34,24 @@
34
34
  "README.md"
35
35
  ],
36
36
  "dependencies": {
37
- "commander": "^12.1.0"
37
+ "@modelcontextprotocol/sdk": "^1.17.0",
38
+ "@types/eventsource": "^1.1.15",
39
+ "axios": "^1.11.0",
40
+ "chalk": "^5.4.1",
41
+ "commander": "^12.1.0",
42
+ "date-fns": "^4.1.0",
43
+ "dotenv": "^17.2.1",
44
+ "eventsource": "^4.0.0",
45
+ "inquirer": "^12.9.0",
46
+ "jwt-decode": "^4.0.0",
47
+ "open": "^10.2.0",
48
+ "ora": "^8.2.0",
49
+ "table": "^6.9.0",
50
+ "word-wrap": "^1.2.5"
38
51
  },
39
52
  "devDependencies": {
40
- "@types/node": "^22.10.2",
41
53
  "@types/inquirer": "^9.0.7",
54
+ "@types/node": "^22.10.2",
42
55
  "@typescript-eslint/eslint-plugin": "^8.18.1",
43
56
  "@typescript-eslint/parser": "^8.18.1",
44
57
  "eslint": "^9.17.0",
@@ -48,4 +61,4 @@
48
61
  "engines": {
49
62
  "node": ">=18.0.0"
50
63
  }
51
- }
64
+ }