@lanonasis/cli 3.0.1 → 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.
- package/dist/commands/enhanced-memory.d.ts +5 -0
- package/dist/commands/enhanced-memory.js +404 -0
- package/dist/commands/mcp.js +27 -6
- package/dist/index.js +0 -0
- package/dist/mcp/access-control.d.ts +68 -0
- package/dist/mcp/access-control.js +228 -0
- package/dist/mcp/enhanced-server.d.ts +38 -0
- package/dist/mcp/enhanced-server.js +320 -0
- package/dist/mcp/logger.d.ts +20 -0
- package/dist/mcp/logger.js +47 -0
- package/dist/mcp/memory-state.d.ts +81 -0
- package/dist/mcp/memory-state.js +301 -0
- package/dist/mcp/schemas/tool-schemas.d.ts +8 -8
- package/dist/mcp/vector-store.d.ts +31 -0
- package/dist/mcp/vector-store.js +92 -0
- package/package.json +1 -1
|
@@ -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
|
+
}
|
|
@@ -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
|
+
});
|