@lanonasis/cli 1.0.0 → 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.
- package/README.md +77 -5
- package/dist/commands/auth.d.ts +6 -1
- package/dist/commands/auth.js +7 -4
- package/dist/commands/config.js +88 -4
- package/dist/commands/init.d.ts +5 -1
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +318 -0
- package/dist/commands/memory.js +20 -13
- package/dist/commands/topics.js +10 -5
- package/dist/index-simple.js +1 -1
- package/dist/index.js +27 -3
- package/dist/utils/api.d.ts +155 -15
- package/dist/utils/config.d.ts +6 -0
- package/dist/utils/config.js +46 -12
- package/dist/utils/formatting.d.ts +1 -1
- package/dist/utils/mcp-client.d.ts +53 -0
- package/dist/utils/mcp-client.js +249 -0
- package/package.json +17 -4
|
@@ -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.
|
|
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
|
-
"
|
|
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
|
+
}
|