collective-memory-mcp 0.3.0 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "collective-memory-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "A persistent, graph-based memory system for AI agents (MCP Server)",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
package/src/server.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  /**
2
3
  * Collective Memory MCP Server
3
4
  * A persistent, graph-based memory system for AI agents.
package/src/storage.js CHANGED
@@ -4,120 +4,81 @@
4
4
  */
5
5
 
6
6
  import { promises as fs } from "fs";
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
7
8
  import path from "path";
8
9
  import os from "os";
9
- import { createHash } from "crypto";
10
10
  import { Entity, Relation } from "./models.js";
11
11
 
12
12
  const DB_DIR = path.join(os.homedir(), ".collective-memory");
13
13
  const DB_PATH = path.join(DB_DIR, "memory.json");
14
14
 
15
15
  /**
16
- * Lock file to prevent concurrent writes
17
- */
18
- const LOCK_FILE = path.join(DB_DIR, "memory.lock");
19
-
20
- /**
21
- * Simple file-based storage with locking
16
+ * Simple file-based storage
22
17
  */
23
18
  export class Storage {
24
19
  constructor(dbPath = DB_PATH) {
25
20
  this.dbPath = dbPath;
26
- this.lockPath = LOCK_FILE;
27
21
  this.data = null;
28
- this.lockRetries = 10;
29
- this.lockDelay = 50;
22
+ // Initialize synchronously
30
23
  this.init();
31
24
  }
32
25
 
33
26
  /**
34
- * Initialize storage
35
- */
36
- async init() {
37
- await this.ensureDir();
38
- await this.load();
39
- }
40
-
41
- /**
42
- * Ensure data directory exists
27
+ * Initialize storage (synchronous)
43
28
  */
44
- async ensureDir() {
29
+ init() {
45
30
  try {
46
- await fs.mkdir(path.dirname(this.dbPath), { recursive: true });
47
- } catch {
48
- // Ignore if exists
49
- }
50
- }
51
-
52
- /**
53
- * Load data from file
54
- */
55
- async load() {
56
- try {
57
- const content = await fs.readFile(this.dbPath, "utf-8");
58
- this.data = JSON.parse(content);
59
- } catch {
60
- // File doesn't exist or is invalid - create new
31
+ // Ensure directory exists
32
+ const dir = path.dirname(this.dbPath);
33
+ mkdirSync(dir, { recursive: true });
34
+
35
+ // Try to load existing data
36
+ if (existsSync(this.dbPath)) {
37
+ const content = readFileSync(this.dbPath, "utf-8");
38
+ this.data = JSON.parse(content);
39
+ } else {
40
+ // Create new empty data
41
+ this.data = {
42
+ entities: {},
43
+ relations: [],
44
+ version: "1.0",
45
+ };
46
+ this.saveSync();
47
+ }
48
+ } catch (error) {
49
+ // If anything fails, start with empty data
61
50
  this.data = {
62
51
  entities: {},
63
52
  relations: [],
64
53
  version: "1.0",
65
54
  };
66
- await this.save();
67
55
  }
68
56
  }
69
57
 
70
58
  /**
71
- * Save data to file
72
- */
73
- async save() {
74
- const dir = path.dirname(this.dbPath);
75
- await fs.mkdir(dir, { recursive: true });
76
- const tempPath = path.join(dir, ".memory.tmp");
77
- await fs.writeFile(tempPath, JSON.stringify(this.data, null, 2), "utf-8");
78
- await fs.rename(tempPath, this.dbPath);
79
- }
80
-
81
- /**
82
- * Acquire lock
59
+ * Save data synchronously
83
60
  */
84
- async acquireLock() {
85
- for (let i = 0; i < this.lockRetries; i++) {
86
- try {
87
- await fs.writeFile(
88
- this.lockPath,
89
- process.pid.toString(),
90
- { flag: "wx" }
91
- );
92
- return;
93
- } catch {
94
- await new Promise(r => setTimeout(r, this.lockDelay));
95
- }
61
+ saveSync() {
62
+ try {
63
+ const dir = path.dirname(this.dbPath);
64
+ mkdirSync(dir, { recursive: true });
65
+ writeFileSync(this.dbPath, JSON.stringify(this.data, null, 2), "utf-8");
66
+ } catch (error) {
67
+ console.error("Failed to save:", error.message);
96
68
  }
97
- // If we can't get lock, just proceed (it's a simple file lock)
98
69
  }
99
70
 
100
71
  /**
101
- * Release lock
72
+ * Save data asynchronously
102
73
  */
103
- async releaseLock() {
74
+ async save() {
75
+ const dir = path.dirname(this.dbPath);
104
76
  try {
105
- await fs.unlink(this.lockPath);
77
+ await fs.mkdir(dir, { recursive: true });
106
78
  } catch {
107
79
  // Ignore
108
80
  }
109
- }
110
-
111
- /**
112
- * Execute operation with lock
113
- */
114
- async withLock(fn) {
115
- await this.acquireLock();
116
- try {
117
- return await fn();
118
- } finally {
119
- await this.releaseLock();
120
- }
81
+ await fs.writeFile(this.dbPath, JSON.stringify(this.data, null, 2), "utf-8");
121
82
  }
122
83
 
123
84
  // ========== Entity Operations ==========
@@ -126,14 +87,12 @@ export class Storage {
126
87
  * Create a new entity. Returns true if created, false if duplicate.
127
88
  */
128
89
  async createEntity(entity) {
129
- return this.withLock(async () => {
130
- if (this.data.entities[entity.name]) {
131
- return false;
132
- }
133
- this.data.entities[entity.name] = entity.toJSON();
134
- await this.save();
135
- return true;
136
- });
90
+ if (this.data.entities[entity.name]) {
91
+ return false;
92
+ }
93
+ this.data.entities[entity.name] = entity.toJSON();
94
+ await this.save();
95
+ return true;
137
96
  }
138
97
 
139
98
  /**
@@ -165,41 +124,37 @@ export class Storage {
165
124
  * Update entity
166
125
  */
167
126
  async updateEntity(name, { observations, metadata } = {}) {
168
- return this.withLock(async () => {
169
- const entity = this.data.entities[name];
170
- if (!entity) return false;
127
+ const entity = this.data.entities[name];
128
+ if (!entity) return false;
171
129
 
172
- if (observations !== undefined) {
173
- entity.observations = observations;
174
- }
175
- if (metadata !== undefined) {
176
- entity.metadata = metadata;
177
- }
130
+ if (observations !== undefined) {
131
+ entity.observations = observations;
132
+ }
133
+ if (metadata !== undefined) {
134
+ entity.metadata = metadata;
135
+ }
178
136
 
179
- await this.save();
180
- return true;
181
- });
137
+ await this.save();
138
+ return true;
182
139
  }
183
140
 
184
141
  /**
185
142
  * Delete an entity and its relations
186
143
  */
187
144
  async deleteEntity(name) {
188
- return this.withLock(async () => {
189
- if (!this.data.entities[name]) {
190
- return false;
191
- }
145
+ if (!this.data.entities[name]) {
146
+ return false;
147
+ }
192
148
 
193
- delete this.data.entities[name];
149
+ delete this.data.entities[name];
194
150
 
195
- // Remove relations involving this entity
196
- this.data.relations = this.data.relations.filter(
197
- r => r.from !== name && r.to !== name
198
- );
151
+ // Remove relations involving this entity
152
+ this.data.relations = this.data.relations.filter(
153
+ r => r.from !== name && r.to !== name
154
+ );
199
155
 
200
- await this.save();
201
- return true;
202
- });
156
+ await this.save();
157
+ return true;
203
158
  }
204
159
 
205
160
  /**
@@ -219,16 +174,14 @@ export class Storage {
219
174
  * Create a new relation. Returns true if created, false if duplicate.
220
175
  */
221
176
  async createRelation(relation) {
222
- return this.withLock(async () => {
223
- const key = this.relationKey(relation.from, relation.to, relation.relationType);
224
- if (this.data.relations.some(r => this.relationKey(r.from, r.to, r.relationType) === key)) {
225
- return false;
226
- }
177
+ const key = this.relationKey(relation.from, relation.to, relation.relationType);
178
+ if (this.data.relations.some(r => this.relationKey(r.from, r.to, r.relationType) === key)) {
179
+ return false;
180
+ }
227
181
 
228
- this.data.relations.push(relation.toJSON());
229
- await this.save();
230
- return true;
231
- });
182
+ this.data.relations.push(relation.toJSON());
183
+ await this.save();
184
+ return true;
232
185
  }
233
186
 
234
187
  /**
@@ -277,18 +230,16 @@ export class Storage {
277
230
  * Delete a specific relation
278
231
  */
279
232
  async deleteRelation(fromEntity, toEntity, relationType) {
280
- return this.withLock(async () => {
281
- const before = this.data.relations.length;
282
- this.data.relations = this.data.relations.filter(
283
- r => !(r.from === fromEntity && r.to === toEntity && r.relationType === relationType)
284
- );
285
-
286
- if (this.data.relations.length < before) {
287
- await this.save();
288
- return true;
289
- }
290
- return false;
291
- });
233
+ const before = this.data.relations.length;
234
+ this.data.relations = this.data.relations.filter(
235
+ r => !(r.from === fromEntity && r.to === toEntity && r.relationType === relationType)
236
+ );
237
+
238
+ if (this.data.relations.length < before) {
239
+ await this.save();
240
+ return true;
241
+ }
242
+ return false;
292
243
  }
293
244
 
294
245
  /**
@@ -354,89 +305,12 @@ export class Storage {
354
305
  }
355
306
  }
356
307
 
357
- // Sync wrapper for async operations
308
+ // Singleton instance
358
309
  let storageInstance = null;
359
310
 
360
311
  export function getStorage(dbPath) {
361
312
  if (!storageInstance) {
362
313
  storageInstance = new Storage(dbPath);
363
- // Initialize asynchronously but don't wait
364
- storageInstance.init().catch(console.error);
365
314
  }
366
315
  return storageInstance;
367
316
  }
368
-
369
- // Synchronous methods for compatibility
370
- export class SyncStorage {
371
- constructor(dbPath = DB_PATH) {
372
- this.storage = new Storage(dbPath);
373
- }
374
-
375
- async init() {
376
- await this.storage.init();
377
- }
378
-
379
- createEntity(entity) {
380
- return this.storage.createEntity(entity);
381
- }
382
-
383
- getEntity(name) {
384
- return this.storage.getEntity(name);
385
- }
386
-
387
- getAllEntities() {
388
- return this.storage.getAllEntities();
389
- }
390
-
391
- entityExists(name) {
392
- return this.storage.entityExists(name);
393
- }
394
-
395
- updateEntity(name, data) {
396
- return this.storage.updateEntity(name, data);
397
- }
398
-
399
- deleteEntity(name) {
400
- return this.storage.deleteEntity(name);
401
- }
402
-
403
- deleteEntities(names) {
404
- return this.storage.deleteEntities(names);
405
- }
406
-
407
- createRelation(relation) {
408
- return this.storage.createRelation(relation);
409
- }
410
-
411
- getRelations(filters) {
412
- return this.storage.getRelations(filters);
413
- }
414
-
415
- getAllRelations() {
416
- return this.storage.getAllRelations();
417
- }
418
-
419
- relationExists(from, to, type) {
420
- return this.storage.relationExists(from, to, type);
421
- }
422
-
423
- deleteRelation(from, to, type) {
424
- return this.storage.deleteRelation(from, to, type);
425
- }
426
-
427
- deleteRelations(relations) {
428
- return this.storage.deleteRelations(relations);
429
- }
430
-
431
- searchEntities(query) {
432
- return this.storage.searchEntities(query);
433
- }
434
-
435
- getRelatedEntities(name) {
436
- return this.storage.getRelatedEntities(name);
437
- }
438
-
439
- close() {
440
- return this.storage.close();
441
- }
442
- }