@lanonasis/cli 1.2.2 → 1.3.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/dist/commands/mcp.js +31 -12
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +281 -0
- package/dist/utils/mcp-client.d.ts +50 -1
- package/dist/utils/mcp-client.js +149 -31
- package/package.json +5 -2
package/dist/commands/mcp.js
CHANGED
|
@@ -54,41 +54,60 @@ export function mcpCommands(program) {
|
|
|
54
54
|
});
|
|
55
55
|
// Connect command
|
|
56
56
|
mcp.command('connect')
|
|
57
|
-
.description('Connect to MCP server (local or
|
|
57
|
+
.description('Connect to MCP server (local, remote, or WebSocket)')
|
|
58
58
|
.option('-l, --local', 'Connect to local MCP server')
|
|
59
59
|
.option('-r, --remote', 'Connect to remote MCP server (api.lanonasis.com)')
|
|
60
|
+
.option('-w, --websocket', 'Connect using WebSocket mode for enterprise users')
|
|
60
61
|
.option('-s, --server <path>', 'Local MCP server path')
|
|
61
|
-
.option('-u, --url <url>', 'Remote
|
|
62
|
+
.option('-u, --url <url>', 'Remote/WebSocket server URL')
|
|
62
63
|
.action(async (options) => {
|
|
63
64
|
const spinner = ora('Connecting to MCP server...').start();
|
|
64
65
|
const config = new CLIConfig();
|
|
65
66
|
try {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
67
|
+
let connectionMode;
|
|
68
|
+
// Determine connection mode - WebSocket takes precedence over remote and local
|
|
69
|
+
if (options.websocket) {
|
|
70
|
+
connectionMode = 'websocket';
|
|
71
|
+
}
|
|
72
|
+
else if (options.remote) {
|
|
73
|
+
connectionMode = 'remote';
|
|
74
|
+
}
|
|
75
|
+
else if (options.local) {
|
|
76
|
+
connectionMode = 'local';
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
69
79
|
// Default to remote if authenticated, otherwise local
|
|
70
|
-
|
|
80
|
+
connectionMode = !!config.get('token') ? 'remote' : 'local';
|
|
71
81
|
}
|
|
72
|
-
// Save
|
|
73
|
-
config.set('
|
|
82
|
+
// Save preferences
|
|
83
|
+
config.set('mcpConnectionMode', connectionMode);
|
|
74
84
|
if (options.server) {
|
|
75
85
|
config.set('mcpServerPath', options.server);
|
|
76
86
|
}
|
|
77
87
|
if (options.url) {
|
|
78
|
-
|
|
88
|
+
if (connectionMode === 'websocket') {
|
|
89
|
+
config.set('mcpWebSocketUrl', options.url);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
config.set('mcpServerUrl', options.url);
|
|
93
|
+
}
|
|
79
94
|
}
|
|
80
95
|
const client = getMCPClient();
|
|
81
96
|
const connected = await client.connect({
|
|
82
|
-
|
|
97
|
+
connectionMode,
|
|
83
98
|
serverPath: options.server,
|
|
84
99
|
serverUrl: options.url
|
|
85
100
|
});
|
|
86
101
|
if (connected) {
|
|
87
|
-
spinner.succeed(chalk.green(`Connected to
|
|
88
|
-
if (
|
|
102
|
+
spinner.succeed(chalk.green(`Connected to MCP server in ${connectionMode} mode`));
|
|
103
|
+
if (connectionMode === 'remote') {
|
|
89
104
|
console.log(chalk.cyan('ℹ️ Using remote MCP via api.lanonasis.com'));
|
|
90
105
|
console.log(chalk.cyan('📡 SSE endpoint active for real-time updates'));
|
|
91
106
|
}
|
|
107
|
+
else if (connectionMode === 'websocket') {
|
|
108
|
+
console.log(chalk.cyan('ℹ️ Using enterprise WebSocket MCP server'));
|
|
109
|
+
console.log(chalk.cyan('📡 WebSocket connection active with auto-reconnect'));
|
|
110
|
+
}
|
|
92
111
|
}
|
|
93
112
|
else {
|
|
94
113
|
spinner.fail('Failed to connect to MCP server');
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { CLIConfig } from './utils/config.js';
|
|
6
|
+
// Redirect all console output to stderr to avoid polluting stdout with non-JSON
|
|
7
|
+
const originalConsoleLog = console.log;
|
|
8
|
+
const originalConsoleError = console.error;
|
|
9
|
+
const originalConsoleWarn = console.warn;
|
|
10
|
+
const originalConsoleInfo = console.info;
|
|
11
|
+
console.log = (...args) => originalConsoleError('[MCP-LOG]', ...args);
|
|
12
|
+
console.error = (...args) => originalConsoleError('[MCP-ERROR]', ...args);
|
|
13
|
+
console.warn = (...args) => originalConsoleError('[MCP-WARN]', ...args);
|
|
14
|
+
console.info = (...args) => originalConsoleError('[MCP-INFO]', ...args);
|
|
15
|
+
// Silence ora spinner and chalk output by redirecting to stderr
|
|
16
|
+
process.env.FORCE_COLOR = '0'; // Disable colors for cleaner stderr output
|
|
17
|
+
class LanonasisMCPServer {
|
|
18
|
+
server;
|
|
19
|
+
config;
|
|
20
|
+
constructor() {
|
|
21
|
+
this.config = new CLIConfig();
|
|
22
|
+
this.server = new Server({
|
|
23
|
+
name: 'lanonasis-mcp-server',
|
|
24
|
+
version: '1.2.0',
|
|
25
|
+
}, {
|
|
26
|
+
capabilities: {
|
|
27
|
+
tools: {},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
this.setupHandlers();
|
|
31
|
+
}
|
|
32
|
+
setupHandlers() {
|
|
33
|
+
// List available tools
|
|
34
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
35
|
+
return {
|
|
36
|
+
tools: [
|
|
37
|
+
{
|
|
38
|
+
name: 'create_memory',
|
|
39
|
+
description: 'Create a new memory entry',
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
title: { type: 'string', description: 'Memory title' },
|
|
44
|
+
content: { type: 'string', description: 'Memory content' },
|
|
45
|
+
memory_type: { type: 'string', description: 'Type of memory', enum: ['note', 'task', 'reference'] },
|
|
46
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Memory tags' }
|
|
47
|
+
},
|
|
48
|
+
required: ['title', 'content']
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'search_memories',
|
|
53
|
+
description: 'Search through memories',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
query: { type: 'string', description: 'Search query' },
|
|
58
|
+
memory_type: { type: 'string', description: 'Filter by memory type' },
|
|
59
|
+
limit: { type: 'number', description: 'Maximum results to return', default: 10 }
|
|
60
|
+
},
|
|
61
|
+
required: ['query']
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'get_memory',
|
|
66
|
+
description: 'Get a specific memory by ID',
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
id: { type: 'string', description: 'Memory ID' }
|
|
71
|
+
},
|
|
72
|
+
required: ['id']
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'update_memory',
|
|
77
|
+
description: 'Update an existing memory',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
id: { type: 'string', description: 'Memory ID' },
|
|
82
|
+
title: { type: 'string', description: 'Memory title' },
|
|
83
|
+
content: { type: 'string', description: 'Memory content' },
|
|
84
|
+
memory_type: { type: 'string', description: 'Type of memory' },
|
|
85
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Memory tags' }
|
|
86
|
+
},
|
|
87
|
+
required: ['id']
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'delete_memory',
|
|
92
|
+
description: 'Delete a memory by ID',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
id: { type: 'string', description: 'Memory ID' }
|
|
97
|
+
},
|
|
98
|
+
required: ['id']
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
// Handle tool calls
|
|
105
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
106
|
+
const { name, arguments: args } = request.params;
|
|
107
|
+
try {
|
|
108
|
+
switch (name) {
|
|
109
|
+
case 'create_memory':
|
|
110
|
+
return await this.createMemory(args);
|
|
111
|
+
case 'search_memories':
|
|
112
|
+
return await this.searchMemories(args);
|
|
113
|
+
case 'get_memory':
|
|
114
|
+
return await this.getMemory(args);
|
|
115
|
+
case 'update_memory':
|
|
116
|
+
return await this.updateMemory(args);
|
|
117
|
+
case 'delete_memory':
|
|
118
|
+
return await this.deleteMemory(args);
|
|
119
|
+
default:
|
|
120
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
return {
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: 'text',
|
|
128
|
+
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async createMemory(args) {
|
|
136
|
+
const apiUrl = process.env.LANONASIS_API_URL || 'https://api.lanonasis.com';
|
|
137
|
+
const apiKey = process.env.LANONASIS_API_KEY || this.config.get('token');
|
|
138
|
+
if (!apiKey) {
|
|
139
|
+
throw new Error('No API key found. Please authenticate first.');
|
|
140
|
+
}
|
|
141
|
+
const response = await fetch(`${apiUrl}/api/memories`, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: {
|
|
144
|
+
'Content-Type': 'application/json',
|
|
145
|
+
'Authorization': `Bearer ${apiKey}`
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify(args)
|
|
148
|
+
});
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
throw new Error(`API request failed: ${response.statusText}`);
|
|
151
|
+
}
|
|
152
|
+
const result = await response.json();
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: 'text',
|
|
157
|
+
text: `Memory created successfully with ID: ${result.id}`
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async searchMemories(args) {
|
|
163
|
+
const apiUrl = process.env.LANONASIS_API_URL || 'https://api.lanonasis.com';
|
|
164
|
+
const apiKey = process.env.LANONASIS_API_KEY || this.config.get('token');
|
|
165
|
+
if (!apiKey) {
|
|
166
|
+
throw new Error('No API key found. Please authenticate first.');
|
|
167
|
+
}
|
|
168
|
+
const params = new URLSearchParams();
|
|
169
|
+
if (args.query)
|
|
170
|
+
params.append('query', args.query);
|
|
171
|
+
if (args.memory_type)
|
|
172
|
+
params.append('memory_type', args.memory_type);
|
|
173
|
+
if (args.limit)
|
|
174
|
+
params.append('limit', args.limit.toString());
|
|
175
|
+
const response = await fetch(`${apiUrl}/api/memories/search?${params}`, {
|
|
176
|
+
headers: {
|
|
177
|
+
'Authorization': `Bearer ${apiKey}`
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
throw new Error(`API request failed: ${response.statusText}`);
|
|
182
|
+
}
|
|
183
|
+
const results = await response.json();
|
|
184
|
+
return {
|
|
185
|
+
content: [
|
|
186
|
+
{
|
|
187
|
+
type: 'text',
|
|
188
|
+
text: `Found ${results.length} memories:\n\n${results.map((m) => `ID: ${m.id}\nTitle: ${m.title}\nType: ${m.memory_type}\nContent: ${m.content.substring(0, 100)}...\n`).join('\n')}`
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
async getMemory(args) {
|
|
194
|
+
const apiUrl = process.env.LANONASIS_API_URL || 'https://api.lanonasis.com';
|
|
195
|
+
const apiKey = process.env.LANONASIS_API_KEY || this.config.get('token');
|
|
196
|
+
if (!apiKey) {
|
|
197
|
+
throw new Error('No API key found. Please authenticate first.');
|
|
198
|
+
}
|
|
199
|
+
const response = await fetch(`${apiUrl}/api/memories/${args.id}`, {
|
|
200
|
+
headers: {
|
|
201
|
+
'Authorization': `Bearer ${apiKey}`
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new Error(`API request failed: ${response.statusText}`);
|
|
206
|
+
}
|
|
207
|
+
const memory = await response.json();
|
|
208
|
+
return {
|
|
209
|
+
content: [
|
|
210
|
+
{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: `Memory Details:\nID: ${memory.id}\nTitle: ${memory.title}\nType: ${memory.memory_type}\nContent: ${memory.content}\nTags: ${memory.tags?.join(', ') || 'None'}\nCreated: ${memory.created_at}`
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async updateMemory(args) {
|
|
218
|
+
const apiUrl = process.env.LANONASIS_API_URL || 'https://api.lanonasis.com';
|
|
219
|
+
const apiKey = process.env.LANONASIS_API_KEY || this.config.get('token');
|
|
220
|
+
if (!apiKey) {
|
|
221
|
+
throw new Error('No API key found. Please authenticate first.');
|
|
222
|
+
}
|
|
223
|
+
const { id, ...updateData } = args;
|
|
224
|
+
const response = await fetch(`${apiUrl}/api/memories/${id}`, {
|
|
225
|
+
method: 'PUT',
|
|
226
|
+
headers: {
|
|
227
|
+
'Content-Type': 'application/json',
|
|
228
|
+
'Authorization': `Bearer ${apiKey}`
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify(updateData)
|
|
231
|
+
});
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
throw new Error(`API request failed: ${response.statusText}`);
|
|
234
|
+
}
|
|
235
|
+
const result = await response.json();
|
|
236
|
+
return {
|
|
237
|
+
content: [
|
|
238
|
+
{
|
|
239
|
+
type: 'text',
|
|
240
|
+
text: `Memory ${id} updated successfully`
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async deleteMemory(args) {
|
|
246
|
+
const apiUrl = process.env.LANONASIS_API_URL || 'https://api.lanonasis.com';
|
|
247
|
+
const apiKey = process.env.LANONASIS_API_KEY || this.config.get('token');
|
|
248
|
+
if (!apiKey) {
|
|
249
|
+
throw new Error('No API key found. Please authenticate first.');
|
|
250
|
+
}
|
|
251
|
+
const response = await fetch(`${apiUrl}/api/memories/${args.id}`, {
|
|
252
|
+
method: 'DELETE',
|
|
253
|
+
headers: {
|
|
254
|
+
'Authorization': `Bearer ${apiKey}`
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
if (!response.ok) {
|
|
258
|
+
throw new Error(`API request failed: ${response.statusText}`);
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
content: [
|
|
262
|
+
{
|
|
263
|
+
type: 'text',
|
|
264
|
+
text: `Memory ${args.id} deleted successfully`
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
async run() {
|
|
270
|
+
const transport = new StdioServerTransport();
|
|
271
|
+
await this.server.connect(transport);
|
|
272
|
+
// Log to stderr that server is ready
|
|
273
|
+
console.error('[MCP-INFO] Lanonasis MCP Server started and ready');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Start the server
|
|
277
|
+
const server = new LanonasisMCPServer();
|
|
278
|
+
server.run().catch((error) => {
|
|
279
|
+
console.error('[MCP-ERROR] Failed to start server:', error);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
});
|
|
@@ -2,12 +2,53 @@ interface MCPConnectionOptions {
|
|
|
2
2
|
serverPath?: string;
|
|
3
3
|
serverUrl?: string;
|
|
4
4
|
useRemote?: boolean;
|
|
5
|
+
useWebSocket?: boolean;
|
|
6
|
+
connectionMode?: 'local' | 'remote' | 'websocket';
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Interface for MCP tool arguments
|
|
10
|
+
*/
|
|
11
|
+
interface MCPToolArgs {
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Interface for MCP tool response
|
|
16
|
+
*/
|
|
17
|
+
export interface MCPToolResponse {
|
|
18
|
+
result?: unknown;
|
|
19
|
+
error?: {
|
|
20
|
+
code: number;
|
|
21
|
+
message: string;
|
|
22
|
+
};
|
|
23
|
+
id?: string;
|
|
24
|
+
title?: string;
|
|
25
|
+
memory_type?: string;
|
|
26
|
+
length?: number;
|
|
27
|
+
forEach?: (callback: (item: any, index: number) => void) => void;
|
|
28
|
+
code?: number;
|
|
29
|
+
message?: string;
|
|
30
|
+
response?: any;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Interface for MCP WebSocket messages
|
|
34
|
+
*/
|
|
35
|
+
export interface MCPWebSocketMessage {
|
|
36
|
+
id: number;
|
|
37
|
+
method?: string;
|
|
38
|
+
params?: Record<string, unknown>;
|
|
39
|
+
result?: Record<string, unknown>;
|
|
40
|
+
error?: {
|
|
41
|
+
code: number;
|
|
42
|
+
message: string;
|
|
43
|
+
data?: unknown;
|
|
44
|
+
};
|
|
5
45
|
}
|
|
6
46
|
export declare class MCPClient {
|
|
7
47
|
private client;
|
|
8
48
|
private config;
|
|
9
49
|
private isConnected;
|
|
10
50
|
private sseConnection;
|
|
51
|
+
private wsConnection;
|
|
11
52
|
constructor();
|
|
12
53
|
/**
|
|
13
54
|
* Connect to MCP server (local or remote)
|
|
@@ -17,6 +58,14 @@ export declare class MCPClient {
|
|
|
17
58
|
* Initialize SSE connection for real-time updates
|
|
18
59
|
*/
|
|
19
60
|
private initializeSSE;
|
|
61
|
+
/**
|
|
62
|
+
* Initialize WebSocket connection for enterprise MCP server
|
|
63
|
+
*/
|
|
64
|
+
private initializeWebSocket;
|
|
65
|
+
/**
|
|
66
|
+
* Send a message over the WebSocket connection
|
|
67
|
+
*/
|
|
68
|
+
private sendWebSocketMessage;
|
|
20
69
|
/**
|
|
21
70
|
* Disconnect from MCP server
|
|
22
71
|
*/
|
|
@@ -24,7 +73,7 @@ export declare class MCPClient {
|
|
|
24
73
|
/**
|
|
25
74
|
* Call an MCP tool
|
|
26
75
|
*/
|
|
27
|
-
callTool(toolName: string, args:
|
|
76
|
+
callTool(toolName: string, args: MCPToolArgs): Promise<MCPToolResponse>;
|
|
28
77
|
/**
|
|
29
78
|
* Call remote tool via REST API with MCP interface
|
|
30
79
|
*/
|
package/dist/utils/mcp-client.js
CHANGED
|
@@ -5,6 +5,7 @@ import { CLIConfig } from './config.js';
|
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { EventSource } from 'eventsource';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
|
+
import WebSocket from 'ws';
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
const __dirname = path.dirname(__filename);
|
|
10
11
|
export class MCPClient {
|
|
@@ -12,6 +13,7 @@ export class MCPClient {
|
|
|
12
13
|
config;
|
|
13
14
|
isConnected = false;
|
|
14
15
|
sseConnection = null;
|
|
16
|
+
wsConnection = null;
|
|
15
17
|
constructor() {
|
|
16
18
|
this.config = new CLIConfig();
|
|
17
19
|
}
|
|
@@ -20,34 +22,59 @@ export class MCPClient {
|
|
|
20
22
|
*/
|
|
21
23
|
async connect(options = {}) {
|
|
22
24
|
try {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
25
|
+
// Determine connection mode with priority to explicit mode option
|
|
26
|
+
const connectionMode = options.connectionMode ??
|
|
27
|
+
(options.useWebSocket ? 'websocket' :
|
|
28
|
+
options.useRemote ? 'remote' :
|
|
29
|
+
this.config.get('mcpConnectionMode') ??
|
|
30
|
+
this.config.get('mcpUseRemote') ? 'remote' : 'local');
|
|
31
|
+
let wsUrl;
|
|
32
|
+
let serverUrl;
|
|
33
|
+
let serverPath;
|
|
34
|
+
switch (connectionMode) {
|
|
35
|
+
case 'websocket':
|
|
36
|
+
// WebSocket connection mode for enterprise users
|
|
37
|
+
wsUrl = options.serverUrl ??
|
|
38
|
+
this.config.get('mcpWebSocketUrl') ??
|
|
39
|
+
'ws://localhost:8081/mcp/ws';
|
|
40
|
+
console.log(chalk.cyan(`Connecting to WebSocket MCP server at ${wsUrl}...`));
|
|
41
|
+
// Initialize WebSocket connection
|
|
42
|
+
await this.initializeWebSocket(wsUrl);
|
|
43
|
+
this.isConnected = true;
|
|
44
|
+
return true;
|
|
45
|
+
case 'remote':
|
|
46
|
+
// For remote MCP, we'll use the REST API with MCP-style interface
|
|
47
|
+
serverUrl = options.serverUrl ??
|
|
48
|
+
this.config.get('mcpServerUrl') ??
|
|
49
|
+
'https://api.lanonasis.com';
|
|
50
|
+
console.log(chalk.cyan(`Connecting to remote MCP server at ${serverUrl}...`));
|
|
51
|
+
// Initialize SSE connection for real-time updates
|
|
52
|
+
await this.initializeSSE(serverUrl);
|
|
53
|
+
this.isConnected = true;
|
|
54
|
+
return true;
|
|
55
|
+
case 'local':
|
|
56
|
+
default:
|
|
57
|
+
{
|
|
58
|
+
// Local MCP server connection
|
|
59
|
+
serverPath = options.serverPath ??
|
|
60
|
+
this.config.get('mcpServerPath') ??
|
|
61
|
+
path.join(__dirname, '../../../../onasis-gateway/mcp-server/server.js');
|
|
62
|
+
console.log(chalk.cyan(`Connecting to local MCP server at ${serverPath}...`));
|
|
63
|
+
const localTransport = new StdioClientTransport({
|
|
64
|
+
command: 'node',
|
|
65
|
+
args: [serverPath]
|
|
66
|
+
});
|
|
67
|
+
this.client = new Client({
|
|
68
|
+
name: '@lanonasis/cli',
|
|
69
|
+
version: '1.0.0'
|
|
70
|
+
}, {
|
|
71
|
+
capabilities: {}
|
|
72
|
+
});
|
|
73
|
+
await this.client.connect(localTransport);
|
|
74
|
+
}
|
|
75
|
+
this.isConnected = true;
|
|
76
|
+
console.log(chalk.green('✓ Connected to MCP server'));
|
|
77
|
+
return true;
|
|
51
78
|
}
|
|
52
79
|
}
|
|
53
80
|
catch (error) {
|
|
@@ -79,6 +106,87 @@ export class MCPClient {
|
|
|
79
106
|
};
|
|
80
107
|
}
|
|
81
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Initialize WebSocket connection for enterprise MCP server
|
|
111
|
+
*/
|
|
112
|
+
async initializeWebSocket(wsUrl) {
|
|
113
|
+
const token = this.config.get('token');
|
|
114
|
+
if (!token) {
|
|
115
|
+
throw new Error('API key required for WebSocket mode. Set LANONASIS_API_KEY or login first.');
|
|
116
|
+
}
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
try {
|
|
119
|
+
// Close existing connection if any
|
|
120
|
+
if (this.wsConnection) {
|
|
121
|
+
this.wsConnection.close();
|
|
122
|
+
this.wsConnection = null;
|
|
123
|
+
}
|
|
124
|
+
// Create new WebSocket connection with authentication
|
|
125
|
+
this.wsConnection = new WebSocket(wsUrl, {
|
|
126
|
+
headers: {
|
|
127
|
+
'Authorization': `Bearer ${token}`,
|
|
128
|
+
'X-API-Key': token
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
this.wsConnection.on('open', () => {
|
|
132
|
+
console.log(chalk.green('✅ Connected to MCP WebSocket server'));
|
|
133
|
+
// Send initialization message
|
|
134
|
+
this.sendWebSocketMessage({
|
|
135
|
+
id: 1,
|
|
136
|
+
method: 'initialize',
|
|
137
|
+
params: {
|
|
138
|
+
protocolVersion: '2024-11-05',
|
|
139
|
+
capabilities: {
|
|
140
|
+
tools: ['memory_management', 'workflow_orchestration']
|
|
141
|
+
},
|
|
142
|
+
clientInfo: {
|
|
143
|
+
name: '@lanonasis/cli',
|
|
144
|
+
version: '1.1.0'
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
resolve();
|
|
149
|
+
});
|
|
150
|
+
this.wsConnection.on('message', (data) => {
|
|
151
|
+
try {
|
|
152
|
+
const message = JSON.parse(data.toString());
|
|
153
|
+
console.log(chalk.blue('📡 MCP message:'), message.id, message.method || 'response');
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.error('Failed to parse WebSocket message:', error);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
this.wsConnection.on('error', (error) => {
|
|
160
|
+
console.error(chalk.red('WebSocket error:'), error);
|
|
161
|
+
reject(error);
|
|
162
|
+
});
|
|
163
|
+
this.wsConnection.on('close', (code, reason) => {
|
|
164
|
+
console.log(chalk.yellow(`WebSocket connection closed (${code}): ${reason}`));
|
|
165
|
+
// Auto-reconnect after delay
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
if (this.isConnected) {
|
|
168
|
+
console.log(chalk.blue('🔄 Attempting to reconnect to WebSocket...'));
|
|
169
|
+
this.initializeWebSocket(wsUrl).catch(err => {
|
|
170
|
+
console.error('Failed to reconnect:', err);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}, 5000);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
reject(error);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Send a message over the WebSocket connection
|
|
183
|
+
*/
|
|
184
|
+
sendWebSocketMessage(message) {
|
|
185
|
+
if (!this.wsConnection) {
|
|
186
|
+
throw new Error('WebSocket not connected');
|
|
187
|
+
}
|
|
188
|
+
this.wsConnection.send(JSON.stringify(message));
|
|
189
|
+
}
|
|
82
190
|
/**
|
|
83
191
|
* Disconnect from MCP server
|
|
84
192
|
*/
|
|
@@ -115,7 +223,12 @@ export class MCPClient {
|
|
|
115
223
|
name: toolName,
|
|
116
224
|
arguments: args
|
|
117
225
|
});
|
|
118
|
-
|
|
226
|
+
// Convert the SDK result to our expected MCPToolResponse format
|
|
227
|
+
return {
|
|
228
|
+
result: result,
|
|
229
|
+
code: 200,
|
|
230
|
+
message: 'Success'
|
|
231
|
+
};
|
|
119
232
|
}
|
|
120
233
|
catch (error) {
|
|
121
234
|
throw new Error(`MCP tool call failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
@@ -177,7 +290,8 @@ export class MCPClient {
|
|
|
177
290
|
// Handle dynamic endpoint for memory operations that need ID
|
|
178
291
|
let endpoint = mapping.endpoint;
|
|
179
292
|
if (endpoint.includes('{id}') && args.memory_id) {
|
|
180
|
-
|
|
293
|
+
// Ensure memory_id is treated as a string for replacement
|
|
294
|
+
endpoint = endpoint.replace('{id}', String(args.memory_id));
|
|
181
295
|
}
|
|
182
296
|
const response = await axios({
|
|
183
297
|
method: mapping.method,
|
|
@@ -192,7 +306,11 @@ export class MCPClient {
|
|
|
192
306
|
return response.data;
|
|
193
307
|
}
|
|
194
308
|
catch (error) {
|
|
195
|
-
|
|
309
|
+
// Safely handle errors with type checking
|
|
310
|
+
const errorObj = error;
|
|
311
|
+
const errorMsg = errorObj.response?.data?.error ||
|
|
312
|
+
(errorObj.message ? errorObj.message : 'Unknown error');
|
|
313
|
+
throw new Error(`Remote tool call failed: ${errorMsg}`);
|
|
196
314
|
}
|
|
197
315
|
}
|
|
198
316
|
/**
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lanonasis/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "LanOnasis Enterprise CLI - Memory as a Service, API Key Management, and Infrastructure Orchestration",
|
|
5
5
|
"main": "dist/index-simple.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"lanonasis": "dist/index-simple.js",
|
|
8
|
+
"lanonasis-mcp-server": "dist/mcp-server.js",
|
|
8
9
|
"memory": "dist/index-simple.js",
|
|
9
10
|
"maas": "dist/index-simple.js"
|
|
10
11
|
},
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
"dependencies": {
|
|
41
42
|
"@modelcontextprotocol/sdk": "^1.17.0",
|
|
42
43
|
"@types/eventsource": "^1.1.15",
|
|
44
|
+
"@types/ws": "^8.18.1",
|
|
43
45
|
"axios": "^1.11.0",
|
|
44
46
|
"chalk": "^5.4.1",
|
|
45
47
|
"cli-table3": "^0.6.5",
|
|
@@ -52,7 +54,8 @@
|
|
|
52
54
|
"open": "^10.2.0",
|
|
53
55
|
"ora": "^8.2.0",
|
|
54
56
|
"table": "^6.9.0",
|
|
55
|
-
"word-wrap": "^1.2.5"
|
|
57
|
+
"word-wrap": "^1.2.5",
|
|
58
|
+
"ws": "^8.18.3"
|
|
56
59
|
},
|
|
57
60
|
"devDependencies": {
|
|
58
61
|
"@types/inquirer": "^9.0.7",
|