@cohaku/mcp 0.1.0

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.
Files changed (2) hide show
  1. package/dist/index.js +1380 -0
  2. package/package.json +51 -0
package/dist/index.js ADDED
@@ -0,0 +1,1380 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { join } from "path";
5
+ import { homedir } from "os";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+
8
+ // ../core/dist/storage/sqlite-driver.js
9
+ import Database from "better-sqlite3";
10
+ import * as sqliteVec from "sqlite-vec";
11
+
12
+ // ../shared/dist/constants.js
13
+ var DEFAULT_WEIGHTS = {
14
+ semantic: 0.4,
15
+ bm25: 0.25,
16
+ recency: 0.2,
17
+ salience: 0.15
18
+ };
19
+ var MEMORY_LAYER_PRIORITY = {
20
+ rule: 10,
21
+ working: 5,
22
+ long_term: 1
23
+ };
24
+ var DEFAULT_DECAY_RATE = 0.01;
25
+ var DEFAULT_EMBEDDING_DIMENSIONS = 384;
26
+
27
+ // ../core/dist/storage/schema.js
28
+ var SCHEMA_VERSION = 1;
29
+ var CREATE_TABLES = `
30
+ -- Memory (3-layer)
31
+ CREATE TABLE IF NOT EXISTS memories (
32
+ id TEXT PRIMARY KEY,
33
+ content TEXT NOT NULL,
34
+ layer TEXT NOT NULL DEFAULT 'long_term',
35
+ type TEXT NOT NULL DEFAULT 'note',
36
+ priority INTEGER NOT NULL DEFAULT 1,
37
+ importance REAL NOT NULL DEFAULT 0.5,
38
+ tags TEXT,
39
+ expires_at TEXT,
40
+ last_accessed_at TEXT NOT NULL,
41
+ created_at TEXT NOT NULL,
42
+ updated_at TEXT NOT NULL,
43
+ deleted_at TEXT
44
+ );
45
+
46
+ -- FTS5 virtual table (BM25 search)
47
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
48
+ content,
49
+ tags,
50
+ content='memories',
51
+ content_rowid='rowid',
52
+ tokenize='porter'
53
+ );
54
+
55
+ -- FTS5 sync triggers
56
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
57
+ INSERT INTO memories_fts(rowid, content, tags)
58
+ VALUES (new.rowid, new.content, new.tags);
59
+ END;
60
+
61
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
62
+ INSERT INTO memories_fts(memories_fts, rowid, content, tags)
63
+ VALUES ('delete', old.rowid, old.content, old.tags);
64
+ END;
65
+
66
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
67
+ INSERT INTO memories_fts(memories_fts, rowid, content, tags)
68
+ VALUES ('delete', old.rowid, old.content, old.tags);
69
+ INSERT INTO memories_fts(rowid, content, tags)
70
+ VALUES (new.rowid, new.content, new.tags);
71
+ END;
72
+
73
+ -- Entity nodes
74
+ CREATE TABLE IF NOT EXISTS nodes (
75
+ id TEXT PRIMARY KEY,
76
+ name TEXT NOT NULL,
77
+ name_embedding BLOB,
78
+ summary TEXT,
79
+ entity_type TEXT,
80
+ created_at TEXT NOT NULL,
81
+ updated_at TEXT NOT NULL
82
+ );
83
+
84
+ -- Edges (bi-temporal)
85
+ CREATE TABLE IF NOT EXISTS edges (
86
+ id TEXT PRIMARY KEY,
87
+ source_id TEXT NOT NULL REFERENCES nodes(id),
88
+ target_id TEXT NOT NULL REFERENCES nodes(id),
89
+ relation TEXT NOT NULL,
90
+ fact TEXT,
91
+ fact_embedding BLOB,
92
+ created_at TEXT NOT NULL,
93
+ expired_at TEXT,
94
+ valid_at TEXT,
95
+ invalid_at TEXT
96
+ );
97
+
98
+ -- Episodes
99
+ CREATE TABLE IF NOT EXISTS episodes (
100
+ id TEXT PRIMARY KEY,
101
+ content TEXT NOT NULL,
102
+ content_type TEXT,
103
+ source TEXT,
104
+ created_at TEXT NOT NULL
105
+ );
106
+
107
+ -- Episode references
108
+ CREATE TABLE IF NOT EXISTS episode_refs (
109
+ episode_id TEXT REFERENCES episodes(id),
110
+ ref_type TEXT,
111
+ ref_id TEXT,
112
+ PRIMARY KEY (episode_id, ref_type, ref_id)
113
+ );
114
+
115
+ -- Sessions
116
+ CREATE TABLE IF NOT EXISTS sessions (
117
+ id TEXT PRIMARY KEY,
118
+ status TEXT NOT NULL DEFAULT 'active',
119
+ started_at TEXT NOT NULL,
120
+ ended_at TEXT,
121
+ checkpoint_count INTEGER NOT NULL DEFAULT 0,
122
+ working_memory_snapshot TEXT,
123
+ metadata TEXT
124
+ );
125
+
126
+ -- Config
127
+ CREATE TABLE IF NOT EXISTS config (
128
+ key TEXT PRIMARY KEY,
129
+ value TEXT NOT NULL
130
+ );
131
+
132
+ -- Indexes
133
+ CREATE INDEX IF NOT EXISTS idx_memories_layer ON memories(layer);
134
+ CREATE INDEX IF NOT EXISTS idx_memories_deleted ON memories(deleted_at);
135
+ CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
136
+ CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
137
+ CREATE INDEX IF NOT EXISTS idx_edges_expired ON edges(expired_at);
138
+ CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
139
+ `;
140
+ function createVecTables(dimensions) {
141
+ return `
142
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(
143
+ memory_id TEXT PRIMARY KEY,
144
+ embedding float[${dimensions}]
145
+ );
146
+
147
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_nodes USING vec0(
148
+ node_id TEXT PRIMARY KEY,
149
+ embedding float[${dimensions}]
150
+ );
151
+
152
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_edges USING vec0(
153
+ edge_id TEXT PRIMARY KEY,
154
+ embedding float[${dimensions}]
155
+ );
156
+ `;
157
+ }
158
+
159
+ // ../core/dist/utils/temporal.js
160
+ function nowISO() {
161
+ return (/* @__PURE__ */ new Date()).toISOString();
162
+ }
163
+ function toVecBlob(embedding) {
164
+ return Buffer.from(new Float32Array(embedding).buffer);
165
+ }
166
+
167
+ // ../core/dist/storage/sqlite-driver.js
168
+ var SqliteDriver = class {
169
+ dbPath;
170
+ dimensions;
171
+ db;
172
+ constructor(dbPath2, dimensions = DEFAULT_EMBEDDING_DIMENSIONS) {
173
+ this.dbPath = dbPath2;
174
+ this.dimensions = dimensions;
175
+ }
176
+ async initialize() {
177
+ this.db = new Database(this.dbPath);
178
+ this.db.pragma("journal_mode = WAL");
179
+ this.db.pragma("foreign_keys = ON");
180
+ sqliteVec.load(this.db);
181
+ this.db.exec(CREATE_TABLES);
182
+ const vecSQL = createVecTables(this.dimensions);
183
+ for (const stmt of vecSQL.split(";").filter((s) => s.trim())) {
184
+ this.db.exec(stmt);
185
+ }
186
+ const existing = this.db.prepare("SELECT value FROM config WHERE key = ?").get("schema_version");
187
+ if (!existing) {
188
+ this.db.prepare("INSERT INTO config (key, value) VALUES (?, ?)").run("schema_version", String(SCHEMA_VERSION));
189
+ }
190
+ }
191
+ close() {
192
+ this.db.close();
193
+ }
194
+ // ── Memory ──
195
+ async addMemory(memory) {
196
+ const tagsStr = memory.tags ? JSON.stringify(memory.tags) : null;
197
+ this.db.transaction(() => {
198
+ this.db.prepare(`
199
+ INSERT INTO memories (id, content, layer, type, priority, importance, tags, expires_at, last_accessed_at, created_at, updated_at, deleted_at)
200
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
201
+ `).run(memory.id, memory.content, memory.layer, memory.type, memory.priority, memory.importance, tagsStr, memory.expiresAt ?? null, memory.lastAccessedAt, memory.createdAt, memory.updatedAt, memory.deletedAt ?? null);
202
+ if (memory.embedding) {
203
+ this.db.prepare(`
204
+ INSERT INTO vec_memories (memory_id, embedding) VALUES (?, ?)
205
+ `).run(memory.id, toVecBlob(memory.embedding));
206
+ }
207
+ })();
208
+ }
209
+ async getMemory(id) {
210
+ const row = this.db.prepare("SELECT * FROM memories WHERE id = ? AND deleted_at IS NULL").get(id);
211
+ return row ? this.rowToMemory(row) : null;
212
+ }
213
+ async updateMemory(id, input) {
214
+ const sets = ["updated_at = ?"];
215
+ const values = [(/* @__PURE__ */ new Date()).toISOString()];
216
+ if (input.content !== void 0) {
217
+ sets.push("content = ?");
218
+ values.push(input.content);
219
+ }
220
+ if (input.layer !== void 0) {
221
+ sets.push("layer = ?");
222
+ values.push(input.layer);
223
+ }
224
+ if (input.type !== void 0) {
225
+ sets.push("type = ?");
226
+ values.push(input.type);
227
+ }
228
+ if (input.importance !== void 0) {
229
+ sets.push("importance = ?");
230
+ values.push(input.importance);
231
+ }
232
+ if (input.tags !== void 0) {
233
+ sets.push("tags = ?");
234
+ values.push(JSON.stringify(input.tags));
235
+ }
236
+ if (input.expiresAt !== void 0) {
237
+ sets.push("expires_at = ?");
238
+ values.push(input.expiresAt);
239
+ }
240
+ values.push(id);
241
+ this.db.prepare(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`).run(...values);
242
+ }
243
+ async updateMemoryEmbedding(id, embedding) {
244
+ this.db.transaction(() => {
245
+ this.db.prepare("DELETE FROM vec_memories WHERE memory_id = ?").run(id);
246
+ this.db.prepare("INSERT INTO vec_memories (memory_id, embedding) VALUES (?, ?)").run(id, toVecBlob(embedding));
247
+ })();
248
+ }
249
+ async deleteMemory(id) {
250
+ this.db.prepare("UPDATE memories SET deleted_at = ? WHERE id = ?").run((/* @__PURE__ */ new Date()).toISOString(), id);
251
+ }
252
+ async searchMemories(embedding, limit) {
253
+ const rows = this.db.prepare(`
254
+ SELECT m.*, v.distance
255
+ FROM vec_memories v
256
+ INNER JOIN memories m ON m.id = v.memory_id
257
+ WHERE v.embedding MATCH ?
258
+ AND m.deleted_at IS NULL
259
+ AND k = ?
260
+ `).all(toVecBlob(embedding), limit);
261
+ return rows.map((row) => ({
262
+ memory: this.rowToMemory(row),
263
+ distance: row.distance
264
+ }));
265
+ }
266
+ async fullTextSearch(query, limit) {
267
+ const rows = this.db.prepare(`
268
+ SELECT m.*, fts.rank
269
+ FROM memories_fts fts
270
+ INNER JOIN memories m ON m.rowid = fts.rowid
271
+ WHERE memories_fts MATCH ?
272
+ AND m.deleted_at IS NULL
273
+ ORDER BY fts.rank
274
+ LIMIT ?
275
+ `).all(query, limit);
276
+ return rows.map((row) => ({
277
+ memory: this.rowToMemory(row),
278
+ rank: row.rank
279
+ }));
280
+ }
281
+ async findSimilarMemories(embedding, threshold, limit) {
282
+ const results = await this.searchMemories(embedding, limit * 3);
283
+ return results.filter((r) => r.distance <= threshold).slice(0, limit);
284
+ }
285
+ async listMemoriesByLayer(layer, limit) {
286
+ const now = (/* @__PURE__ */ new Date()).toISOString();
287
+ const rows = this.db.prepare(`
288
+ SELECT * FROM memories
289
+ WHERE layer = ?
290
+ AND deleted_at IS NULL
291
+ AND (expires_at IS NULL OR expires_at > ?)
292
+ ORDER BY updated_at DESC
293
+ LIMIT ?
294
+ `).all(layer, now, limit);
295
+ return rows.map((row) => this.rowToMemory(row));
296
+ }
297
+ // ── Nodes ──
298
+ async upsertNode(node) {
299
+ this.db.transaction(() => {
300
+ this.db.prepare(`
301
+ INSERT INTO nodes (id, name, summary, entity_type, created_at, updated_at)
302
+ VALUES (?, ?, ?, ?, ?, ?)
303
+ ON CONFLICT(id) DO UPDATE SET
304
+ name = excluded.name,
305
+ summary = excluded.summary,
306
+ entity_type = excluded.entity_type,
307
+ updated_at = excluded.updated_at
308
+ `).run(node.id, node.name, node.summary ?? null, node.entityType ?? null, node.createdAt, node.updatedAt);
309
+ if (node.nameEmbedding) {
310
+ this.db.prepare(`
311
+ DELETE FROM vec_nodes WHERE node_id = ?
312
+ `).run(node.id);
313
+ this.db.prepare(`
314
+ INSERT INTO vec_nodes (node_id, embedding) VALUES (?, ?)
315
+ `).run(node.id, toVecBlob(node.nameEmbedding));
316
+ }
317
+ })();
318
+ }
319
+ async getNode(id) {
320
+ const row = this.db.prepare("SELECT * FROM nodes WHERE id = ?").get(id);
321
+ return row ? this.rowToNode(row) : null;
322
+ }
323
+ async searchNodes(embedding, limit) {
324
+ const rows = this.db.prepare(`
325
+ SELECT n.*
326
+ FROM vec_nodes v
327
+ INNER JOIN nodes n ON n.id = v.node_id
328
+ WHERE v.embedding MATCH ?
329
+ AND k = ?
330
+ `).all(toVecBlob(embedding), limit);
331
+ return rows.map((row) => this.rowToNode(row));
332
+ }
333
+ // ── Edges ──
334
+ async upsertEdge(edge) {
335
+ this.db.transaction(() => {
336
+ this.db.prepare(`
337
+ INSERT INTO edges (id, source_id, target_id, relation, fact, created_at, expired_at, valid_at, invalid_at)
338
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
339
+ ON CONFLICT(id) DO UPDATE SET
340
+ relation = excluded.relation,
341
+ fact = excluded.fact,
342
+ expired_at = excluded.expired_at,
343
+ valid_at = excluded.valid_at,
344
+ invalid_at = excluded.invalid_at
345
+ `).run(edge.id, edge.sourceId, edge.targetId, edge.relation, edge.fact ?? null, edge.createdAt, edge.expiredAt ?? null, edge.validAt ?? null, edge.invalidAt ?? null);
346
+ if (edge.factEmbedding) {
347
+ this.db.prepare(`
348
+ DELETE FROM vec_edges WHERE edge_id = ?
349
+ `).run(edge.id);
350
+ this.db.prepare(`
351
+ INSERT INTO vec_edges (edge_id, embedding) VALUES (?, ?)
352
+ `).run(edge.id, toVecBlob(edge.factEmbedding));
353
+ }
354
+ })();
355
+ }
356
+ async getEdge(id) {
357
+ const row = this.db.prepare("SELECT * FROM edges WHERE id = ?").get(id);
358
+ return row ? this.rowToEdge(row) : null;
359
+ }
360
+ async invalidateEdge(id, expiredAt, invalidAt) {
361
+ this.db.prepare("UPDATE edges SET expired_at = ?, invalid_at = ? WHERE id = ?").run(expiredAt, invalidAt, id);
362
+ }
363
+ async searchEdges(embedding, filters, limit) {
364
+ let whereClause = "1=1";
365
+ const params = [toVecBlob(embedding), limit];
366
+ if (filters.excludeExpired !== false) {
367
+ whereClause += " AND e.expired_at IS NULL";
368
+ }
369
+ if (filters.asOf) {
370
+ whereClause += " AND (e.valid_at IS NULL OR e.valid_at <= ?)";
371
+ params.push(filters.asOf);
372
+ whereClause += " AND (e.invalid_at IS NULL OR e.invalid_at > ?)";
373
+ params.push(filters.asOf);
374
+ }
375
+ const rows = this.db.prepare(`
376
+ SELECT e.*
377
+ FROM vec_edges v
378
+ INNER JOIN edges e ON e.id = v.edge_id
379
+ WHERE v.embedding MATCH ?
380
+ AND k = ?
381
+ AND ${whereClause}
382
+ `).all(...params);
383
+ return rows.map((row) => this.rowToEdge(row));
384
+ }
385
+ // ── Graph Traversal ──
386
+ async getEdgesForNode(nodeId) {
387
+ const rows = this.db.prepare(`
388
+ SELECT * FROM edges
389
+ WHERE (source_id = ? OR target_id = ?) AND expired_at IS NULL
390
+ `).all(nodeId, nodeId);
391
+ return rows.map((row) => this.rowToEdge(row));
392
+ }
393
+ async getNeighbors(nodeId, depth) {
394
+ if (depth < 1)
395
+ return [];
396
+ const oneHop = this.db.prepare(`
397
+ SELECT DISTINCT n.* FROM nodes n
398
+ INNER JOIN edges e ON (e.source_id = ? AND e.target_id = n.id)
399
+ OR (e.target_id = ? AND e.source_id = n.id)
400
+ WHERE e.expired_at IS NULL
401
+ `).all(nodeId, nodeId);
402
+ if (depth === 1) {
403
+ return oneHop.map((row) => this.rowToNode(row));
404
+ }
405
+ const visited = /* @__PURE__ */ new Set([nodeId]);
406
+ const results = [];
407
+ for (const row of oneHop) {
408
+ visited.add(row.id);
409
+ results.push(this.rowToNode(row));
410
+ }
411
+ for (const node of [...results]) {
412
+ const twoHop = this.db.prepare(`
413
+ SELECT DISTINCT n.* FROM nodes n
414
+ INNER JOIN edges e ON (e.source_id = ? AND e.target_id = n.id)
415
+ OR (e.target_id = ? AND e.source_id = n.id)
416
+ WHERE e.expired_at IS NULL
417
+ `).all(node.id, node.id);
418
+ for (const row of twoHop) {
419
+ if (!visited.has(row.id)) {
420
+ visited.add(row.id);
421
+ results.push(this.rowToNode(row));
422
+ }
423
+ }
424
+ }
425
+ return results;
426
+ }
427
+ // ── Episodes ──
428
+ async addEpisode(episode) {
429
+ this.db.prepare(`
430
+ INSERT INTO episodes (id, content, content_type, source, created_at)
431
+ VALUES (?, ?, ?, ?, ?)
432
+ `).run(episode.id, episode.content, episode.contentType ?? null, episode.source ?? null, episode.createdAt);
433
+ }
434
+ async addEpisodeRef(ref) {
435
+ this.db.prepare(`
436
+ INSERT OR IGNORE INTO episode_refs (episode_id, ref_type, ref_id)
437
+ VALUES (?, ?, ?)
438
+ `).run(ref.episodeId, ref.refType, ref.refId);
439
+ }
440
+ async getEpisode(id) {
441
+ const row = this.db.prepare("SELECT * FROM episodes WHERE id = ?").get(id);
442
+ return row ? this.rowToEpisode(row) : null;
443
+ }
444
+ async listEpisodes(limit) {
445
+ const rows = this.db.prepare("SELECT * FROM episodes ORDER BY created_at DESC LIMIT ?").all(limit);
446
+ return rows.map((row) => this.rowToEpisode(row));
447
+ }
448
+ async getEpisodeRefs(episodeId) {
449
+ const rows = this.db.prepare("SELECT * FROM episode_refs WHERE episode_id = ?").all(episodeId);
450
+ return rows.map((row) => ({
451
+ episodeId: row.episode_id,
452
+ refType: row.ref_type,
453
+ refId: row.ref_id
454
+ }));
455
+ }
456
+ // ── Sessions ──
457
+ async addSession(session) {
458
+ this.db.prepare(`
459
+ INSERT INTO sessions (id, status, started_at, ended_at, checkpoint_count, working_memory_snapshot, metadata)
460
+ VALUES (?, ?, ?, ?, ?, ?, ?)
461
+ `).run(session.id, session.status, session.startedAt, session.endedAt ?? null, session.checkpointCount, session.workingMemorySnapshot ?? null, session.metadata ?? null);
462
+ }
463
+ async updateSession(id, updates) {
464
+ const sets = [];
465
+ const values = [];
466
+ if (updates.status !== void 0) {
467
+ sets.push("status = ?");
468
+ values.push(updates.status);
469
+ }
470
+ if (updates.endedAt !== void 0) {
471
+ sets.push("ended_at = ?");
472
+ values.push(updates.endedAt);
473
+ }
474
+ if (updates.checkpointCount !== void 0) {
475
+ sets.push("checkpoint_count = ?");
476
+ values.push(updates.checkpointCount);
477
+ }
478
+ if (updates.workingMemorySnapshot !== void 0) {
479
+ sets.push("working_memory_snapshot = ?");
480
+ values.push(updates.workingMemorySnapshot);
481
+ }
482
+ if (updates.metadata !== void 0) {
483
+ sets.push("metadata = ?");
484
+ values.push(updates.metadata);
485
+ }
486
+ if (sets.length === 0)
487
+ return;
488
+ values.push(id);
489
+ this.db.prepare(`UPDATE sessions SET ${sets.join(", ")} WHERE id = ?`).run(...values);
490
+ }
491
+ async getSession(id) {
492
+ const row = this.db.prepare("SELECT * FROM sessions WHERE id = ?").get(id);
493
+ return row ? this.rowToSession(row) : null;
494
+ }
495
+ async listSessions(limit) {
496
+ const rows = this.db.prepare("SELECT * FROM sessions ORDER BY started_at DESC LIMIT ?").all(limit);
497
+ return rows.map((row) => this.rowToSession(row));
498
+ }
499
+ // ── Config ──
500
+ async getConfig(key) {
501
+ const row = this.db.prepare("SELECT value FROM config WHERE key = ?").get(key);
502
+ return row?.value ?? null;
503
+ }
504
+ async setConfig(key, value) {
505
+ this.db.prepare("INSERT INTO config (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
506
+ }
507
+ // ── Row Converters ──
508
+ rowToMemory(row) {
509
+ return {
510
+ id: row.id,
511
+ content: row.content,
512
+ layer: row.layer,
513
+ type: row.type,
514
+ priority: row.priority,
515
+ importance: row.importance,
516
+ tags: row.tags ? JSON.parse(row.tags) : void 0,
517
+ expiresAt: row.expires_at ?? void 0,
518
+ lastAccessedAt: row.last_accessed_at,
519
+ createdAt: row.created_at,
520
+ updatedAt: row.updated_at,
521
+ deletedAt: row.deleted_at ?? void 0
522
+ };
523
+ }
524
+ rowToNode(row) {
525
+ return {
526
+ id: row.id,
527
+ name: row.name,
528
+ summary: row.summary ?? void 0,
529
+ entityType: row.entity_type ?? void 0,
530
+ createdAt: row.created_at,
531
+ updatedAt: row.updated_at
532
+ };
533
+ }
534
+ rowToEdge(row) {
535
+ return {
536
+ id: row.id,
537
+ sourceId: row.source_id,
538
+ targetId: row.target_id,
539
+ relation: row.relation,
540
+ fact: row.fact ?? void 0,
541
+ createdAt: row.created_at,
542
+ expiredAt: row.expired_at ?? void 0,
543
+ validAt: row.valid_at ?? void 0,
544
+ invalidAt: row.invalid_at ?? void 0
545
+ };
546
+ }
547
+ rowToEpisode(row) {
548
+ return {
549
+ id: row.id,
550
+ content: row.content,
551
+ contentType: row.content_type ?? void 0,
552
+ source: row.source ?? void 0,
553
+ createdAt: row.created_at
554
+ };
555
+ }
556
+ rowToSession(row) {
557
+ return {
558
+ id: row.id,
559
+ status: row.status,
560
+ startedAt: row.started_at,
561
+ endedAt: row.ended_at ?? void 0,
562
+ checkpointCount: row.checkpoint_count,
563
+ workingMemorySnapshot: row.working_memory_snapshot ?? void 0,
564
+ metadata: row.metadata ?? void 0
565
+ };
566
+ }
567
+ };
568
+
569
+ // ../core/dist/embedding/local-embedding.js
570
+ var LocalEmbedding = class {
571
+ cacheDir;
572
+ dimensions = 384;
573
+ pipe = null;
574
+ initPromise = null;
575
+ constructor(cacheDir) {
576
+ this.cacheDir = cacheDir;
577
+ }
578
+ async getPipeline() {
579
+ if (this.pipe)
580
+ return this.pipe;
581
+ if (!this.initPromise) {
582
+ this.initPromise = (async () => {
583
+ const { pipeline, env } = await import("@xenova/transformers");
584
+ if (this.cacheDir) {
585
+ env.cacheDir = this.cacheDir;
586
+ }
587
+ const pipe = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
588
+ return pipe;
589
+ })();
590
+ }
591
+ this.pipe = await this.initPromise;
592
+ return this.pipe;
593
+ }
594
+ async embed(text) {
595
+ const pipe = await this.getPipeline();
596
+ const result = await pipe(text, { pooling: "mean", normalize: true });
597
+ return Array.from(result.data).slice(0, this.dimensions);
598
+ }
599
+ async embedBatch(texts) {
600
+ return Promise.all(texts.map((t) => this.embed(t)));
601
+ }
602
+ };
603
+
604
+ // ../core/dist/engine/memory-engine.js
605
+ import { mkdirSync } from "fs";
606
+ import { dirname } from "path";
607
+
608
+ // ../core/dist/utils/id.js
609
+ import { nanoid } from "nanoid";
610
+ function generateId() {
611
+ return nanoid();
612
+ }
613
+
614
+ // ../core/dist/engine/scoring.js
615
+ function decay(importance, lastAccessedAt, rate = 0.01) {
616
+ const ageHours = (Date.now() - new Date(lastAccessedAt).getTime()) / 36e5;
617
+ return importance * Math.exp(-rate * ageHours);
618
+ }
619
+ function recencyScore(timestamp, halfLifeHours = 24) {
620
+ const ageHours = (Date.now() - new Date(timestamp).getTime()) / 36e5;
621
+ return Math.exp(-Math.LN2 / halfLifeHours * ageHours);
622
+ }
623
+ function hybridScore(semantic, bm25, recency, salience, weights) {
624
+ return semantic * weights.semantic + bm25 * weights.bm25 + recency * weights.recency + salience * weights.salience;
625
+ }
626
+ function normalizeBm25(rank) {
627
+ return 1 / (1 + Math.abs(rank));
628
+ }
629
+ function distanceToSimilarity(distance) {
630
+ return 1 / (1 + distance);
631
+ }
632
+
633
+ // ../core/dist/engine/context.js
634
+ async function getContext(storage, options = {}) {
635
+ const { maxMemories = 50 } = options;
636
+ const rules = await storage.listMemoriesByLayer("rule", maxMemories);
637
+ const remaining1 = maxMemories - rules.length;
638
+ const working = remaining1 > 0 ? await storage.listMemoriesByLayer("working", Math.ceil(remaining1 * 0.6)) : [];
639
+ const remaining2 = maxMemories - rules.length - working.length;
640
+ const longTerm = remaining2 > 0 ? await storage.listMemoriesByLayer("long_term", remaining2) : [];
641
+ const memories = [...rules, ...working, ...longTerm];
642
+ const totalCount = memories.length;
643
+ return {
644
+ memories,
645
+ totalCount,
646
+ truncated: totalCount >= maxMemories
647
+ };
648
+ }
649
+
650
+ // ../core/dist/engine/memory-engine.js
651
+ var MemoryEngine = class {
652
+ storage;
653
+ embedding;
654
+ weights;
655
+ decayRate;
656
+ constructor(config) {
657
+ mkdirSync(dirname(config.dbPath), { recursive: true });
658
+ this.storage = new SqliteDriver(config.dbPath);
659
+ this.embedding = new LocalEmbedding(config.embeddingCacheDir);
660
+ this.weights = { ...DEFAULT_WEIGHTS, ...config.weights };
661
+ this.decayRate = config.decayRate ?? DEFAULT_DECAY_RATE;
662
+ }
663
+ async initialize() {
664
+ await this.storage.initialize();
665
+ }
666
+ close() {
667
+ this.storage.close();
668
+ }
669
+ // ── Memory ──
670
+ async addMemory(input) {
671
+ const now = nowISO();
672
+ const layer = input.layer ?? "long_term";
673
+ const memory = {
674
+ id: generateId(),
675
+ content: input.content,
676
+ layer,
677
+ type: input.type ?? "note",
678
+ priority: MEMORY_LAYER_PRIORITY[layer] ?? 1,
679
+ importance: input.importance ?? 0.5,
680
+ tags: input.tags,
681
+ embedding: await this.embedding.embed(input.content),
682
+ expiresAt: input.expiresAt,
683
+ lastAccessedAt: now,
684
+ createdAt: now,
685
+ updatedAt: now
686
+ };
687
+ await this.storage.addMemory(memory);
688
+ return memory;
689
+ }
690
+ async searchMemories(query) {
691
+ const limit = query.limit ?? 10;
692
+ const weights = { ...this.weights, ...query.weights };
693
+ const queryEmbedding = query.embedding ?? await this.embedding.embed(query.text);
694
+ const semanticResults = await this.storage.searchMemories(queryEmbedding, limit * 2);
695
+ let bm25Results = [];
696
+ try {
697
+ bm25Results = await this.storage.fullTextSearch(query.text, limit * 2);
698
+ } catch {
699
+ }
700
+ const candidateMap = /* @__PURE__ */ new Map();
701
+ for (const { memory } of semanticResults)
702
+ candidateMap.set(memory.id, memory);
703
+ for (const { memory } of bm25Results)
704
+ candidateMap.set(memory.id, memory);
705
+ const semanticScoreMap = /* @__PURE__ */ new Map();
706
+ for (const { memory, distance } of semanticResults) {
707
+ semanticScoreMap.set(memory.id, distanceToSimilarity(distance));
708
+ }
709
+ const bm25ScoreMap = /* @__PURE__ */ new Map();
710
+ for (const { memory, rank } of bm25Results) {
711
+ bm25ScoreMap.set(memory.id, normalizeBm25(rank));
712
+ }
713
+ const layerFilter = query.layers ? new Set(query.layers) : null;
714
+ const results = [];
715
+ for (const [id, memory] of candidateMap) {
716
+ if (layerFilter && !layerFilter.has(memory.layer))
717
+ continue;
718
+ if (memory.expiresAt && new Date(memory.expiresAt) < /* @__PURE__ */ new Date())
719
+ continue;
720
+ const semantic = semanticScoreMap.get(id) ?? 0;
721
+ const bm25 = bm25ScoreMap.get(id) ?? 0;
722
+ const rec = recencyScore(memory.lastAccessedAt);
723
+ const sal = decay(memory.importance, memory.lastAccessedAt, this.decayRate);
724
+ const score = hybridScore(semantic, bm25, rec, sal, weights);
725
+ results.push({
726
+ id: memory.id,
727
+ type: "memory",
728
+ content: memory.content,
729
+ score,
730
+ scoreBreakdown: { semantic, bm25, recency: rec, salience: sal }
731
+ });
732
+ }
733
+ results.sort((a, b) => b.score - a.score);
734
+ return results.slice(0, limit);
735
+ }
736
+ async updateMemory(id, input) {
737
+ await this.storage.updateMemory(id, input);
738
+ if (input.content !== void 0) {
739
+ const newEmbedding = await this.embedding.embed(input.content);
740
+ await this.storage.updateMemoryEmbedding(id, newEmbedding);
741
+ }
742
+ }
743
+ async deleteMemory(id) {
744
+ await this.storage.deleteMemory(id);
745
+ }
746
+ async getMemory(id) {
747
+ return this.storage.getMemory(id);
748
+ }
749
+ // ── Deduplication & Consolidation ──
750
+ async findDuplicates(memoryId, threshold = 0.3) {
751
+ const target = await this.storage.getMemory(memoryId);
752
+ if (!target)
753
+ throw new Error(`Memory not found: ${memoryId}`);
754
+ const embedding = target.embedding ?? await this.embedding.embed(target.content);
755
+ const similar = await this.storage.findSimilarMemories(embedding, threshold, 20);
756
+ return similar.filter((r) => r.memory.id !== memoryId);
757
+ }
758
+ async consolidateMemories(sourceIds, mergedContent, layer) {
759
+ const merged = await this.addMemory({
760
+ content: mergedContent,
761
+ layer: layer ?? "long_term",
762
+ type: "note"
763
+ });
764
+ for (const id of sourceIds) {
765
+ await this.storage.deleteMemory(id);
766
+ }
767
+ return merged;
768
+ }
769
+ // ── Graph ──
770
+ async addEntity(input) {
771
+ const now = nowISO();
772
+ const node = {
773
+ id: generateId(),
774
+ name: input.name,
775
+ nameEmbedding: await this.embedding.embed(input.name),
776
+ summary: input.summary,
777
+ entityType: input.entityType,
778
+ createdAt: now,
779
+ updatedAt: now
780
+ };
781
+ await this.storage.upsertNode(node);
782
+ return node;
783
+ }
784
+ async addEdge(input) {
785
+ const now = nowISO();
786
+ const edge = {
787
+ id: generateId(),
788
+ sourceId: input.sourceId,
789
+ targetId: input.targetId,
790
+ relation: input.relation,
791
+ fact: input.fact,
792
+ factEmbedding: input.fact ? await this.embedding.embed(input.fact) : void 0,
793
+ createdAt: now,
794
+ validAt: input.validAt
795
+ };
796
+ await this.storage.upsertEdge(edge);
797
+ return edge;
798
+ }
799
+ async getEdge(id) {
800
+ return this.storage.getEdge(id);
801
+ }
802
+ async invalidateEdge(edgeId) {
803
+ const now = nowISO();
804
+ await this.storage.invalidateEdge(edgeId, now, now);
805
+ }
806
+ async updateEdge(edgeId, input) {
807
+ const old = await this.storage.getEdge(edgeId);
808
+ if (!old)
809
+ return null;
810
+ const now = nowISO();
811
+ await this.storage.invalidateEdge(edgeId, now, now);
812
+ return this.addEdge({
813
+ sourceId: old.sourceId,
814
+ targetId: old.targetId,
815
+ relation: input.relation ?? old.relation,
816
+ fact: input.fact ?? old.fact,
817
+ validAt: now
818
+ });
819
+ }
820
+ async searchGraph(query, limit = 10, options) {
821
+ const depth = options?.traverseDepth ?? 1;
822
+ const queryEmbedding = await this.embedding.embed(query);
823
+ const nodes = await this.storage.searchNodes(queryEmbedding, Math.ceil(limit / 2));
824
+ const edges = await this.storage.searchEdges(queryEmbedding, { excludeExpired: true }, Math.ceil(limit / 2));
825
+ const results = [];
826
+ const seenNodeIds = /* @__PURE__ */ new Set();
827
+ for (const node of nodes) {
828
+ seenNodeIds.add(node.id);
829
+ results.push({
830
+ id: node.id,
831
+ type: "node",
832
+ content: `${node.name}${node.summary ? ": " + node.summary : ""}`,
833
+ score: 1
834
+ });
835
+ }
836
+ if (depth > 0) {
837
+ for (const node of nodes) {
838
+ const neighbors = await this.storage.getNeighbors(node.id, depth);
839
+ for (const neighbor of neighbors) {
840
+ if (!seenNodeIds.has(neighbor.id)) {
841
+ seenNodeIds.add(neighbor.id);
842
+ results.push({
843
+ id: neighbor.id,
844
+ type: "node",
845
+ content: `${neighbor.name}${neighbor.summary ? ": " + neighbor.summary : ""}`,
846
+ score: 0.5
847
+ });
848
+ }
849
+ }
850
+ }
851
+ }
852
+ for (const edge of edges) {
853
+ results.push({
854
+ id: edge.id,
855
+ type: "edge",
856
+ content: `${edge.relation}: ${edge.fact ?? ""}`,
857
+ score: 0.8
858
+ });
859
+ }
860
+ results.sort((a, b) => b.score - a.score);
861
+ return results.slice(0, limit);
862
+ }
863
+ // ── Episodes ──
864
+ async addEpisode(input, refs) {
865
+ const episode = {
866
+ id: generateId(),
867
+ content: input.content,
868
+ contentType: input.contentType,
869
+ source: input.source,
870
+ createdAt: nowISO()
871
+ };
872
+ await this.storage.addEpisode(episode);
873
+ if (refs) {
874
+ for (const ref of refs) {
875
+ await this.storage.addEpisodeRef({ ...ref, episodeId: episode.id });
876
+ }
877
+ }
878
+ return episode;
879
+ }
880
+ async getEpisode(id) {
881
+ return this.storage.getEpisode(id);
882
+ }
883
+ async listEpisodes(limit = 20) {
884
+ return this.storage.listEpisodes(limit);
885
+ }
886
+ // ── Sessions ──
887
+ async sessionStart(metadata) {
888
+ const session = {
889
+ id: generateId(),
890
+ status: "active",
891
+ startedAt: nowISO(),
892
+ checkpointCount: 0,
893
+ metadata
894
+ };
895
+ await this.storage.addSession(session);
896
+ return session;
897
+ }
898
+ async sessionEnd(id) {
899
+ await this.storage.updateSession(id, {
900
+ status: "ended",
901
+ endedAt: nowISO()
902
+ });
903
+ }
904
+ async sessionCheckpoint(id) {
905
+ const session = await this.storage.getSession(id);
906
+ if (!session)
907
+ throw new Error(`Session not found: ${id}`);
908
+ await this.storage.updateSession(id, {
909
+ checkpointCount: session.checkpointCount + 1
910
+ });
911
+ }
912
+ async sessionList(limit = 20) {
913
+ return this.storage.listSessions(limit);
914
+ }
915
+ // ── Context ──
916
+ async getContext(options) {
917
+ return getContext(this.storage, options);
918
+ }
919
+ };
920
+
921
+ // src/server.ts
922
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
923
+
924
+ // src/tools/memory.ts
925
+ import { z } from "zod";
926
+
927
+ // src/helpers/tool-helpers.ts
928
+ function formatMemory(memory) {
929
+ const parts = [
930
+ `[${memory.layer}/${memory.type}] ${memory.content}`,
931
+ memory.tags?.length ? `tags: ${memory.tags.join(", ")}` : null,
932
+ memory.expiresAt ? `expires: ${memory.expiresAt}` : null
933
+ ].filter(Boolean);
934
+ return parts.join("\n");
935
+ }
936
+ function formatNode(node) {
937
+ const parts = [
938
+ `[${node.entityType ?? "entity"}] ${node.name}`,
939
+ node.summary ? `summary: ${node.summary}` : null
940
+ ].filter(Boolean);
941
+ return parts.join("\n");
942
+ }
943
+ function formatSearchResults(results) {
944
+ if (results.length === 0) return "No results found";
945
+ return results.map((r, i) => `${i + 1}. [${r.type}] (score: ${r.score.toFixed(3)}) ${r.content}`).join("\n");
946
+ }
947
+ function formatEpisode(episode) {
948
+ const parts = [
949
+ `[${episode.contentType ?? "text"}] ${episode.content.slice(0, 200)}`,
950
+ episode.source ? `source: ${episode.source}` : null,
951
+ `created: ${episode.createdAt}`
952
+ ].filter(Boolean);
953
+ return parts.join("\n");
954
+ }
955
+ function formatSession(session) {
956
+ return `[${session.status}] ${session.id} (started: ${session.startedAt}${session.endedAt ? `, ended: ${session.endedAt}` : ""}, checkpoints: ${session.checkpointCount})`;
957
+ }
958
+ function formatContext(ctx) {
959
+ const header = `Memories: ${ctx.memories.length}${ctx.truncated ? ` (of ${ctx.totalCount} total)` : ""}`;
960
+ const body = ctx.memories.map((m) => formatMemory(m)).join("\n---\n");
961
+ return `${header}
962
+
963
+ ${body}`;
964
+ }
965
+ function textContent(text) {
966
+ return { content: [{ type: "text", text }] };
967
+ }
968
+
969
+ // src/tools/memory.ts
970
+ function registerMemoryTools(server2, engine2) {
971
+ server2.tool(
972
+ "add_memory",
973
+ "Add a memory entry",
974
+ {
975
+ content: z.string().describe("Memory content"),
976
+ layer: z.enum(["rule", "working", "long_term"]).default("long_term").describe("Memory layer"),
977
+ type: z.enum(["rule", "decision", "fact", "note", "skill"]).default("note").describe("Memory type"),
978
+ tags: z.array(z.string()).optional().describe("Tags"),
979
+ expiresAt: z.string().optional().describe("Expiration date (ISO 8601)")
980
+ },
981
+ async (params) => {
982
+ const memory = await engine2.addMemory(params);
983
+ return textContent(`Memory added:
984
+ ${formatMemory(memory)}
985
+ ID: ${memory.id}`);
986
+ }
987
+ );
988
+ server2.tool(
989
+ "search_memories",
990
+ "Search memories (semantic + BM25 hybrid search)",
991
+ {
992
+ query: z.string().describe("Search query"),
993
+ limit: z.number().int().min(1).max(100).default(10).describe("Max results"),
994
+ layers: z.array(z.enum(["rule", "working", "long_term"])).optional().describe("Target layers")
995
+ },
996
+ async (params) => {
997
+ const results = await engine2.searchMemories({
998
+ text: params.query,
999
+ limit: params.limit,
1000
+ layers: params.layers
1001
+ });
1002
+ return textContent(formatSearchResults(results));
1003
+ }
1004
+ );
1005
+ server2.tool(
1006
+ "find_duplicates",
1007
+ "Find duplicate candidates similar to a given memory",
1008
+ {
1009
+ memoryId: z.string().describe("Target memory ID"),
1010
+ threshold: z.number().min(0).max(2).default(0.3).describe("Similarity threshold (distance, lower = more similar)")
1011
+ },
1012
+ async (params) => {
1013
+ const duplicates = await engine2.findDuplicates(params.memoryId, params.threshold);
1014
+ if (duplicates.length === 0) return textContent("No duplicates found");
1015
+ const lines = duplicates.map(
1016
+ (d, i) => `${i + 1}. (distance: ${d.distance.toFixed(3)}) ${formatMemory(d.memory)}
1017
+ ID: ${d.memory.id}`
1018
+ );
1019
+ return textContent(`${duplicates.length} duplicate(s) found:
1020
+ ${lines.join("\n")}`);
1021
+ }
1022
+ );
1023
+ server2.tool(
1024
+ "consolidate_memories",
1025
+ "Consolidate multiple memories into one (source memories are soft-deleted)",
1026
+ {
1027
+ sourceIds: z.array(z.string()).min(2).describe("List of source memory IDs to merge"),
1028
+ mergedContent: z.string().describe("Merged content text"),
1029
+ layer: z.enum(["rule", "working", "long_term"]).optional().describe("Target layer for merged memory")
1030
+ },
1031
+ async (params) => {
1032
+ const merged = await engine2.consolidateMemories(params.sourceIds, params.mergedContent, params.layer);
1033
+ return textContent(`Consolidated ${params.sourceIds.length} memories:
1034
+ ${formatMemory(merged)}
1035
+ ID: ${merged.id}`);
1036
+ }
1037
+ );
1038
+ server2.tool(
1039
+ "update_memory",
1040
+ "Update a memory entry",
1041
+ {
1042
+ id: z.string().describe("Memory ID"),
1043
+ content: z.string().optional().describe("New content"),
1044
+ layer: z.enum(["rule", "working", "long_term"]).optional().describe("New layer"),
1045
+ type: z.enum(["rule", "decision", "fact", "note", "skill"]).optional().describe("New type"),
1046
+ importance: z.number().min(0).max(1).optional().describe("Importance (0-1)"),
1047
+ tags: z.array(z.string()).optional().describe("New tags")
1048
+ },
1049
+ async (params) => {
1050
+ const { id, ...input } = params;
1051
+ await engine2.updateMemory(id, input);
1052
+ return textContent(`Memory ${id} updated`);
1053
+ }
1054
+ );
1055
+ server2.tool(
1056
+ "delete_memory",
1057
+ "Delete a memory (soft delete)",
1058
+ {
1059
+ id: z.string().describe("Memory ID")
1060
+ },
1061
+ async (params) => {
1062
+ await engine2.deleteMemory(params.id);
1063
+ return textContent(`Memory ${params.id} deleted`);
1064
+ }
1065
+ );
1066
+ }
1067
+
1068
+ // src/tools/session.ts
1069
+ import { z as z2 } from "zod";
1070
+ function registerSessionTools(server2, engine2) {
1071
+ server2.tool(
1072
+ "session_start",
1073
+ "Start a new session",
1074
+ {
1075
+ metadata: z2.string().optional().describe("Session metadata (JSON)")
1076
+ },
1077
+ async (params) => {
1078
+ const session = await engine2.sessionStart(params.metadata);
1079
+ return textContent(`Session started:
1080
+ ${formatSession(session)}`);
1081
+ }
1082
+ );
1083
+ server2.tool(
1084
+ "session_end",
1085
+ "End a session",
1086
+ {
1087
+ id: z2.string().describe("Session ID")
1088
+ },
1089
+ async (params) => {
1090
+ await engine2.sessionEnd(params.id);
1091
+ return textContent(`Session ${params.id} ended`);
1092
+ }
1093
+ );
1094
+ server2.tool(
1095
+ "session_checkpoint",
1096
+ "Create a session checkpoint",
1097
+ {
1098
+ id: z2.string().describe("Session ID")
1099
+ },
1100
+ async (params) => {
1101
+ await engine2.sessionCheckpoint(params.id);
1102
+ return textContent(`Checkpoint created for session ${params.id}`);
1103
+ }
1104
+ );
1105
+ server2.tool(
1106
+ "session_list",
1107
+ "List past sessions",
1108
+ {
1109
+ limit: z2.number().int().min(1).max(100).default(20).describe("Max results")
1110
+ },
1111
+ async (params) => {
1112
+ const sessions = await engine2.sessionList(params.limit);
1113
+ if (sessions.length === 0) return textContent("No sessions found");
1114
+ return textContent(sessions.map((s) => formatSession(s)).join("\n"));
1115
+ }
1116
+ );
1117
+ }
1118
+
1119
+ // src/tools/context.ts
1120
+ import { z as z3 } from "zod";
1121
+ function registerContextTools(server2, engine2) {
1122
+ server2.tool(
1123
+ "get_context",
1124
+ "Get current context (priority: rule > working > long_term)",
1125
+ {
1126
+ maxMemories: z3.number().int().min(1).max(200).default(50).describe("Max number of memories")
1127
+ },
1128
+ async (params) => {
1129
+ const ctx = await engine2.getContext({ maxMemories: params.maxMemories });
1130
+ return textContent(formatContext(ctx));
1131
+ }
1132
+ );
1133
+ }
1134
+
1135
+ // src/tools/graph.ts
1136
+ import { z as z4 } from "zod";
1137
+ function registerGraphTools(server2, engine2) {
1138
+ server2.tool(
1139
+ "add_entity",
1140
+ "Add an entity to the knowledge graph",
1141
+ {
1142
+ name: z4.string().describe("Entity name"),
1143
+ summary: z4.string().optional().describe("Summary"),
1144
+ entityType: z4.string().optional().describe("Type (person, project, technology, etc.)")
1145
+ },
1146
+ async (params) => {
1147
+ const node = await engine2.addEntity(params);
1148
+ return textContent(`Entity added:
1149
+ ${formatNode(node)}
1150
+ ID: ${node.id}`);
1151
+ }
1152
+ );
1153
+ server2.tool(
1154
+ "add_edge",
1155
+ "Add an edge (relationship) to the knowledge graph",
1156
+ {
1157
+ sourceId: z4.string().describe("Source node ID"),
1158
+ targetId: z4.string().describe("Target node ID"),
1159
+ relation: z4.string().describe("Relation (works_at, uses, depends_on, etc.)"),
1160
+ fact: z4.string().optional().describe("Fact description"),
1161
+ validAt: z4.string().optional().describe("Valid-from timestamp (ISO 8601)")
1162
+ },
1163
+ async (params) => {
1164
+ const edge = await engine2.addEdge(params);
1165
+ return textContent(`Edge added: ${edge.sourceId} --[${edge.relation}]--> ${edge.targetId}
1166
+ ID: ${edge.id}`);
1167
+ }
1168
+ );
1169
+ server2.tool(
1170
+ "invalidate_edge",
1171
+ "Invalidate an edge (mark as expired while preserving history)",
1172
+ {
1173
+ id: z4.string().describe("Edge ID to invalidate")
1174
+ },
1175
+ async (params) => {
1176
+ await engine2.invalidateEdge(params.id);
1177
+ return textContent(`Edge ${params.id} invalidated`);
1178
+ }
1179
+ );
1180
+ server2.tool(
1181
+ "update_edge",
1182
+ "Update an edge fact (invalidates old, creates new with history preserved)",
1183
+ {
1184
+ id: z4.string().describe("Edge ID to update"),
1185
+ relation: z4.string().optional().describe("New relation"),
1186
+ fact: z4.string().optional().describe("New fact")
1187
+ },
1188
+ async (params) => {
1189
+ const edge = await engine2.updateEdge(params.id, {
1190
+ relation: params.relation,
1191
+ fact: params.fact
1192
+ });
1193
+ if (!edge) return textContent(`Edge ${params.id} not found`);
1194
+ return textContent(`Edge updated: ${edge.sourceId} --[${edge.relation}]--> ${edge.targetId}
1195
+ New ID: ${edge.id}`);
1196
+ }
1197
+ );
1198
+ server2.tool(
1199
+ "search_graph",
1200
+ "Search the knowledge graph (semantic search + graph traversal)",
1201
+ {
1202
+ query: z4.string().describe("Search query"),
1203
+ limit: z4.number().int().min(1).max(100).default(10).describe("Max results"),
1204
+ traverseDepth: z4.number().int().min(0).max(2).default(1).describe("Graph traversal depth (0=none, 1=1-hop, 2=2-hop)")
1205
+ },
1206
+ async (params) => {
1207
+ const results = await engine2.searchGraph(params.query, params.limit, {
1208
+ traverseDepth: params.traverseDepth
1209
+ });
1210
+ return textContent(formatSearchResults(results));
1211
+ }
1212
+ );
1213
+ }
1214
+
1215
+ // src/tools/episode.ts
1216
+ import { z as z5 } from "zod";
1217
+ function registerEpisodeTools(server2, engine2) {
1218
+ server2.tool(
1219
+ "add_episode",
1220
+ "Add an episode (conversation log or action record)",
1221
+ {
1222
+ content: z5.string().describe("Episode content"),
1223
+ contentType: z5.enum(["text", "json", "message"]).default("text").describe("Content format"),
1224
+ source: z5.string().optional().describe("Source info (session_id, file_path, etc.)"),
1225
+ refs: z5.array(z5.object({
1226
+ refType: z5.enum(["node", "edge"]).describe("Reference type"),
1227
+ refId: z5.string().describe("Referenced entity ID")
1228
+ })).optional().describe("References to related nodes/edges")
1229
+ },
1230
+ async (params) => {
1231
+ const refs = params.refs?.map((r) => ({
1232
+ episodeId: "",
1233
+ // set inside addEpisode
1234
+ refType: r.refType,
1235
+ refId: r.refId
1236
+ }));
1237
+ const episode = await engine2.addEpisode(
1238
+ { content: params.content, contentType: params.contentType, source: params.source },
1239
+ refs
1240
+ );
1241
+ return textContent(`Episode added:
1242
+ ${formatEpisode(episode)}
1243
+ ID: ${episode.id}`);
1244
+ }
1245
+ );
1246
+ server2.tool(
1247
+ "get_episode",
1248
+ "Get an episode by ID",
1249
+ {
1250
+ id: z5.string().describe("Episode ID")
1251
+ },
1252
+ async (params) => {
1253
+ const episode = await engine2.getEpisode(params.id);
1254
+ if (!episode) return textContent(`Episode ${params.id} not found`);
1255
+ return textContent(formatEpisode(episode));
1256
+ }
1257
+ );
1258
+ server2.tool(
1259
+ "list_episodes",
1260
+ "List episodes",
1261
+ {
1262
+ limit: z5.number().int().min(1).max(100).default(20).describe("Max results")
1263
+ },
1264
+ async (params) => {
1265
+ const episodes = await engine2.listEpisodes(params.limit);
1266
+ if (episodes.length === 0) return textContent("No episodes found");
1267
+ const lines = episodes.map((ep, i) => `${i + 1}. ${formatEpisode(ep)}
1268
+ ID: ${ep.id}`);
1269
+ return textContent(`${episodes.length} episode(s):
1270
+ ${lines.join("\n")}`);
1271
+ }
1272
+ );
1273
+ }
1274
+
1275
+ // src/tools/generate.ts
1276
+ import { z as z6 } from "zod";
1277
+ var GENERATORS = [
1278
+ { toolName: "generate_claude_md", description: "Generate a CLAUDE.md file", fileName: "CLAUDE.md", format: "claude_md" },
1279
+ { toolName: "generate_cursorrules", description: "Generate a .cursorrules file", fileName: ".cursorrules", format: "cursorrules" },
1280
+ { toolName: "generate_windsurf", description: "Generate a Windsurf config file", fileName: ".windsurfrules", format: "windsurf" },
1281
+ { toolName: "generate_copilot", description: "Generate a GitHub Copilot config", fileName: ".github/copilot-instructions.md", format: "copilot" },
1282
+ { toolName: "generate_codex", description: "Generate a Codex config", fileName: "codex.md", format: "codex" },
1283
+ { toolName: "generate_aider", description: "Generate an Aider config", fileName: ".aider.conf.yml", format: "aider" },
1284
+ { toolName: "generate_continue", description: "Generate a Continue config", fileName: ".continue/rules.md", format: "continue" },
1285
+ { toolName: "generate_cline", description: "Generate a Cline config", fileName: ".clinerules", format: "cline" },
1286
+ { toolName: "generate_roo", description: "Generate a Roo Code config", fileName: ".roo/rules.md", format: "roo" }
1287
+ ];
1288
+ async function generateContent(engine2, format) {
1289
+ const ctx = await engine2.getContext({ maxMemories: 100 });
1290
+ const rules = ctx.memories.filter((m) => m.layer === "rule");
1291
+ const longTerm = ctx.memories.filter((m) => m.layer === "long_term");
1292
+ const sections = [];
1293
+ if (rules.length > 0) {
1294
+ sections.push("# Rules\n");
1295
+ for (const m of rules) {
1296
+ sections.push(`- ${m.content}`);
1297
+ }
1298
+ sections.push("");
1299
+ }
1300
+ if (longTerm.length > 0) {
1301
+ sections.push("# Project Knowledge\n");
1302
+ for (const m of longTerm) {
1303
+ sections.push(`- ${m.content}`);
1304
+ }
1305
+ sections.push("");
1306
+ }
1307
+ const content = sections.join("\n");
1308
+ switch (format) {
1309
+ case "claude_md":
1310
+ case "cursorrules":
1311
+ case "windsurf":
1312
+ case "copilot":
1313
+ case "codex":
1314
+ case "continue":
1315
+ case "cline":
1316
+ case "roo":
1317
+ return content;
1318
+ case "aider":
1319
+ return `# Aider Configuration
1320
+ # Generated by cohaku-ai
1321
+
1322
+ read:
1323
+ - .aiderrules
1324
+ `;
1325
+ default:
1326
+ return content;
1327
+ }
1328
+ }
1329
+ function registerGenerateTools(server2, engine2) {
1330
+ for (const gen of GENERATORS) {
1331
+ const { format } = gen;
1332
+ server2.tool(
1333
+ gen.toolName,
1334
+ gen.description,
1335
+ {
1336
+ includeWorking: z6.boolean().default(false).describe("Include working memory")
1337
+ },
1338
+ async () => {
1339
+ const content = await generateContent(engine2, format);
1340
+ return textContent(`# ${gen.fileName}
1341
+
1342
+ ${content}
1343
+
1344
+ ---
1345
+ Save the above content to ${gen.fileName}.`);
1346
+ }
1347
+ );
1348
+ }
1349
+ }
1350
+
1351
+ // src/server.ts
1352
+ function createServer(engine2) {
1353
+ const server2 = new McpServer({
1354
+ name: "cohaku-memory",
1355
+ version: "0.1.0"
1356
+ });
1357
+ registerContextTools(server2, engine2);
1358
+ registerMemoryTools(server2, engine2);
1359
+ registerSessionTools(server2, engine2);
1360
+ registerGraphTools(server2, engine2);
1361
+ registerEpisodeTools(server2, engine2);
1362
+ registerGenerateTools(server2, engine2);
1363
+ return server2;
1364
+ }
1365
+
1366
+ // src/index.ts
1367
+ var dbPath = process.env["COHAKU_DB_PATH"] ?? join(homedir(), ".config", "cohaku", "memory.db");
1368
+ var engine = new MemoryEngine({ dbPath });
1369
+ await engine.initialize();
1370
+ var server = createServer(engine);
1371
+ var transport = new StdioServerTransport();
1372
+ await server.connect(transport);
1373
+ process.on("SIGINT", () => {
1374
+ engine.close();
1375
+ process.exit(0);
1376
+ });
1377
+ process.on("SIGTERM", () => {
1378
+ engine.close();
1379
+ process.exit(0);
1380
+ });
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@cohaku/mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Cohaku persistent memory layer",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/cohaku-ai/cohaku-ai"
10
+ },
11
+ "keywords": [
12
+ "mcp",
13
+ "memory",
14
+ "ai",
15
+ "coding-agent",
16
+ "knowledge-graph",
17
+ "model-context-protocol"
18
+ ],
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "bin": {
22
+ "cohaku-mcp": "./dist/index.js"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "typecheck": "tsc --noEmit",
31
+ "test": "vitest run",
32
+ "clean": "rm -rf dist"
33
+ },
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.27.1",
36
+ "@xenova/transformers": "^2.17.2",
37
+ "better-sqlite3": "^12.6.2",
38
+ "nanoid": "^5.1.6",
39
+ "sqlite-vec": "0.1.7-alpha.2",
40
+ "zod": "^4.3.6"
41
+ },
42
+ "devDependencies": {
43
+ "@cohaku/core": "workspace:*",
44
+ "@cohaku/shared": "workspace:*",
45
+ "@types/better-sqlite3": "^7.6.13",
46
+ "@types/node": "^25.3.2",
47
+ "tsup": "^8.5.0",
48
+ "typescript": "^5.7.0",
49
+ "vitest": "^4.0.18"
50
+ }
51
+ }