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,587 @@
|
|
|
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 weaviate, { EmbeddedOptions } from 'weaviate-ts-embedded';
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// WEAVIATE MCP ADAPTER IMPLEMENTATION
|
|
19
|
+
// =============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* WeaviateMCPAdapter - Direct interface to embedded Weaviate
|
|
22
|
+
*
|
|
23
|
+
* This adapter maintains 100% API compatibility with the existing MCPClient
|
|
24
|
+
* while providing superior search capabilities through embedded Weaviate's hybrid search.
|
|
25
|
+
*/
|
|
26
|
+
export class WeaviateMCPAdapter {
|
|
27
|
+
isConnected = false;
|
|
28
|
+
client = null;
|
|
29
|
+
maxRetries = 3;
|
|
30
|
+
retryDelay = 1000; // 1 second base delay
|
|
31
|
+
constructor() {
|
|
32
|
+
// Initialize embedded Weaviate client with proper options
|
|
33
|
+
// The EmbeddedOptions() constructor sets up the embedded instance configuration
|
|
34
|
+
const embeddedOptions = new EmbeddedOptions({
|
|
35
|
+
version: '1.32.5', // Use specific version for stability
|
|
36
|
+
port: 6666 // Default embedded port
|
|
37
|
+
});
|
|
38
|
+
this.client = weaviate.client(embeddedOptions);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Connect to embedded Weaviate
|
|
42
|
+
*/
|
|
43
|
+
async connect() {
|
|
44
|
+
if (this.isConnected) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let lastError = null;
|
|
48
|
+
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
|
49
|
+
try {
|
|
50
|
+
// Start embedded Weaviate instance - weaviate-ts-embedded handles all setup
|
|
51
|
+
await this.client.embedded.start();
|
|
52
|
+
// Wait a moment for the embedded instance to fully initialize
|
|
53
|
+
await this.sleep(1000);
|
|
54
|
+
// Verify connection with a simple ready check
|
|
55
|
+
await this.client.misc.readyChecker().do();
|
|
56
|
+
// Ensure required collections exist
|
|
57
|
+
await this.ensureCollections();
|
|
58
|
+
this.isConnected = true;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
lastError = error;
|
|
63
|
+
if (attempt < this.maxRetries) {
|
|
64
|
+
const delay = this.retryDelay * Math.pow(2, attempt - 1);
|
|
65
|
+
await this.sleep(delay);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Failed to connect to embedded Weaviate after ${this.maxRetries} attempts: ${lastError?.message}`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Disconnect from embedded Weaviate
|
|
73
|
+
*/
|
|
74
|
+
async disconnect() {
|
|
75
|
+
this.isConnected = false;
|
|
76
|
+
if (this.client) {
|
|
77
|
+
try {
|
|
78
|
+
await this.client.embedded.stop();
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// Ignore errors during shutdown
|
|
82
|
+
}
|
|
83
|
+
// Don't null the client - keep it for reconnection
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Create entities in Weaviate using the weaviate-insert-one tool
|
|
88
|
+
*/
|
|
89
|
+
async createEntities(entities) {
|
|
90
|
+
if (!this.isConnected) {
|
|
91
|
+
await this.connect();
|
|
92
|
+
}
|
|
93
|
+
for (const entity of entities) {
|
|
94
|
+
const weaviateObject = {
|
|
95
|
+
collection: 'ClaudeMemEntities',
|
|
96
|
+
properties: {
|
|
97
|
+
name: entity.name,
|
|
98
|
+
entityType: entity.entityType,
|
|
99
|
+
observations: entity.observations,
|
|
100
|
+
_searchText: this.generateEntitySearchText(entity),
|
|
101
|
+
_timestamp: new Date().toISOString()
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
await this.callTool('insert-entity', {
|
|
105
|
+
properties: weaviateObject.properties
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Create relations in Weaviate using the weaviate-insert-one tool
|
|
111
|
+
*/
|
|
112
|
+
async createRelations(relations) {
|
|
113
|
+
if (!this.isConnected) {
|
|
114
|
+
await this.connect();
|
|
115
|
+
}
|
|
116
|
+
for (const relation of relations) {
|
|
117
|
+
const weaviateObject = {
|
|
118
|
+
collection: 'ClaudeMemRelations',
|
|
119
|
+
properties: {
|
|
120
|
+
from: relation.from,
|
|
121
|
+
to: relation.to,
|
|
122
|
+
relationType: relation.relationType,
|
|
123
|
+
_searchText: this.generateRelationSearchText(relation),
|
|
124
|
+
_timestamp: new Date().toISOString()
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
await this.callTool('insert-relation', {
|
|
128
|
+
properties: weaviateObject.properties
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Search nodes using Weaviate's hybrid search capabilities
|
|
134
|
+
*
|
|
135
|
+
* This method detects entity type prefix searches (e.g., "Component_") and
|
|
136
|
+
* uses appropriate search strategies for optimal results.
|
|
137
|
+
*/
|
|
138
|
+
async searchNodes(query) {
|
|
139
|
+
if (!this.isConnected) {
|
|
140
|
+
await this.connect();
|
|
141
|
+
}
|
|
142
|
+
// Detect entity type prefix searches for better targeting
|
|
143
|
+
const isTypeSearch = /^[A-Z][a-z]+_/.test(query);
|
|
144
|
+
const enhancedQuery = this.enhanceQuery(query, isTypeSearch);
|
|
145
|
+
// Search entities using enhanced query
|
|
146
|
+
const entityResults = await this.callTool('query-entities', {
|
|
147
|
+
query: enhancedQuery,
|
|
148
|
+
targetProperties: ['name', 'entityType', 'observations', '_searchText'],
|
|
149
|
+
hybridSearch: true,
|
|
150
|
+
limit: 50
|
|
151
|
+
});
|
|
152
|
+
// Search relations (if not a specific entity type search)
|
|
153
|
+
let relationResults = { data: [] };
|
|
154
|
+
if (!isTypeSearch) {
|
|
155
|
+
relationResults = await this.callTool('query-relations', {
|
|
156
|
+
query: enhancedQuery,
|
|
157
|
+
targetProperties: ['from', 'to', 'relationType', '_searchText'],
|
|
158
|
+
hybridSearch: true,
|
|
159
|
+
limit: 25
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return this.formatSearchResults(entityResults, relationResults);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Open specific nodes by exact name matching
|
|
166
|
+
*/
|
|
167
|
+
async openNodes(names) {
|
|
168
|
+
if (!this.isConnected) {
|
|
169
|
+
await this.connect();
|
|
170
|
+
}
|
|
171
|
+
const entities = [];
|
|
172
|
+
const relations = [];
|
|
173
|
+
// Fetch each entity by exact name
|
|
174
|
+
for (const name of names) {
|
|
175
|
+
const entityResult = await this.callTool('query-entities', {
|
|
176
|
+
query: `"${name}"`, // Exact match query
|
|
177
|
+
targetProperties: ['name', 'entityType', 'observations'],
|
|
178
|
+
limit: 1
|
|
179
|
+
});
|
|
180
|
+
if (entityResult.data && entityResult.data.length > 0) {
|
|
181
|
+
entities.push(...entityResult.data);
|
|
182
|
+
}
|
|
183
|
+
// Find relations involving this entity (from and to searches combined)
|
|
184
|
+
const relationResults = await this.callTool('query-relations', {
|
|
185
|
+
query: `"${name}"`, // Searches both from and to fields
|
|
186
|
+
targetProperties: ['from', 'to', 'relationType'],
|
|
187
|
+
limit: 20
|
|
188
|
+
});
|
|
189
|
+
if (relationResults.data)
|
|
190
|
+
relations.push(...relationResults.data);
|
|
191
|
+
}
|
|
192
|
+
return this.formatSearchResults({ data: entities }, { data: relations });
|
|
193
|
+
}
|
|
194
|
+
// =============================================================================
|
|
195
|
+
// PRIVATE HELPER METHODS
|
|
196
|
+
// =============================================================================
|
|
197
|
+
/**
|
|
198
|
+
* Ensure required Weaviate collections exist
|
|
199
|
+
*/
|
|
200
|
+
async ensureCollections() {
|
|
201
|
+
try {
|
|
202
|
+
// Create entities collection
|
|
203
|
+
await this.callTool('create-collection', {
|
|
204
|
+
name: 'ClaudeMemEntities',
|
|
205
|
+
properties: [
|
|
206
|
+
{
|
|
207
|
+
name: 'name',
|
|
208
|
+
dataType: ['text'],
|
|
209
|
+
indexInverted: true,
|
|
210
|
+
tokenization: 'field'
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'entityType',
|
|
214
|
+
dataType: ['text'],
|
|
215
|
+
indexInverted: true,
|
|
216
|
+
tokenization: 'field'
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: 'observations',
|
|
220
|
+
dataType: ['text[]'],
|
|
221
|
+
indexInverted: true,
|
|
222
|
+
tokenization: 'word'
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: '_searchText',
|
|
226
|
+
dataType: ['text'],
|
|
227
|
+
indexInverted: true,
|
|
228
|
+
tokenization: 'word'
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: '_timestamp',
|
|
232
|
+
dataType: ['date']
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
});
|
|
236
|
+
// Create relations collection
|
|
237
|
+
await this.callTool('create-collection', {
|
|
238
|
+
name: 'ClaudeMemRelations',
|
|
239
|
+
properties: [
|
|
240
|
+
{
|
|
241
|
+
name: 'from',
|
|
242
|
+
dataType: ['text'],
|
|
243
|
+
indexInverted: true,
|
|
244
|
+
tokenization: 'field'
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: 'to',
|
|
248
|
+
dataType: ['text'],
|
|
249
|
+
indexInverted: true,
|
|
250
|
+
tokenization: 'field'
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'relationType',
|
|
254
|
+
dataType: ['text'],
|
|
255
|
+
indexInverted: true,
|
|
256
|
+
tokenization: 'field'
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: '_searchText',
|
|
260
|
+
dataType: ['text'],
|
|
261
|
+
indexInverted: true,
|
|
262
|
+
tokenization: 'word'
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: '_timestamp',
|
|
266
|
+
dataType: ['date']
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
// Collections might already exist, which is fine
|
|
273
|
+
// In a real implementation, we'd check for specific error types
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Generate searchable text for an entity to improve hybrid search
|
|
278
|
+
*/
|
|
279
|
+
generateEntitySearchText(entity) {
|
|
280
|
+
const parts = [
|
|
281
|
+
entity.name,
|
|
282
|
+
entity.entityType,
|
|
283
|
+
...entity.observations,
|
|
284
|
+
// Extract keywords from entity name (e.g., "claude_mem_Component_Auth" -> "Component Auth")
|
|
285
|
+
entity.name.split('_').slice(2).join(' '),
|
|
286
|
+
// Extract type from name for better categorization
|
|
287
|
+
entity.name.includes('_') ? entity.name.split('_')[2] : ''
|
|
288
|
+
];
|
|
289
|
+
return parts.filter(Boolean).join(' ').toLowerCase();
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Generate searchable text for a relation to improve hybrid search
|
|
293
|
+
*/
|
|
294
|
+
generateRelationSearchText(relation) {
|
|
295
|
+
const fromParts = relation.from.split('_').slice(2).join(' ');
|
|
296
|
+
const toParts = relation.to.split('_').slice(2).join(' ');
|
|
297
|
+
return [
|
|
298
|
+
fromParts,
|
|
299
|
+
relation.relationType,
|
|
300
|
+
toParts,
|
|
301
|
+
relation.from,
|
|
302
|
+
relation.to
|
|
303
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Enhance query based on search type for better results
|
|
307
|
+
*/
|
|
308
|
+
enhanceQuery(query, isTypeSearch) {
|
|
309
|
+
if (isTypeSearch) {
|
|
310
|
+
// For entity type searches like "Component_", search the entityType field
|
|
311
|
+
const type = query.replace('_', '').toLowerCase();
|
|
312
|
+
return `entityType:${type}`;
|
|
313
|
+
}
|
|
314
|
+
if (query.endsWith('_')) {
|
|
315
|
+
// Handle partial type searches
|
|
316
|
+
const type = query.slice(0, -1).toLowerCase();
|
|
317
|
+
return `entityType:${type}*`;
|
|
318
|
+
}
|
|
319
|
+
// For general searches, let Weaviate's hybrid search handle it
|
|
320
|
+
return query;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Format Weaviate results to match the memory MCP structure
|
|
324
|
+
*/
|
|
325
|
+
formatSearchResults(entityResults, relationResults) {
|
|
326
|
+
const entities = (entityResults.data || []).map(item => ({
|
|
327
|
+
type: 'entity',
|
|
328
|
+
name: item.properties.name,
|
|
329
|
+
entityType: item.properties.entityType,
|
|
330
|
+
observations: item.properties.observations
|
|
331
|
+
}));
|
|
332
|
+
const relations = (relationResults?.data || []).map(item => ({
|
|
333
|
+
type: 'relation',
|
|
334
|
+
from: item.properties.from,
|
|
335
|
+
to: item.properties.to,
|
|
336
|
+
relationType: item.properties.relationType
|
|
337
|
+
}));
|
|
338
|
+
return { entities, relations };
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Call a Weaviate MCP tool with error handling and retries
|
|
342
|
+
*/
|
|
343
|
+
async callTool(tool, parameters) {
|
|
344
|
+
let lastError = null;
|
|
345
|
+
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
|
346
|
+
try {
|
|
347
|
+
// Execute the actual Weaviate operation
|
|
348
|
+
return await this.executeWeaviateOperation(tool, parameters);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
lastError = error;
|
|
352
|
+
if (attempt < this.maxRetries) {
|
|
353
|
+
const delay = this.retryDelay * Math.pow(2, attempt - 1);
|
|
354
|
+
await this.sleep(delay);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
throw new Error(`Tool call ${tool} failed after ${this.maxRetries} attempts: ${lastError?.message}`);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Execute actual Weaviate operations using the embedded client
|
|
362
|
+
*/
|
|
363
|
+
async executeWeaviateOperation(operation, parameters) {
|
|
364
|
+
if (!this.client) {
|
|
365
|
+
throw new Error('Weaviate client not initialized');
|
|
366
|
+
}
|
|
367
|
+
switch (operation) {
|
|
368
|
+
case 'insert-entity': {
|
|
369
|
+
const { properties } = parameters;
|
|
370
|
+
// Direct Weaviate client insert for entities
|
|
371
|
+
const result = await this.client
|
|
372
|
+
.data
|
|
373
|
+
.creator()
|
|
374
|
+
.withClassName('ClaudeMemEntities')
|
|
375
|
+
.withProperties(properties)
|
|
376
|
+
.do();
|
|
377
|
+
return { success: true, id: result };
|
|
378
|
+
}
|
|
379
|
+
case 'insert-relation': {
|
|
380
|
+
const { properties } = parameters;
|
|
381
|
+
// Direct Weaviate client insert for relations
|
|
382
|
+
const result = await this.client
|
|
383
|
+
.data
|
|
384
|
+
.creator()
|
|
385
|
+
.withClassName('ClaudeMemRelations')
|
|
386
|
+
.withProperties(properties)
|
|
387
|
+
.do();
|
|
388
|
+
return { success: true, id: result };
|
|
389
|
+
}
|
|
390
|
+
case 'query-entities': {
|
|
391
|
+
const { query, targetProperties, limit = 10 } = parameters;
|
|
392
|
+
// Use REST API instead of GraphQL for better compatibility
|
|
393
|
+
try {
|
|
394
|
+
// Build REST API query for entities
|
|
395
|
+
let whereFilter = null;
|
|
396
|
+
if (query) {
|
|
397
|
+
if (query.includes(':')) {
|
|
398
|
+
// Field-specific search
|
|
399
|
+
const [field, value] = query.split(':', 2);
|
|
400
|
+
whereFilter = {
|
|
401
|
+
path: [field],
|
|
402
|
+
operator: 'Like',
|
|
403
|
+
valueText: `*${value.replace('*', '')}*`
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
else if (query.startsWith('"') && query.endsWith('"')) {
|
|
407
|
+
// Exact match search
|
|
408
|
+
const exactValue = query.slice(1, -1);
|
|
409
|
+
whereFilter = {
|
|
410
|
+
path: ['name'],
|
|
411
|
+
operator: 'Equal',
|
|
412
|
+
valueText: exactValue
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
// General text search
|
|
417
|
+
whereFilter = {
|
|
418
|
+
path: ['_searchText'],
|
|
419
|
+
operator: 'Like',
|
|
420
|
+
valueText: `*${query}*`
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Use the getter API to retrieve all entities, then filter in memory
|
|
425
|
+
// This is a simplified approach that avoids the GraphQL endpoint issues
|
|
426
|
+
const result = await this.client.data
|
|
427
|
+
.getter()
|
|
428
|
+
.withClassName('ClaudeMemEntities')
|
|
429
|
+
.withLimit(limit * 2) // Get more to allow for filtering
|
|
430
|
+
.do();
|
|
431
|
+
let filteredObjects = result.objects || [];
|
|
432
|
+
// Apply filtering logic in memory if query is provided
|
|
433
|
+
if (query && filteredObjects.length > 0) {
|
|
434
|
+
if (query.includes(':')) {
|
|
435
|
+
// Field-specific search
|
|
436
|
+
const [field, value] = query.split(':', 2);
|
|
437
|
+
const searchValue = value.replace('*', '').toLowerCase();
|
|
438
|
+
filteredObjects = filteredObjects.filter((obj) => {
|
|
439
|
+
const fieldValue = obj.properties[field];
|
|
440
|
+
return fieldValue && fieldValue.toString().toLowerCase().includes(searchValue);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
else if (query.startsWith('"') && query.endsWith('"')) {
|
|
444
|
+
// Exact match search
|
|
445
|
+
const exactValue = query.slice(1, -1);
|
|
446
|
+
filteredObjects = filteredObjects.filter((obj) => obj.properties.name === exactValue);
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
// General text search in _searchText field
|
|
450
|
+
const searchValue = query.toLowerCase();
|
|
451
|
+
filteredObjects = filteredObjects.filter((obj) => {
|
|
452
|
+
const searchText = obj.properties._searchText;
|
|
453
|
+
return searchText && searchText.toLowerCase().includes(searchValue);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// Apply limit after filtering
|
|
458
|
+
filteredObjects = filteredObjects.slice(0, limit);
|
|
459
|
+
// Transform results to expected format
|
|
460
|
+
return {
|
|
461
|
+
data: filteredObjects.map((item) => ({
|
|
462
|
+
properties: item.properties
|
|
463
|
+
}))
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
console.error('Entity query error:', error);
|
|
468
|
+
return { data: [] };
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
case 'query-relations': {
|
|
472
|
+
const { query, targetProperties, limit = 10 } = parameters;
|
|
473
|
+
// Use REST API instead of GraphQL for better compatibility
|
|
474
|
+
try {
|
|
475
|
+
// Build REST API query for relations
|
|
476
|
+
let whereFilter = null;
|
|
477
|
+
if (query) {
|
|
478
|
+
if (query.startsWith('"') && query.endsWith('"')) {
|
|
479
|
+
// Exact match - search in both from and to fields
|
|
480
|
+
const exactValue = query.slice(1, -1);
|
|
481
|
+
whereFilter = {
|
|
482
|
+
operator: 'Or',
|
|
483
|
+
operands: [
|
|
484
|
+
{
|
|
485
|
+
path: ['from'],
|
|
486
|
+
operator: 'Equal',
|
|
487
|
+
valueText: exactValue
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
path: ['to'],
|
|
491
|
+
operator: 'Equal',
|
|
492
|
+
valueText: exactValue
|
|
493
|
+
}
|
|
494
|
+
]
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
// General search in searchable text
|
|
499
|
+
whereFilter = {
|
|
500
|
+
path: ['_searchText'],
|
|
501
|
+
operator: 'Like',
|
|
502
|
+
valueText: `*${query}*`
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Use the getter API to retrieve all relations, then filter in memory
|
|
507
|
+
const result = await this.client.data
|
|
508
|
+
.getter()
|
|
509
|
+
.withClassName('ClaudeMemRelations')
|
|
510
|
+
.withLimit(limit * 2) // Get more to allow for filtering
|
|
511
|
+
.do();
|
|
512
|
+
let filteredObjects = result.objects || [];
|
|
513
|
+
// Apply filtering logic in memory if query is provided
|
|
514
|
+
if (query && filteredObjects.length > 0) {
|
|
515
|
+
if (query.startsWith('"') && query.endsWith('"')) {
|
|
516
|
+
// Exact match - search in both from and to fields
|
|
517
|
+
const exactValue = query.slice(1, -1);
|
|
518
|
+
filteredObjects = filteredObjects.filter((obj) => obj.properties.from === exactValue || obj.properties.to === exactValue);
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
// General search in searchable text
|
|
522
|
+
const searchValue = query.toLowerCase();
|
|
523
|
+
filteredObjects = filteredObjects.filter((obj) => {
|
|
524
|
+
const searchText = obj.properties._searchText;
|
|
525
|
+
return searchText && searchText.toLowerCase().includes(searchValue);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// Apply limit after filtering
|
|
530
|
+
filteredObjects = filteredObjects.slice(0, limit);
|
|
531
|
+
// Transform results to expected format
|
|
532
|
+
return {
|
|
533
|
+
data: filteredObjects.map((item) => ({
|
|
534
|
+
properties: item.properties
|
|
535
|
+
}))
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
console.error('Relation query error:', error);
|
|
540
|
+
return { data: [] };
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
case 'create-collection': {
|
|
544
|
+
const { name, properties } = parameters;
|
|
545
|
+
try {
|
|
546
|
+
// Check if collection already exists
|
|
547
|
+
const existing = await this.client.schema.classGetter().withClassName(name).do();
|
|
548
|
+
if (existing) {
|
|
549
|
+
return { success: true, collection: name, existed: true };
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
// Collection doesn't exist, which is expected for new setups
|
|
554
|
+
}
|
|
555
|
+
// Create collection schema
|
|
556
|
+
const classObj = {
|
|
557
|
+
class: name,
|
|
558
|
+
properties: properties || []
|
|
559
|
+
};
|
|
560
|
+
await this.client.schema.classCreator().withClass(classObj).do();
|
|
561
|
+
return { success: true, collection: name };
|
|
562
|
+
}
|
|
563
|
+
default:
|
|
564
|
+
throw new Error(`Unknown Weaviate operation: ${operation}`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Utility method for async delays
|
|
569
|
+
*/
|
|
570
|
+
sleep(ms) {
|
|
571
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// =============================================================================
|
|
575
|
+
// EXPORTS
|
|
576
|
+
// =============================================================================
|
|
577
|
+
/**
|
|
578
|
+
* Factory function to create WeaviateMCPAdapter instance
|
|
579
|
+
* Provides clean interface for creating embedded Weaviate adapters
|
|
580
|
+
*/
|
|
581
|
+
export function createWeaviateMCPAdapter() {
|
|
582
|
+
return new WeaviateMCPAdapter();
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Default export for convenience
|
|
586
|
+
*/
|
|
587
|
+
export default WeaviateMCPAdapter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "Memory compression system for Claude Code - persist context across sessions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"files": [
|
|
33
33
|
"claude-mem",
|
|
34
34
|
"hooks",
|
|
35
|
-
"
|
|
35
|
+
"dist",
|
|
36
|
+
".mcp.json"
|
|
36
37
|
]
|
|
37
38
|
}
|