claude-mem 3.0.2 → 3.0.4
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/.mcp.json +11 -0
- package/claude-mem +0 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +64 -0
- package/dist/commands/compress.d.ts +2 -0
- package/dist/commands/compress.js +59 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.js +372 -0
- package/dist/commands/load-context.d.ts +2 -0
- package/dist/commands/load-context.js +330 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +41 -0
- package/dist/commands/migrate.d.ts +9 -0
- package/dist/commands/migrate.js +174 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +159 -0
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.js +105 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +33 -0
- package/dist/constants.d.ts +516 -0
- package/dist/constants.js +522 -0
- package/dist/error-handler.d.ts +17 -0
- package/dist/error-handler.js +103 -0
- package/dist/mcp-server-cli.d.ts +34 -0
- package/dist/mcp-server-cli.js +158 -0
- package/dist/mcp-server.d.ts +103 -0
- package/dist/mcp-server.js +269 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.js +78 -0
- package/dist/utils/HookDetector.d.ts +64 -0
- package/dist/utils/HookDetector.js +213 -0
- package/dist/utils/PathResolver.d.ts +16 -0
- package/dist/utils/PathResolver.js +55 -0
- package/dist/utils/SettingsManager.d.ts +63 -0
- package/dist/utils/SettingsManager.js +133 -0
- package/dist/utils/TranscriptCompressor.d.ts +111 -0
- package/dist/utils/TranscriptCompressor.js +486 -0
- package/dist/utils/common.d.ts +29 -0
- package/dist/utils/common.js +14 -0
- package/dist/utils/error-utils.d.ts +93 -0
- package/dist/utils/error-utils.js +238 -0
- package/dist/utils/index.d.ts +19 -0
- package/dist/utils/index.js +26 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.js +42 -0
- package/dist/utils/mcp-client-factory.d.ts +51 -0
- package/dist/utils/mcp-client-factory.js +115 -0
- package/dist/utils/mcp-client.d.ts +75 -0
- package/dist/utils/mcp-client.js +120 -0
- package/dist/utils/memory-mcp-client.d.ts +135 -0
- package/dist/utils/memory-mcp-client.js +490 -0
- package/dist/utils/weaviate-mcp-adapter.d.ts +102 -0
- package/dist/utils/weaviate-mcp-adapter.js +587 -0
- package/package.json +3 -2
- package/src/claude-mem.js +0 -859
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory MCP Client for claude-mem
|
|
3
|
+
*
|
|
4
|
+
* This client implements the IMCPClient interface using the traditional JSONL file-based
|
|
5
|
+
* storage system. It maintains backward compatibility with the existing claude-mem
|
|
6
|
+
* architecture while providing a clean interface for MCP operations.
|
|
7
|
+
*
|
|
8
|
+
* Key Features:
|
|
9
|
+
* - 100% backward compatibility with existing JSONL index files
|
|
10
|
+
* - Implements full IMCPClient interface
|
|
11
|
+
* - Thread-safe file operations with locking
|
|
12
|
+
* - Efficient search through in-memory indexing
|
|
13
|
+
* - Atomic writes to prevent corruption
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
16
|
+
import { join } from 'path';
|
|
17
|
+
import { homedir } from 'os';
|
|
18
|
+
/**
|
|
19
|
+
* Memory MCP Client - JSONL file-based implementation
|
|
20
|
+
*
|
|
21
|
+
* This client provides the IMCPClient interface while using the existing
|
|
22
|
+
* JSONL file storage system for backward compatibility.
|
|
23
|
+
*/
|
|
24
|
+
export class MemoryMCPClient {
|
|
25
|
+
static instance = null;
|
|
26
|
+
isConnected = false;
|
|
27
|
+
cache = null;
|
|
28
|
+
// File paths
|
|
29
|
+
configDir = join(homedir(), '.claude-mem');
|
|
30
|
+
indexDir = join(this.configDir, 'index');
|
|
31
|
+
indexPath = join(this.indexDir, 'claude_mem_index.jsonl');
|
|
32
|
+
lockPath = join(this.indexDir, 'index.lock');
|
|
33
|
+
/**
|
|
34
|
+
* Singleton pattern - ensures only one instance manages file operations
|
|
35
|
+
*/
|
|
36
|
+
static getInstance() {
|
|
37
|
+
if (!MemoryMCPClient.instance) {
|
|
38
|
+
MemoryMCPClient.instance = new MemoryMCPClient();
|
|
39
|
+
}
|
|
40
|
+
return MemoryMCPClient.instance;
|
|
41
|
+
}
|
|
42
|
+
constructor() {
|
|
43
|
+
// Public constructor but singleton pattern enforced via getInstance()
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Connect to the memory backend (initialize file system)
|
|
47
|
+
*/
|
|
48
|
+
async connect() {
|
|
49
|
+
if (this.isConnected) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
// Ensure directories exist
|
|
54
|
+
this.ensureDirectories();
|
|
55
|
+
// Initialize or load cache
|
|
56
|
+
await this.loadCache();
|
|
57
|
+
this.isConnected = true;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
61
|
+
throw new Error(`Failed to connect to memory backend: ${errorMessage}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Disconnect from the memory backend (cleanup)
|
|
66
|
+
*/
|
|
67
|
+
async disconnect() {
|
|
68
|
+
this.isConnected = false;
|
|
69
|
+
this.cache = null;
|
|
70
|
+
// Remove lock file if it exists
|
|
71
|
+
if (existsSync(this.lockPath)) {
|
|
72
|
+
try {
|
|
73
|
+
require('fs').unlinkSync(this.lockPath);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// Ignore lock cleanup errors
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create entities in the memory backend
|
|
82
|
+
*/
|
|
83
|
+
async createEntities(entities) {
|
|
84
|
+
if (!this.isConnected) {
|
|
85
|
+
await this.connect();
|
|
86
|
+
}
|
|
87
|
+
await this.withFileLock(async () => {
|
|
88
|
+
// Create a new index entry for this batch of entities
|
|
89
|
+
const entry = {
|
|
90
|
+
id: this.generateId(),
|
|
91
|
+
timestamp: new Date().toISOString(),
|
|
92
|
+
project: this.extractProjectFromEntities(entities),
|
|
93
|
+
session: this.generateSessionId(),
|
|
94
|
+
summary: this.generateSummaryFromEntities(entities),
|
|
95
|
+
nodes: entities.map(e => e.name),
|
|
96
|
+
keywords: this.extractKeywords(entities),
|
|
97
|
+
relations: [],
|
|
98
|
+
entities: entities.map(e => ({
|
|
99
|
+
name: e.name,
|
|
100
|
+
type: e.entityType,
|
|
101
|
+
observations: e.observations
|
|
102
|
+
}))
|
|
103
|
+
};
|
|
104
|
+
// Append to JSONL file
|
|
105
|
+
this.appendToIndex(entry);
|
|
106
|
+
// Update cache
|
|
107
|
+
entities.forEach(entity => {
|
|
108
|
+
this.cache.entities.set(entity.name, entity);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Create relations in the memory backend
|
|
114
|
+
*/
|
|
115
|
+
async createRelations(relations) {
|
|
116
|
+
if (!this.isConnected) {
|
|
117
|
+
await this.connect();
|
|
118
|
+
}
|
|
119
|
+
await this.withFileLock(async () => {
|
|
120
|
+
// Create a new index entry for this batch of relations
|
|
121
|
+
const entry = {
|
|
122
|
+
id: this.generateId(),
|
|
123
|
+
timestamp: new Date().toISOString(),
|
|
124
|
+
project: this.extractProjectFromRelations(relations),
|
|
125
|
+
session: this.generateSessionId(),
|
|
126
|
+
summary: this.generateSummaryFromRelations(relations),
|
|
127
|
+
nodes: this.getUniqueNodesFromRelations(relations),
|
|
128
|
+
keywords: this.extractKeywordsFromRelations(relations),
|
|
129
|
+
relations: relations.map(r => ({
|
|
130
|
+
from: r.from,
|
|
131
|
+
to: r.to,
|
|
132
|
+
type: r.relationType
|
|
133
|
+
})),
|
|
134
|
+
entities: []
|
|
135
|
+
};
|
|
136
|
+
// Append to JSONL file
|
|
137
|
+
this.appendToIndex(entry);
|
|
138
|
+
// Update cache
|
|
139
|
+
relations.forEach(relation => {
|
|
140
|
+
const key = `${relation.from}->${relation.to}:${relation.relationType}`;
|
|
141
|
+
this.cache.relations.set(key, relation);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Search nodes using text-based matching
|
|
147
|
+
*/
|
|
148
|
+
async searchNodes(query) {
|
|
149
|
+
if (!this.isConnected) {
|
|
150
|
+
await this.connect();
|
|
151
|
+
}
|
|
152
|
+
await this.ensureCacheLoaded();
|
|
153
|
+
const results = {
|
|
154
|
+
entities: [],
|
|
155
|
+
relations: []
|
|
156
|
+
};
|
|
157
|
+
const queryLower = query.toLowerCase();
|
|
158
|
+
const isEntityTypeQuery = /^[A-Z][a-z]+_/.test(query);
|
|
159
|
+
// Search entities
|
|
160
|
+
for (const [name, entity] of this.cache.entities) {
|
|
161
|
+
if (this.matchesEntity(entity, queryLower, isEntityTypeQuery)) {
|
|
162
|
+
results.entities.push({
|
|
163
|
+
name: entity.name,
|
|
164
|
+
entityType: entity.entityType,
|
|
165
|
+
observations: entity.observations
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Search relations (unless it's a specific entity type search)
|
|
170
|
+
if (!isEntityTypeQuery) {
|
|
171
|
+
for (const [key, relation] of this.cache.relations) {
|
|
172
|
+
if (this.matchesRelation(relation, queryLower)) {
|
|
173
|
+
results.relations.push({
|
|
174
|
+
from: relation.from,
|
|
175
|
+
to: relation.to,
|
|
176
|
+
relationType: relation.relationType
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Sort results by relevance (exact matches first)
|
|
182
|
+
results.entities = results.entities.sort((a, b) => {
|
|
183
|
+
const aExact = a.name.toLowerCase().includes(queryLower) ? 0 : 1;
|
|
184
|
+
const bExact = b.name.toLowerCase().includes(queryLower) ? 0 : 1;
|
|
185
|
+
return aExact - bExact;
|
|
186
|
+
});
|
|
187
|
+
return results;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Open specific nodes by exact name matching
|
|
191
|
+
*/
|
|
192
|
+
async openNodes(names) {
|
|
193
|
+
if (!this.isConnected) {
|
|
194
|
+
await this.connect();
|
|
195
|
+
}
|
|
196
|
+
await this.ensureCacheLoaded();
|
|
197
|
+
const results = {
|
|
198
|
+
entities: [],
|
|
199
|
+
relations: []
|
|
200
|
+
};
|
|
201
|
+
// Find exact entity matches
|
|
202
|
+
for (const name of names) {
|
|
203
|
+
const entity = this.cache.entities.get(name);
|
|
204
|
+
if (entity) {
|
|
205
|
+
results.entities.push({
|
|
206
|
+
name: entity.name,
|
|
207
|
+
entityType: entity.entityType,
|
|
208
|
+
observations: entity.observations
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
// Find relations involving this entity
|
|
212
|
+
for (const [key, relation] of this.cache.relations) {
|
|
213
|
+
if (relation.from === name || relation.to === name) {
|
|
214
|
+
results.relations.push({
|
|
215
|
+
from: relation.from,
|
|
216
|
+
to: relation.to,
|
|
217
|
+
relationType: relation.relationType
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return results;
|
|
223
|
+
}
|
|
224
|
+
// =============================================================================
|
|
225
|
+
// PRIVATE HELPER METHODS
|
|
226
|
+
// =============================================================================
|
|
227
|
+
/**
|
|
228
|
+
* Ensure required directories exist
|
|
229
|
+
*/
|
|
230
|
+
ensureDirectories() {
|
|
231
|
+
if (!existsSync(this.configDir)) {
|
|
232
|
+
mkdirSync(this.configDir, { recursive: true });
|
|
233
|
+
}
|
|
234
|
+
if (!existsSync(this.indexDir)) {
|
|
235
|
+
mkdirSync(this.indexDir, { recursive: true });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Load cache from JSONL files
|
|
240
|
+
*/
|
|
241
|
+
async loadCache() {
|
|
242
|
+
this.cache = {
|
|
243
|
+
entities: new Map(),
|
|
244
|
+
relations: new Map(),
|
|
245
|
+
lastModified: 0
|
|
246
|
+
};
|
|
247
|
+
if (!existsSync(this.indexPath)) {
|
|
248
|
+
return; // No index file yet
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const content = readFileSync(this.indexPath, 'utf-8');
|
|
252
|
+
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
253
|
+
for (const line of lines) {
|
|
254
|
+
try {
|
|
255
|
+
const entry = JSON.parse(line);
|
|
256
|
+
// Load entities
|
|
257
|
+
entry.entities?.forEach(entity => {
|
|
258
|
+
this.cache.entities.set(entity.name, {
|
|
259
|
+
name: entity.name,
|
|
260
|
+
entityType: entity.type,
|
|
261
|
+
observations: entity.observations
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
// Load relations
|
|
265
|
+
entry.relations?.forEach(relation => {
|
|
266
|
+
const key = `${relation.from}->${relation.to}:${relation.type}`;
|
|
267
|
+
this.cache.relations.set(key, {
|
|
268
|
+
from: relation.from,
|
|
269
|
+
to: relation.to,
|
|
270
|
+
relationType: relation.type
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
// Skip malformed lines
|
|
276
|
+
console.warn(`Skipping malformed index line: ${line}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Update last modified time
|
|
280
|
+
const stats = require('fs').statSync(this.indexPath);
|
|
281
|
+
this.cache.lastModified = stats.mtime.getTime();
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
285
|
+
console.warn(`Failed to load memory cache: ${errorMessage}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Ensure cache is loaded and up to date
|
|
290
|
+
*/
|
|
291
|
+
async ensureCacheLoaded() {
|
|
292
|
+
if (!this.cache) {
|
|
293
|
+
await this.loadCache();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
// Check if index file has been modified
|
|
297
|
+
if (existsSync(this.indexPath)) {
|
|
298
|
+
const stats = require('fs').statSync(this.indexPath);
|
|
299
|
+
if (stats.mtime.getTime() > this.cache.lastModified) {
|
|
300
|
+
await this.loadCache();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Execute operation with file locking
|
|
306
|
+
*/
|
|
307
|
+
async withFileLock(operation) {
|
|
308
|
+
const maxRetries = 5;
|
|
309
|
+
const retryDelay = 100;
|
|
310
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
311
|
+
try {
|
|
312
|
+
// Check for existing lock
|
|
313
|
+
if (existsSync(this.lockPath)) {
|
|
314
|
+
if (attempt === maxRetries) {
|
|
315
|
+
throw new Error('File is locked by another process');
|
|
316
|
+
}
|
|
317
|
+
await this.sleep(retryDelay * attempt);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
// Create lock
|
|
321
|
+
writeFileSync(this.lockPath, process.pid.toString());
|
|
322
|
+
try {
|
|
323
|
+
return await operation();
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
// Remove lock
|
|
327
|
+
if (existsSync(this.lockPath)) {
|
|
328
|
+
require('fs').unlinkSync(this.lockPath);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
if (attempt === maxRetries) {
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
await this.sleep(retryDelay * attempt);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
throw new Error('Failed to acquire file lock');
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Append entry to JSONL index file
|
|
343
|
+
*/
|
|
344
|
+
appendToIndex(entry) {
|
|
345
|
+
const line = JSON.stringify(entry) + '\n';
|
|
346
|
+
if (existsSync(this.indexPath)) {
|
|
347
|
+
require('fs').appendFileSync(this.indexPath, line, 'utf-8');
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
writeFileSync(this.indexPath, line, 'utf-8');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Check if entity matches search query
|
|
355
|
+
*/
|
|
356
|
+
matchesEntity(entity, queryLower, isEntityTypeQuery) {
|
|
357
|
+
if (isEntityTypeQuery) {
|
|
358
|
+
// For entity type queries like "Component_", match the entityType
|
|
359
|
+
const type = queryLower.replace('_', '').toLowerCase();
|
|
360
|
+
return entity.entityType.toLowerCase() === type;
|
|
361
|
+
}
|
|
362
|
+
// General text search
|
|
363
|
+
const searchableText = [
|
|
364
|
+
entity.name,
|
|
365
|
+
entity.entityType,
|
|
366
|
+
...entity.observations
|
|
367
|
+
].join(' ').toLowerCase();
|
|
368
|
+
return searchableText.includes(queryLower);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Check if relation matches search query
|
|
372
|
+
*/
|
|
373
|
+
matchesRelation(relation, queryLower) {
|
|
374
|
+
const searchableText = [
|
|
375
|
+
relation.from,
|
|
376
|
+
relation.to,
|
|
377
|
+
relation.relationType
|
|
378
|
+
].join(' ').toLowerCase();
|
|
379
|
+
return searchableText.includes(queryLower);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Generate unique ID for index entries
|
|
383
|
+
*/
|
|
384
|
+
generateId() {
|
|
385
|
+
return `mem_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Generate session ID (simplified)
|
|
389
|
+
*/
|
|
390
|
+
generateSessionId() {
|
|
391
|
+
return `session_${Date.now()}`;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Extract project name from entities
|
|
395
|
+
*/
|
|
396
|
+
extractProjectFromEntities(entities) {
|
|
397
|
+
if (entities.length === 0)
|
|
398
|
+
return 'unknown';
|
|
399
|
+
// Extract project from entity name format: project_entityType_name
|
|
400
|
+
const parts = entities[0].name.split('_');
|
|
401
|
+
return parts.length > 0 ? parts[0] : 'unknown';
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Extract project name from relations
|
|
405
|
+
*/
|
|
406
|
+
extractProjectFromRelations(relations) {
|
|
407
|
+
if (relations.length === 0)
|
|
408
|
+
return 'unknown';
|
|
409
|
+
const parts = relations[0].from.split('_');
|
|
410
|
+
return parts.length > 0 ? parts[0] : 'unknown';
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Generate summary from entities
|
|
414
|
+
*/
|
|
415
|
+
generateSummaryFromEntities(entities) {
|
|
416
|
+
if (entities.length === 1) {
|
|
417
|
+
return `Created ${entities[0].entityType}: ${entities[0].name.split('_').pop()}`;
|
|
418
|
+
}
|
|
419
|
+
return `Created ${entities.length} ${entities[0].entityType} entities`;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Generate summary from relations
|
|
423
|
+
*/
|
|
424
|
+
generateSummaryFromRelations(relations) {
|
|
425
|
+
if (relations.length === 1) {
|
|
426
|
+
const rel = relations[0];
|
|
427
|
+
const fromName = rel.from.split('_').pop();
|
|
428
|
+
const toName = rel.to.split('_').pop();
|
|
429
|
+
return `${fromName} ${rel.relationType} ${toName}`;
|
|
430
|
+
}
|
|
431
|
+
return `Created ${relations.length} relations`;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Extract keywords from entities
|
|
435
|
+
*/
|
|
436
|
+
extractKeywords(entities) {
|
|
437
|
+
const keywords = new Set();
|
|
438
|
+
entities.forEach(entity => {
|
|
439
|
+
keywords.add(entity.entityType);
|
|
440
|
+
keywords.add(entity.name.split('_').pop() || entity.name);
|
|
441
|
+
entity.observations.forEach(obs => {
|
|
442
|
+
// Extract key terms from observations
|
|
443
|
+
const words = obs.split(/\s+/)
|
|
444
|
+
.filter(word => word.length > 3)
|
|
445
|
+
.slice(0, 3); // Limit to first 3 significant words
|
|
446
|
+
words.forEach(word => keywords.add(word.toLowerCase()));
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
return Array.from(keywords).slice(0, 10); // Limit total keywords
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Extract keywords from relations
|
|
453
|
+
*/
|
|
454
|
+
extractKeywordsFromRelations(relations) {
|
|
455
|
+
const keywords = new Set();
|
|
456
|
+
relations.forEach(relation => {
|
|
457
|
+
keywords.add(relation.relationType);
|
|
458
|
+
keywords.add(relation.from.split('_').pop() || relation.from);
|
|
459
|
+
keywords.add(relation.to.split('_').pop() || relation.to);
|
|
460
|
+
});
|
|
461
|
+
return Array.from(keywords);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Get unique nodes from relations
|
|
465
|
+
*/
|
|
466
|
+
getUniqueNodesFromRelations(relations) {
|
|
467
|
+
const nodes = new Set();
|
|
468
|
+
relations.forEach(relation => {
|
|
469
|
+
nodes.add(relation.from);
|
|
470
|
+
nodes.add(relation.to);
|
|
471
|
+
});
|
|
472
|
+
return Array.from(nodes);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Utility method for async delays
|
|
476
|
+
*/
|
|
477
|
+
sleep(ms) {
|
|
478
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Factory function to create MemoryMCPClient instance
|
|
483
|
+
*/
|
|
484
|
+
export function createMemoryMCPClient() {
|
|
485
|
+
return MemoryMCPClient.getInstance();
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Default export for convenience
|
|
489
|
+
*/
|
|
490
|
+
export default MemoryMCPClient;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded Weaviate Adapter for claude-mem
|
|
3
|
+
*
|
|
4
|
+
* This adapter provides a direct interface to embedded Weaviate using weaviate-ts-embedded,
|
|
5
|
+
* eliminating external dependencies and simplifying deployment.
|
|
6
|
+
*
|
|
7
|
+
* Key Features:
|
|
8
|
+
* - 100% backward compatibility with existing MCPClient interface
|
|
9
|
+
* - Embedded Weaviate instance (no external server required)
|
|
10
|
+
* - Maps entities to Weaviate objects with searchable text generation
|
|
11
|
+
* - Maps relations to separate Weaviate collection
|
|
12
|
+
* - Hybrid search (semantic + keyword) for better search quality
|
|
13
|
+
* - Automatic instance management (start/stop)
|
|
14
|
+
* - Retry logic with exponential backoff
|
|
15
|
+
*/
|
|
16
|
+
import { IMCPClient, MCPEntity, MCPRelation, MCPSearchResult } from '../types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Interface matching the existing MCPClient for 100% compatibility
|
|
19
|
+
*/
|
|
20
|
+
export interface IWeaviateMCPAdapter extends IMCPClient {
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* WeaviateMCPAdapter - Direct interface to embedded Weaviate
|
|
24
|
+
*
|
|
25
|
+
* This adapter maintains 100% API compatibility with the existing MCPClient
|
|
26
|
+
* while providing superior search capabilities through embedded Weaviate's hybrid search.
|
|
27
|
+
*/
|
|
28
|
+
export declare class WeaviateMCPAdapter implements IMCPClient {
|
|
29
|
+
private isConnected;
|
|
30
|
+
private client;
|
|
31
|
+
private maxRetries;
|
|
32
|
+
private retryDelay;
|
|
33
|
+
constructor();
|
|
34
|
+
/**
|
|
35
|
+
* Connect to embedded Weaviate
|
|
36
|
+
*/
|
|
37
|
+
connect(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Disconnect from embedded Weaviate
|
|
40
|
+
*/
|
|
41
|
+
disconnect(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Create entities in Weaviate using the weaviate-insert-one tool
|
|
44
|
+
*/
|
|
45
|
+
createEntities(entities: MCPEntity[]): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Create relations in Weaviate using the weaviate-insert-one tool
|
|
48
|
+
*/
|
|
49
|
+
createRelations(relations: MCPRelation[]): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Search nodes using Weaviate's hybrid search capabilities
|
|
52
|
+
*
|
|
53
|
+
* This method detects entity type prefix searches (e.g., "Component_") and
|
|
54
|
+
* uses appropriate search strategies for optimal results.
|
|
55
|
+
*/
|
|
56
|
+
searchNodes(query: string): Promise<MCPSearchResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Open specific nodes by exact name matching
|
|
59
|
+
*/
|
|
60
|
+
openNodes(names: string[]): Promise<MCPSearchResult>;
|
|
61
|
+
/**
|
|
62
|
+
* Ensure required Weaviate collections exist
|
|
63
|
+
*/
|
|
64
|
+
private ensureCollections;
|
|
65
|
+
/**
|
|
66
|
+
* Generate searchable text for an entity to improve hybrid search
|
|
67
|
+
*/
|
|
68
|
+
private generateEntitySearchText;
|
|
69
|
+
/**
|
|
70
|
+
* Generate searchable text for a relation to improve hybrid search
|
|
71
|
+
*/
|
|
72
|
+
private generateRelationSearchText;
|
|
73
|
+
/**
|
|
74
|
+
* Enhance query based on search type for better results
|
|
75
|
+
*/
|
|
76
|
+
private enhanceQuery;
|
|
77
|
+
/**
|
|
78
|
+
* Format Weaviate results to match the memory MCP structure
|
|
79
|
+
*/
|
|
80
|
+
private formatSearchResults;
|
|
81
|
+
/**
|
|
82
|
+
* Call a Weaviate MCP tool with error handling and retries
|
|
83
|
+
*/
|
|
84
|
+
private callTool;
|
|
85
|
+
/**
|
|
86
|
+
* Execute actual Weaviate operations using the embedded client
|
|
87
|
+
*/
|
|
88
|
+
private executeWeaviateOperation;
|
|
89
|
+
/**
|
|
90
|
+
* Utility method for async delays
|
|
91
|
+
*/
|
|
92
|
+
private sleep;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Factory function to create WeaviateMCPAdapter instance
|
|
96
|
+
* Provides clean interface for creating embedded Weaviate adapters
|
|
97
|
+
*/
|
|
98
|
+
export declare function createWeaviateMCPAdapter(): IMCPClient;
|
|
99
|
+
/**
|
|
100
|
+
* Default export for convenience
|
|
101
|
+
*/
|
|
102
|
+
export default WeaviateMCPAdapter;
|