@lanonasis/cli 2.0.9 → 3.0.2

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,228 @@
1
+ /**
2
+ * Memory Access Control System
3
+ * Implements granular permissions and audit logging inspired by mem0's ACL system
4
+ */
5
+ import { CLIConfig } from '../utils/config.js';
6
+ import { logger } from './logger.js';
7
+ export class MemoryAccessControl {
8
+ config;
9
+ accessRules = new Map();
10
+ accessLogs = [];
11
+ constructor() {
12
+ this.config = new CLIConfig();
13
+ }
14
+ /**
15
+ * Check if user has access to create memories in an app
16
+ */
17
+ async checkCreateAccess(userId, appId) {
18
+ try {
19
+ // Default: users can create memories in their own apps
20
+ if (await this.isUserApp(userId, appId)) {
21
+ return true;
22
+ }
23
+ // Check explicit permissions
24
+ const rules = this.getAccessRules(userId, appId);
25
+ return rules.some(rule => rule.granted &&
26
+ (rule.permission === 'write' || rule.permission === 'admin') &&
27
+ (!rule.expires_at || new Date(rule.expires_at) > new Date()));
28
+ }
29
+ catch (error) {
30
+ logger.error('Failed to check create access', { error, userId, appId });
31
+ return false;
32
+ }
33
+ }
34
+ /**
35
+ * Check if user has access to read a specific memory
36
+ */
37
+ async checkMemoryAccess(memoryId, appId) {
38
+ try {
39
+ const memory = await this.getMemoryInfo(memoryId);
40
+ const currentUserId = await this.getCurrentUserId();
41
+ if (!memory) {
42
+ return false;
43
+ }
44
+ // Owner always has access
45
+ if (memory.user_id === currentUserId) {
46
+ return true;
47
+ }
48
+ // Check explicit permissions using current user ID (FIXED)
49
+ const rules = this.getAccessRules(currentUserId, appId);
50
+ return rules.some(rule => rule.granted &&
51
+ (!rule.expires_at || new Date(rule.expires_at) > new Date()) &&
52
+ (rule.memory_id === memoryId || !rule.memory_id));
53
+ }
54
+ catch (error) {
55
+ logger.error('Failed to check memory access', { error, memoryId, appId });
56
+ return false;
57
+ }
58
+ }
59
+ /**
60
+ * Get all memories accessible to a user in an app
61
+ */
62
+ async getAccessibleMemories(userId, appId) {
63
+ try {
64
+ // Get user's own memories
65
+ const ownMemories = await this.getUserMemories(userId);
66
+ // Get memories shared with the user
67
+ const sharedMemories = await this.getSharedMemories(userId, appId);
68
+ // Combine and deduplicate
69
+ const allMemories = [...ownMemories, ...sharedMemories];
70
+ return allMemories;
71
+ }
72
+ catch (error) {
73
+ logger.error('Failed to get accessible memories', { error, userId, appId });
74
+ return [];
75
+ }
76
+ }
77
+ /**
78
+ * Log memory access for audit purposes
79
+ */
80
+ async logMemoryAccess(memoryId, appId, accessType, metadata) {
81
+ try {
82
+ const userId = await this.getCurrentUserId();
83
+ const logEntry = {
84
+ id: this.generateId(),
85
+ user_id: userId,
86
+ app_id: appId,
87
+ memory_id: memoryId,
88
+ access_type: accessType,
89
+ timestamp: new Date().toISOString(),
90
+ success: true,
91
+ metadata
92
+ };
93
+ this.accessLogs.push(logEntry);
94
+ // In production, this would be persisted to database
95
+ logger.debug('Memory access logged', logEntry);
96
+ // Keep only recent logs in memory (last 1000)
97
+ if (this.accessLogs.length > 1000) {
98
+ this.accessLogs = this.accessLogs.slice(-1000);
99
+ }
100
+ }
101
+ catch (error) {
102
+ logger.error('Failed to log memory access', { error, memoryId, appId, accessType });
103
+ }
104
+ }
105
+ /**
106
+ * Grant access to a memory or app
107
+ */
108
+ async grantAccess(userId, appId, permission, memoryId, expiresAt) {
109
+ const rule = {
110
+ id: this.generateId(),
111
+ user_id: userId,
112
+ app_id: appId,
113
+ memory_id: memoryId,
114
+ permission,
115
+ granted: true,
116
+ created_at: new Date().toISOString(),
117
+ expires_at: expiresAt
118
+ };
119
+ const key = `${userId}:${appId}`;
120
+ const existingRules = this.accessRules.get(key) || [];
121
+ existingRules.push(rule);
122
+ this.accessRules.set(key, existingRules);
123
+ logger.info('Access granted', { userId, appId, permission, memoryId });
124
+ }
125
+ /**
126
+ * Revoke access to a memory or app
127
+ */
128
+ async revokeAccess(userId, appId, memoryId) {
129
+ const key = `${userId}:${appId}`;
130
+ const existingRules = this.accessRules.get(key) || [];
131
+ const updatedRules = existingRules.map(rule => {
132
+ if (!memoryId || rule.memory_id === memoryId) {
133
+ return { ...rule, granted: false };
134
+ }
135
+ return rule;
136
+ });
137
+ this.accessRules.set(key, updatedRules);
138
+ logger.info('Access revoked', { userId, appId, memoryId });
139
+ }
140
+ /**
141
+ * Get access logs for audit purposes
142
+ */
143
+ getAccessLogs(userId, appId, memoryId, limit = 100) {
144
+ let logs = this.accessLogs;
145
+ if (userId) {
146
+ logs = logs.filter(log => log.user_id === userId);
147
+ }
148
+ if (appId) {
149
+ logs = logs.filter(log => log.app_id === appId);
150
+ }
151
+ if (memoryId) {
152
+ logs = logs.filter(log => log.memory_id === memoryId);
153
+ }
154
+ return logs
155
+ .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
156
+ .slice(0, limit);
157
+ }
158
+ /**
159
+ * Private helper methods
160
+ */
161
+ getAccessRules(userId, appId) {
162
+ const key = `${userId}:${appId}`;
163
+ return this.accessRules.get(key) || [];
164
+ }
165
+ async isUserApp(userId, appId) {
166
+ // In a real implementation, this would check if the app belongs to the user
167
+ // For now, assume apps starting with user ID belong to them
168
+ return appId.startsWith(userId) || appId === 'default';
169
+ }
170
+ async getCurrentUserId() {
171
+ const token = this.config.get('token');
172
+ if (token && typeof token === 'string') {
173
+ try {
174
+ const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
175
+ return payload.sub || payload.user_id || 'anonymous';
176
+ }
177
+ catch {
178
+ return 'anonymous';
179
+ }
180
+ }
181
+ return 'anonymous';
182
+ }
183
+ async getMemoryInfo(memoryId) {
184
+ try {
185
+ // This would typically fetch from the API
186
+ const apiUrl = this.config.get('apiUrl') || 'https://api.lanonasis.com';
187
+ const token = this.config.get('token');
188
+ const axios = (await import('axios')).default;
189
+ const response = await axios.get(`${apiUrl}/api/v1/memory/${memoryId}`, {
190
+ headers: {
191
+ 'Authorization': `Bearer ${token}`,
192
+ 'Content-Type': 'application/json'
193
+ }
194
+ });
195
+ return response.data;
196
+ }
197
+ catch (error) {
198
+ logger.error('Failed to get memory info', { error, memoryId });
199
+ return null;
200
+ }
201
+ }
202
+ async getUserMemories(userId) {
203
+ try {
204
+ const apiUrl = this.config.get('apiUrl') || 'https://api.lanonasis.com';
205
+ const token = this.config.get('token');
206
+ const axios = (await import('axios')).default;
207
+ const response = await axios.get(`${apiUrl}/api/v1/memory?user_id=${userId}`, {
208
+ headers: {
209
+ 'Authorization': `Bearer ${token}`,
210
+ 'Content-Type': 'application/json'
211
+ }
212
+ });
213
+ return response.data.memories?.map((m) => m.id) || [];
214
+ }
215
+ catch (error) {
216
+ logger.error('Failed to get user memories', { error, userId });
217
+ return [];
218
+ }
219
+ }
220
+ async getSharedMemories(userId, appId) {
221
+ // This would implement logic to find memories shared with the user
222
+ // through explicit permissions or app-level sharing
223
+ return [];
224
+ }
225
+ generateId() {
226
+ return `acl_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
227
+ }
228
+ }
@@ -128,7 +128,7 @@ export class EnhancedMCPClient extends EventEmitter {
128
128
  this.transports.set(config.name, transport);
129
129
  const client = new Client({
130
130
  name: `lanonasis-cli-${config.name}`,
131
- version: '2.0.8'
131
+ version: '3.0.1'
132
132
  }, {
133
133
  capabilities: {
134
134
  tools: {},
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Enhanced MCP Server - Adopting mem0 OpenMemory architectural patterns
4
+ * Implements FastMCP-based server with context management and multi-transport support
5
+ */
6
+ export declare class EnhancedMCPServer {
7
+ private server;
8
+ private config;
9
+ private accessControl;
10
+ private vectorStore;
11
+ private stateManager;
12
+ private context;
13
+ constructor();
14
+ private setContext;
15
+ private getContext;
16
+ /**
17
+ * Setup MCP tools with enhanced functionality
18
+ */
19
+ private setupTools;
20
+ private handleCreateMemory;
21
+ private handleSearchMemories;
22
+ private handleBulkOperations;
23
+ /**
24
+ * Start the enhanced MCP server
25
+ */
26
+ start(options?: {
27
+ transport?: 'stdio' | 'sse';
28
+ port?: number;
29
+ verbose?: boolean;
30
+ }): Promise<void>;
31
+ /**
32
+ * Helper methods for memory operations
33
+ */
34
+ private createMemoryWithState;
35
+ private performVectorSearch;
36
+ private performBulkOperation;
37
+ private getCurrentUserId;
38
+ }
@@ -0,0 +1,320 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Enhanced MCP Server - Adopting mem0 OpenMemory architectural patterns
4
+ * Implements FastMCP-based server with context management and multi-transport support
5
+ */
6
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { CLIConfig } from '../utils/config.js';
9
+ import { logger } from './logger.js';
10
+ import { MemoryAccessControl } from './access-control.js';
11
+ import { LanonasisVectorStore } from './vector-store.js';
12
+ import { MemoryStateManager } from './memory-state.js';
13
+ export class EnhancedMCPServer {
14
+ server;
15
+ config;
16
+ accessControl;
17
+ vectorStore;
18
+ stateManager;
19
+ context = new Map();
20
+ constructor() {
21
+ this.server = new Server({
22
+ name: "enhanced-lanonasis-mcp",
23
+ version: "1.0.0",
24
+ }, {
25
+ capabilities: {
26
+ tools: {},
27
+ resources: {},
28
+ prompts: {},
29
+ },
30
+ });
31
+ this.config = new CLIConfig();
32
+ this.accessControl = new MemoryAccessControl();
33
+ this.vectorStore = new LanonasisVectorStore();
34
+ this.stateManager = new MemoryStateManager();
35
+ this.setupTools();
36
+ }
37
+ setContext(sessionId, context) {
38
+ this.context.set(sessionId, context);
39
+ }
40
+ getContext(sessionId) {
41
+ return this.context.get(sessionId) || {};
42
+ }
43
+ /**
44
+ * Setup MCP tools with enhanced functionality
45
+ */
46
+ setupTools() {
47
+ // Enhanced memory creation with state management
48
+ this.server.setRequestHandler({ method: "tools/list" }, async () => ({
49
+ tools: [
50
+ {
51
+ name: "memory_create_memory",
52
+ description: "Create a new memory with advanced state management and access control",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ title: { type: "string", description: "Memory title" },
57
+ content: { type: "string", description: "Memory content" },
58
+ memory_type: { type: "string", enum: ["context", "fact", "preference", "workflow"], default: "context" },
59
+ tags: { type: "array", items: { type: "string" }, description: "Memory tags" },
60
+ app_id: { type: "string", description: "Application identifier" },
61
+ metadata: { type: "object", description: "Additional metadata" }
62
+ },
63
+ required: ["content"]
64
+ }
65
+ },
66
+ {
67
+ name: "memory_search_memories",
68
+ description: "Search memories with advanced filtering and access control",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {
72
+ query: { type: "string", description: "Search query" },
73
+ limit: { type: "number", default: 10, description: "Maximum results" },
74
+ threshold: { type: "number", default: 0.7, description: "Similarity threshold" },
75
+ app_id: { type: "string", description: "Filter by application" },
76
+ category: { type: "string", description: "Filter by category" }
77
+ },
78
+ required: ["query"]
79
+ }
80
+ },
81
+ {
82
+ name: "memory_bulk_operations",
83
+ description: "Perform bulk operations on memories (pause, delete, archive)",
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ operation: { type: "string", enum: ["pause", "delete", "archive"] },
88
+ category: { type: "string", description: "Filter by category" },
89
+ app_id: { type: "string", description: "Filter by application" },
90
+ before: { type: "string", description: "Filter memories before date (ISO)" },
91
+ memory_ids: { type: "array", items: { type: "string" }, description: "Specific memory IDs" }
92
+ },
93
+ required: ["operation"]
94
+ }
95
+ }
96
+ ]
97
+ }));
98
+ this.server.setRequestHandler({ method: "tools/call" }, async (request) => {
99
+ const { name, arguments: args } = request.params;
100
+ try {
101
+ switch (name) {
102
+ case "memory_create_memory":
103
+ return await this.handleCreateMemory(args);
104
+ case "memory_search_memories":
105
+ return await this.handleSearchMemories(args);
106
+ case "memory_bulk_operations":
107
+ return await this.handleBulkOperations(args);
108
+ default:
109
+ throw new Error(`Unknown tool: ${name}`);
110
+ }
111
+ }
112
+ catch (error) {
113
+ logger.error(`Tool execution failed: ${name}`, { error, args });
114
+ throw error;
115
+ }
116
+ });
117
+ }
118
+ async handleCreateMemory(args) {
119
+ try {
120
+ const userId = await this.getCurrentUserId();
121
+ const appId = args.app_id || 'default';
122
+ // Check access control
123
+ if (!await this.accessControl.checkCreateAccess(userId, appId)) {
124
+ throw new Error('Access denied: Cannot create memories in this app');
125
+ }
126
+ // Create memory with state management
127
+ const memory = await this.createMemoryWithState({
128
+ content: args.content,
129
+ title: args.title,
130
+ memory_type: args.memory_type || 'context',
131
+ tags: args.tags || [],
132
+ app_id: appId,
133
+ metadata: args.metadata || {}
134
+ });
135
+ // Log access
136
+ await this.accessControl.logMemoryAccess(memory.id, appId, 'create', {
137
+ memory_type: args.memory_type,
138
+ tags: args.tags
139
+ });
140
+ return {
141
+ content: [{
142
+ type: "text",
143
+ text: `Memory created successfully with ID: ${memory.id}`
144
+ }],
145
+ memory: memory,
146
+ message: "Memory created successfully with enhanced access control"
147
+ };
148
+ }
149
+ catch (error) {
150
+ logger.error('Memory creation failed', { error, args });
151
+ throw error;
152
+ }
153
+ }
154
+ async handleSearchMemories(args) {
155
+ try {
156
+ const userId = await this.getCurrentUserId();
157
+ const appId = args.app_id || 'default';
158
+ // Get accessible memories
159
+ const accessibleMemories = await this.accessControl.getAccessibleMemories(userId, appId);
160
+ // Perform vector search
161
+ const results = await this.performVectorSearch({
162
+ query: args.query,
163
+ limit: args.limit || 10,
164
+ threshold: args.threshold || 0.7,
165
+ filter: {
166
+ app_id: appId,
167
+ memory_ids: accessibleMemories
168
+ }
169
+ });
170
+ // Log access
171
+ await this.accessControl.logMemoryAccess('search', appId, 'search', {
172
+ query: args.query,
173
+ results_count: results.length
174
+ });
175
+ return {
176
+ content: [{
177
+ type: "text",
178
+ text: `Found ${results.length} memories matching your query`
179
+ }],
180
+ results: results,
181
+ total: results.length,
182
+ message: `Found ${results.length} memories`
183
+ };
184
+ }
185
+ catch (error) {
186
+ logger.error('Memory search failed', { error, args });
187
+ throw error;
188
+ }
189
+ }
190
+ async handleBulkOperations(args) {
191
+ try {
192
+ const userId = await this.getCurrentUserId();
193
+ const appId = args.app_id || 'default';
194
+ // Get accessible memories
195
+ const accessibleMemories = await this.accessControl.getAccessibleMemories(userId, appId);
196
+ // Filter memories based on criteria
197
+ let targetMemories = accessibleMemories;
198
+ if (args.memory_ids) {
199
+ targetMemories = args.memory_ids.filter(id => accessibleMemories.includes(id));
200
+ }
201
+ // Perform bulk operation
202
+ const results = await this.performBulkOperation({
203
+ operation: args.operation,
204
+ memory_ids: targetMemories,
205
+ metadata: {
206
+ user_id: userId,
207
+ app_id: appId,
208
+ timestamp: new Date().toISOString()
209
+ }
210
+ });
211
+ // Log bulk access
212
+ await this.accessControl.logMemoryAccess('bulk', appId, args.operation, {
213
+ operation: args.operation,
214
+ affected_count: results.length
215
+ });
216
+ return {
217
+ content: [{
218
+ type: "text",
219
+ text: `Bulk ${args.operation} completed on ${results.length} memories`
220
+ }],
221
+ results,
222
+ affected_count: results.length,
223
+ message: `Bulk ${args.operation} completed on ${results.length} memories`
224
+ };
225
+ }
226
+ catch (error) {
227
+ logger.error('Bulk operation failed', { error, args });
228
+ throw error;
229
+ }
230
+ }
231
+ /**
232
+ * Start the enhanced MCP server
233
+ */
234
+ async start(options = {}) {
235
+ const { transport = 'stdio', port = 3001, verbose = false } = options;
236
+ try {
237
+ // Initialize components
238
+ await this.config.init();
239
+ await this.vectorStore.initialize();
240
+ await this.stateManager.initialize();
241
+ if (verbose) {
242
+ logger.info('Starting Enhanced MCP Server', { transport, port });
243
+ }
244
+ // Use stdio transport (SSE not available in current MCP SDK)
245
+ const stdioTransport = new StdioServerTransport();
246
+ await this.server.connect(stdioTransport);
247
+ logger.info('Enhanced MCP Server running on stdio transport');
248
+ }
249
+ catch (error) {
250
+ logger.error('Failed to start Enhanced MCP Server', { error });
251
+ throw error;
252
+ }
253
+ }
254
+ /**
255
+ * Helper methods for memory operations
256
+ */
257
+ async createMemoryWithState(data) {
258
+ // Mock implementation - in production this would use the actual state manager
259
+ return {
260
+ id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
261
+ content: data.content,
262
+ title: data.title,
263
+ memory_type: data.memory_type,
264
+ tags: data.tags,
265
+ app_id: data.app_id,
266
+ metadata: data.metadata,
267
+ created_at: new Date().toISOString(),
268
+ state: 'active'
269
+ };
270
+ }
271
+ async performVectorSearch(params) {
272
+ // Mock implementation - in production this would use the actual vector store
273
+ return [
274
+ {
275
+ id: `mem_${Date.now()}`,
276
+ content: `Mock result for query: ${params.query}`,
277
+ score: 0.85,
278
+ metadata: { app_id: params.filter?.app_id }
279
+ }
280
+ ];
281
+ }
282
+ async performBulkOperation(params) {
283
+ // Mock implementation - in production this would use the actual state manager
284
+ return params.memory_ids.map((id) => ({
285
+ memory_id: id,
286
+ operation: params.operation,
287
+ success: true,
288
+ timestamp: new Date().toISOString()
289
+ }));
290
+ }
291
+ async getCurrentUserId() {
292
+ const token = this.config.get('token');
293
+ if (token && typeof token === 'string') {
294
+ try {
295
+ const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
296
+ return payload.sub || payload.user_id || 'anonymous';
297
+ }
298
+ catch (error) {
299
+ logger.error('Failed to decode token', { error });
300
+ }
301
+ }
302
+ return 'anonymous';
303
+ }
304
+ }
305
+ /**
306
+ * Main function to start the server
307
+ */
308
+ async function main() {
309
+ const server = new EnhancedMCPServer();
310
+ const args = process.argv.slice(2);
311
+ const options = {
312
+ transport: args.includes('--sse') ? 'sse' : 'stdio',
313
+ port: parseInt(args.find(arg => arg.startsWith('--port='))?.split('=')[1] || '3001'),
314
+ verbose: args.includes('--verbose') || args.includes('-v')
315
+ };
316
+ await server.start(options);
317
+ }
318
+ if (import.meta.url === `file://${process.argv[1]}`) {
319
+ main().catch(console.error);
320
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Enhanced logging system for MCP server
3
+ * Provides structured logging with different levels and contexts
4
+ */
5
+ export interface LogContext {
6
+ [key: string]: any;
7
+ }
8
+ export declare class Logger {
9
+ private defaultContext;
10
+ private context;
11
+ constructor(defaultContext?: LogContext);
12
+ private formatMessage;
13
+ info(message: string, context?: LogContext): void;
14
+ warn(message: string, context?: LogContext): void;
15
+ error(message: string, context?: LogContext): void;
16
+ debug(message: string, context?: LogContext): void;
17
+ setContext(context: LogContext): void;
18
+ child(context: LogContext): Logger;
19
+ }
20
+ export declare const logger: Logger;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Enhanced logging system for MCP server
3
+ * Provides structured logging with different levels and contexts
4
+ */
5
+ export class Logger {
6
+ defaultContext;
7
+ context = {};
8
+ constructor(defaultContext = {}) {
9
+ this.defaultContext = defaultContext;
10
+ this.context = { ...defaultContext };
11
+ }
12
+ formatMessage(level, message, context) {
13
+ const timestamp = new Date().toISOString();
14
+ const fullContext = { ...this.context, ...context };
15
+ const logEntry = {
16
+ timestamp,
17
+ level,
18
+ message,
19
+ ...fullContext
20
+ };
21
+ return JSON.stringify(logEntry);
22
+ }
23
+ info(message, context) {
24
+ console.log(this.formatMessage('INFO', message, context));
25
+ }
26
+ warn(message, context) {
27
+ console.warn(this.formatMessage('WARN', message, context));
28
+ }
29
+ error(message, context) {
30
+ console.error(this.formatMessage('ERROR', message, context));
31
+ }
32
+ debug(message, context) {
33
+ if (process.env.MCP_VERBOSE === 'true') {
34
+ console.debug(this.formatMessage('DEBUG', message, context));
35
+ }
36
+ }
37
+ setContext(context) {
38
+ this.context = { ...this.context, ...context };
39
+ }
40
+ child(context) {
41
+ return new Logger({ ...this.context, ...context });
42
+ }
43
+ }
44
+ export const logger = new Logger({
45
+ service: 'lanonasis-mcp-server',
46
+ version: '1.0.0'
47
+ });