@cartisien/engram-mcp 0.2.0 → 0.3.1

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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/dist/analysis/emotions.d.ts +16 -0
  3. package/dist/analysis/emotions.js +130 -0
  4. package/dist/analysis/emotions.js.map +1 -0
  5. package/dist/analysis/entities.d.ts +15 -0
  6. package/dist/analysis/entities.js +182 -0
  7. package/dist/analysis/entities.js.map +1 -0
  8. package/dist/analysis/themes.d.ts +11 -0
  9. package/dist/analysis/themes.js +151 -0
  10. package/dist/analysis/themes.js.map +1 -0
  11. package/dist/client.d.ts +3 -0
  12. package/dist/client.js +31 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/config.d.ts +13 -0
  15. package/dist/config.js +20 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/response.d.ts +17 -0
  18. package/dist/response.js +17 -0
  19. package/dist/response.js.map +1 -0
  20. package/dist/server.d.ts +7 -0
  21. package/dist/server.js +104 -0
  22. package/dist/server.js.map +1 -0
  23. package/dist/tools/consolidate.d.ts +41 -0
  24. package/dist/tools/consolidate.js +53 -0
  25. package/dist/tools/consolidate.js.map +1 -0
  26. package/dist/tools/context.d.ts +44 -0
  27. package/dist/tools/context.js +186 -0
  28. package/dist/tools/context.js.map +1 -0
  29. package/dist/tools/detect_changes.d.ts +35 -0
  30. package/dist/tools/detect_changes.js +178 -0
  31. package/dist/tools/detect_changes.js.map +1 -0
  32. package/dist/tools/forget.d.ts +38 -0
  33. package/dist/tools/forget.js +38 -0
  34. package/dist/tools/forget.js.map +1 -0
  35. package/dist/tools/graph.d.ts +40 -0
  36. package/dist/tools/graph.js +74 -0
  37. package/dist/tools/graph.js.map +1 -0
  38. package/dist/tools/impact.d.ts +39 -0
  39. package/dist/tools/impact.js +143 -0
  40. package/dist/tools/impact.js.map +1 -0
  41. package/dist/tools/process_detect.d.ts +49 -0
  42. package/dist/tools/process_detect.js +218 -0
  43. package/dist/tools/process_detect.js.map +1 -0
  44. package/dist/tools/recall.d.ts +81 -0
  45. package/dist/tools/recall.js +134 -0
  46. package/dist/tools/recall.js.map +1 -0
  47. package/dist/tools/remember.d.ts +47 -0
  48. package/dist/tools/remember.js +79 -0
  49. package/dist/tools/remember.js.map +1 -0
  50. package/dist/tools/search.d.ts +72 -0
  51. package/dist/tools/search.js +96 -0
  52. package/dist/tools/search.js.map +1 -0
  53. package/dist/tools/stats.d.ts +23 -0
  54. package/dist/tools/stats.js +47 -0
  55. package/dist/tools/stats.js.map +1 -0
  56. package/package.json +29 -14
  57. package/README.md +0 -114
  58. package/dist/engram.d.ts +0 -99
  59. package/dist/engram.js +0 -283
  60. package/dist/src/index.d.ts +0 -2
  61. package/dist/src/index.js +0 -322
  62. package/engram.ts +0 -385
  63. package/src/index.ts +0 -336
  64. package/tests/mcp.test.ts +0 -274
  65. package/tsconfig.json +0 -17
  66. package/tsconfig.test.json +0 -5
package/dist/engram.js DELETED
@@ -1,283 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Engram = void 0;
4
- const crypto_1 = require("crypto");
5
- /**
6
- * Engram - Persistent memory for AI assistants
7
- *
8
- * A lightweight, SQLite-backed memory system that gives your AI assistants
9
- * the ability to remember conversations across sessions.
10
- * v0.2 adds semantic search via Ollama embeddings (nomic-embed-text).
11
- *
12
- * @example
13
- * ```typescript
14
- * import { Engram } from '@cartisien/engram';
15
- *
16
- * const memory = new Engram({ dbPath: './memory.db' });
17
- *
18
- * // Store a memory
19
- * await memory.remember('user_123', 'User prefers dark mode and TypeScript', 'user');
20
- *
21
- * // Retrieve relevant memories semantically
22
- * const context = await memory.recall('user_123', 'What are this user\'s preferences?', 5);
23
- * ```
24
- */
25
- class Engram {
26
- constructor(config = {}) {
27
- this.initialized = false;
28
- this.dbPath = config.dbPath || ':memory:';
29
- this.maxContextLength = config.maxContextLength || 4000;
30
- this.embeddingUrl = config.embeddingUrl || 'http://192.168.68.73:11434';
31
- this.embeddingModel = config.embeddingModel || 'nomic-embed-text';
32
- this.semanticSearch = config.semanticSearch !== false;
33
- }
34
- async init() {
35
- if (this.initialized)
36
- return;
37
- const sqlite3 = require('sqlite3').verbose();
38
- const { open } = require('sqlite');
39
- this.db = await open({
40
- filename: this.dbPath,
41
- driver: sqlite3.Database
42
- });
43
- await this.db.exec(`
44
- CREATE TABLE IF NOT EXISTS memories (
45
- id TEXT PRIMARY KEY,
46
- session_id TEXT NOT NULL,
47
- content TEXT NOT NULL,
48
- role TEXT CHECK(role IN ('user', 'assistant', 'system')),
49
- timestamp INTEGER NOT NULL,
50
- metadata TEXT,
51
- content_hash TEXT NOT NULL,
52
- embedding TEXT
53
- );
54
- `);
55
- // Add embedding column if upgrading from v0.1
56
- try {
57
- await this.db.exec(`ALTER TABLE memories ADD COLUMN embedding TEXT`);
58
- }
59
- catch {
60
- // Column already exists, ignore
61
- }
62
- await this.db.exec(`
63
- CREATE INDEX IF NOT EXISTS idx_session_timestamp
64
- ON memories(session_id, timestamp DESC);
65
- `);
66
- await this.db.exec(`
67
- CREATE INDEX IF NOT EXISTS idx_content
68
- ON memories(content);
69
- `);
70
- this.initialized = true;
71
- }
72
- /**
73
- * Fetch embedding vector from Ollama
74
- */
75
- async embed(text) {
76
- try {
77
- const response = await fetch(`${this.embeddingUrl}/api/embeddings`, {
78
- method: 'POST',
79
- headers: { 'Content-Type': 'application/json' },
80
- body: JSON.stringify({ model: this.embeddingModel, prompt: text }),
81
- signal: AbortSignal.timeout(5000)
82
- });
83
- if (!response.ok)
84
- return null;
85
- const data = await response.json();
86
- return data.embedding ?? null;
87
- }
88
- catch {
89
- return null;
90
- }
91
- }
92
- /**
93
- * Cosine similarity between two vectors
94
- */
95
- cosineSimilarity(a, b) {
96
- let dot = 0, magA = 0, magB = 0;
97
- for (let i = 0; i < a.length; i++) {
98
- dot += a[i] * b[i];
99
- magA += a[i] * a[i];
100
- magB += b[i] * b[i];
101
- }
102
- const denom = Math.sqrt(magA) * Math.sqrt(magB);
103
- return denom === 0 ? 0 : dot / denom;
104
- }
105
- /**
106
- * Store a memory entry
107
- */
108
- async remember(sessionId, content, role = 'user', metadata) {
109
- await this.init();
110
- const id = (0, crypto_1.createHash)('sha256')
111
- .update(`${sessionId}:${content}:${Date.now()}`)
112
- .digest('hex')
113
- .slice(0, 16);
114
- const contentHash = (0, crypto_1.createHash)('sha256')
115
- .update(content)
116
- .digest('hex')
117
- .slice(0, 16);
118
- const truncated = content.slice(0, this.maxContextLength);
119
- // Fetch embedding if semantic search enabled
120
- let embeddingJson = null;
121
- if (this.semanticSearch) {
122
- const vector = await this.embed(truncated);
123
- if (vector)
124
- embeddingJson = JSON.stringify(vector);
125
- }
126
- const entry = {
127
- id,
128
- sessionId,
129
- content: truncated,
130
- role,
131
- timestamp: new Date(),
132
- metadata
133
- };
134
- await this.db.run(`INSERT INTO memories (id, session_id, content, role, timestamp, metadata, content_hash, embedding)
135
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
136
- entry.id,
137
- entry.sessionId,
138
- entry.content,
139
- entry.role,
140
- entry.timestamp.getTime(),
141
- metadata ? JSON.stringify(metadata) : null,
142
- contentHash,
143
- embeddingJson
144
- ]);
145
- return entry;
146
- }
147
- /**
148
- * Recall memories for a session.
149
- *
150
- * With semantic search enabled (default), embeds the query and ranks
151
- * results by cosine similarity. Falls back to keyword search if Ollama
152
- * is unreachable or no embeddings are stored.
153
- */
154
- async recall(sessionId, query, limit = 10, options = {}) {
155
- await this.init();
156
- // Build base query
157
- let sql = `
158
- SELECT id, session_id, content, role, timestamp, metadata, embedding
159
- FROM memories
160
- WHERE session_id = ?
161
- `;
162
- const params = [sessionId];
163
- if (options.role) {
164
- sql += ` AND role = ?`;
165
- params.push(options.role);
166
- }
167
- if (options.after) {
168
- sql += ` AND timestamp >= ?`;
169
- params.push(options.after.getTime());
170
- }
171
- if (options.before) {
172
- sql += ` AND timestamp <= ?`;
173
- params.push(options.before.getTime());
174
- }
175
- // Try semantic search
176
- if (query && query.trim() && this.semanticSearch) {
177
- const queryVector = await this.embed(query);
178
- if (queryVector) {
179
- sql += ` ORDER BY timestamp DESC`;
180
- const rows = await this.db.all(sql, params);
181
- // Score each row by cosine similarity
182
- const scored = rows
183
- .map((row) => {
184
- let similarity = 0;
185
- if (row.embedding) {
186
- try {
187
- const vec = JSON.parse(row.embedding);
188
- similarity = this.cosineSimilarity(queryVector, vec);
189
- }
190
- catch { /* malformed, skip */ }
191
- }
192
- return { row, similarity };
193
- })
194
- .sort((a, b) => b.similarity - a.similarity)
195
- .slice(0, limit);
196
- return scored.map(({ row, similarity }) => ({
197
- id: row.id,
198
- sessionId: row.session_id,
199
- content: row.content,
200
- role: row.role,
201
- timestamp: new Date(row.timestamp),
202
- metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
203
- similarity
204
- }));
205
- }
206
- // Ollama unreachable — fall through to keyword search
207
- }
208
- // Keyword fallback
209
- if (query && query.trim()) {
210
- const keywords = query.toLowerCase().split(/\s+/).filter(k => k.length > 2);
211
- if (keywords.length > 0) {
212
- sql += ` AND (` + keywords.map(() => `LOWER(content) LIKE ?`).join(' OR ') + `)`;
213
- params.push(...keywords.map(k => `%${k}%`));
214
- }
215
- }
216
- sql += ` ORDER BY timestamp DESC LIMIT ?`;
217
- params.push(limit);
218
- const rows = await this.db.all(sql, params);
219
- return rows.map((row) => ({
220
- id: row.id,
221
- sessionId: row.session_id,
222
- content: row.content,
223
- role: row.role,
224
- timestamp: new Date(row.timestamp),
225
- metadata: row.metadata ? JSON.parse(row.metadata) : undefined
226
- }));
227
- }
228
- /**
229
- * Get recent conversation history for a session
230
- */
231
- async history(sessionId, limit = 20) {
232
- return this.recall(sessionId, undefined, limit, {});
233
- }
234
- /**
235
- * Forget (delete) memories
236
- */
237
- async forget(sessionId, options) {
238
- await this.init();
239
- if (options?.id) {
240
- const result = await this.db.run('DELETE FROM memories WHERE session_id = ? AND id = ?', [sessionId, options.id]);
241
- return result.changes || 0;
242
- }
243
- let sql = 'DELETE FROM memories WHERE session_id = ?';
244
- const params = [sessionId];
245
- if (options?.before) {
246
- sql += ' AND timestamp < ?';
247
- params.push(options.before.getTime());
248
- }
249
- const result = await this.db.run(sql, params);
250
- return result.changes || 0;
251
- }
252
- /**
253
- * Get memory statistics for a session
254
- */
255
- async stats(sessionId) {
256
- await this.init();
257
- const totalRow = await this.db.get('SELECT COUNT(*) as count FROM memories WHERE session_id = ?', [sessionId]);
258
- const total = totalRow?.count || 0;
259
- const roleRows = await this.db.all('SELECT role, COUNT(*) as count FROM memories WHERE session_id = ? GROUP BY role', [sessionId]);
260
- const byRole = {};
261
- roleRows.forEach((row) => { byRole[row.role] = row.count; });
262
- const range = await this.db.get('SELECT MIN(timestamp) as oldest, MAX(timestamp) as newest FROM memories WHERE session_id = ?', [sessionId]);
263
- const embRow = await this.db.get('SELECT COUNT(*) as count FROM memories WHERE session_id = ? AND embedding IS NOT NULL', [sessionId]);
264
- return {
265
- total,
266
- byRole,
267
- oldest: range?.oldest ? new Date(range.oldest) : null,
268
- newest: range?.newest ? new Date(range.newest) : null,
269
- withEmbeddings: embRow?.count || 0
270
- };
271
- }
272
- /**
273
- * Close the database connection
274
- */
275
- async close() {
276
- if (this.db) {
277
- await this.db.close();
278
- this.initialized = false;
279
- }
280
- }
281
- }
282
- exports.Engram = Engram;
283
- exports.default = Engram;
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/src/index.js DELETED
@@ -1,322 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || (function () {
20
- var ownKeys = function(o) {
21
- ownKeys = Object.getOwnPropertyNames || function (o) {
22
- var ar = [];
23
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
- return ar;
25
- };
26
- return ownKeys(o);
27
- };
28
- return function (mod) {
29
- if (mod && mod.__esModule) return mod;
30
- var result = {};
31
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
- __setModuleDefault(result, mod);
33
- return result;
34
- };
35
- })();
36
- Object.defineProperty(exports, "__esModule", { value: true });
37
- const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
38
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
39
- const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
40
- const engram_1 = require("@cartisien/engram");
41
- const os = __importStar(require("os"));
42
- const path = __importStar(require("path"));
43
- const fs = __importStar(require("fs"));
44
- const DB_PATH = process.env.ENGRAM_DB || path.join(os.homedir(), '.engram', 'memory.db');
45
- const EMBEDDING_URL = process.env.ENGRAM_EMBEDDING_URL || 'http://192.168.68.73:11434';
46
- const GRAPH_MODEL = process.env.ENGRAM_GRAPH_MODEL || 'qwen2.5:32b';
47
- const CONSOLIDATE_MODEL = process.env.ENGRAM_CONSOLIDATE_MODEL || 'qwen2.5:32b';
48
- const dbDir = path.dirname(DB_PATH);
49
- if (!fs.existsSync(dbDir))
50
- fs.mkdirSync(dbDir, { recursive: true });
51
- const memory = new engram_1.Engram({
52
- dbPath: DB_PATH,
53
- embeddingUrl: EMBEDDING_URL,
54
- semanticSearch: true,
55
- graphMemory: process.env.ENGRAM_GRAPH === '1',
56
- graphModel: GRAPH_MODEL,
57
- consolidateModel: CONSOLIDATE_MODEL,
58
- });
59
- const server = new index_js_1.Server({ name: 'engram', version: '0.2.0' }, { capabilities: { tools: {} } });
60
- server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
61
- tools: [
62
- // ── Session memory ──────────────────────────────────────────────────
63
- {
64
- name: 'remember',
65
- description: 'Store a memory entry for a session. Embeddings and graph extraction happen automatically.',
66
- inputSchema: {
67
- type: 'object',
68
- properties: {
69
- sessionId: { type: 'string', description: 'Session identifier' },
70
- content: { type: 'string', description: 'Memory content to store' },
71
- role: { type: 'string', enum: ['user', 'assistant', 'system'], default: 'user' },
72
- metadata: { type: 'object', description: 'Optional key-value metadata' },
73
- },
74
- required: ['sessionId', 'content'],
75
- },
76
- },
77
- {
78
- name: 'recall',
79
- description: 'Retrieve relevant memories via semantic search (falls back to keyword). Searches working + long_term tiers. Pass userId to blend in cross-session user facts.',
80
- inputSchema: {
81
- type: 'object',
82
- properties: {
83
- sessionId: { type: 'string', description: 'Session identifier' },
84
- query: { type: 'string', description: 'Search query' },
85
- limit: { type: 'number', description: 'Max results (default 10)', default: 10 },
86
- userId: { type: 'string', description: 'Optional: also blend in this user\'s cross-session memories' },
87
- tiers: { type: 'string', description: 'Comma-separated tiers to search: working,long_term,archived (default: working,long_term)' },
88
- },
89
- required: ['sessionId', 'query'],
90
- },
91
- },
92
- {
93
- name: 'history',
94
- description: 'Get recent conversation history for a session in chronological order.',
95
- inputSchema: {
96
- type: 'object',
97
- properties: {
98
- sessionId: { type: 'string', description: 'Session identifier' },
99
- limit: { type: 'number', description: 'Max entries (default 20)', default: 20 },
100
- },
101
- required: ['sessionId'],
102
- },
103
- },
104
- {
105
- name: 'forget',
106
- description: 'Delete session memories. Delete all, one by ID, or entries before a date.',
107
- inputSchema: {
108
- type: 'object',
109
- properties: {
110
- sessionId: { type: 'string', description: 'Session identifier' },
111
- id: { type: 'string', description: 'Specific memory ID to delete (optional)' },
112
- before: { type: 'string', description: 'ISO date — delete entries before this date (optional)' },
113
- },
114
- required: ['sessionId'],
115
- },
116
- },
117
- {
118
- name: 'stats',
119
- description: 'Memory statistics for a session — total, by role, by tier (working/long_term/archived), graph counts.',
120
- inputSchema: {
121
- type: 'object',
122
- properties: {
123
- sessionId: { type: 'string', description: 'Session identifier' },
124
- },
125
- required: ['sessionId'],
126
- },
127
- },
128
- {
129
- name: 'consolidate',
130
- description: 'Consolidate old working memories into dense long-term summaries via LLM. Archives originals.',
131
- inputSchema: {
132
- type: 'object',
133
- properties: {
134
- sessionId: { type: 'string', description: 'Session identifier' },
135
- batch: { type: 'number', description: 'Number of memories to consolidate (default 50)' },
136
- keep: { type: 'number', description: 'Most recent N to leave untouched (default 20)' },
137
- dryRun: { type: 'boolean', description: 'Preview summaries without writing (default false)' },
138
- },
139
- required: ['sessionId'],
140
- },
141
- },
142
- {
143
- name: 'graph',
144
- description: 'Query the knowledge graph for an entity — returns relationships and source memories. Requires ENGRAM_GRAPH=1.',
145
- inputSchema: {
146
- type: 'object',
147
- properties: {
148
- sessionId: { type: 'string', description: 'Session identifier' },
149
- entity: { type: 'string', description: 'Entity name to look up (case-insensitive)' },
150
- },
151
- required: ['sessionId', 'entity'],
152
- },
153
- },
154
- // ── User-scoped (cross-session) memory ──────────────────────────────
155
- {
156
- name: 'remember_user',
157
- description: 'Store a user-scoped memory that persists across all sessions. Use for preferences, identity, long-term facts.',
158
- inputSchema: {
159
- type: 'object',
160
- properties: {
161
- userId: { type: 'string', description: 'User identifier' },
162
- content: { type: 'string', description: 'Memory content to store' },
163
- role: { type: 'string', enum: ['user', 'assistant', 'system'], default: 'user' },
164
- metadata: { type: 'object', description: 'Optional key-value metadata' },
165
- },
166
- required: ['userId', 'content'],
167
- },
168
- },
169
- {
170
- name: 'recall_user',
171
- description: 'Recall user-scoped memories — works from any session context.',
172
- inputSchema: {
173
- type: 'object',
174
- properties: {
175
- userId: { type: 'string', description: 'User identifier' },
176
- query: { type: 'string', description: 'Search query (optional — returns all if omitted)' },
177
- limit: { type: 'number', description: 'Max results (default 10)', default: 10 },
178
- },
179
- required: ['userId'],
180
- },
181
- },
182
- {
183
- name: 'forget_user',
184
- description: 'Delete user-scoped memories.',
185
- inputSchema: {
186
- type: 'object',
187
- properties: {
188
- userId: { type: 'string', description: 'User identifier' },
189
- id: { type: 'string', description: 'Specific memory ID (optional)' },
190
- before: { type: 'string', description: 'ISO date — delete entries before this date (optional)' },
191
- },
192
- required: ['userId'],
193
- },
194
- },
195
- {
196
- name: 'consolidate_user',
197
- description: 'Consolidate user-scoped working memories into long-term summaries.',
198
- inputSchema: {
199
- type: 'object',
200
- properties: {
201
- userId: { type: 'string', description: 'User identifier' },
202
- batch: { type: 'number', description: 'Number of memories to consolidate (default 50)' },
203
- keep: { type: 'number', description: 'Most recent N to leave untouched (default 20)' },
204
- dryRun: { type: 'boolean', description: 'Preview without writing (default false)' },
205
- },
206
- required: ['userId'],
207
- },
208
- },
209
- {
210
- name: 'user_stats',
211
- description: 'Memory statistics for a user — total, by role, by tier.',
212
- inputSchema: {
213
- type: 'object',
214
- properties: {
215
- userId: { type: 'string', description: 'User identifier' },
216
- },
217
- required: ['userId'],
218
- },
219
- },
220
- ],
221
- }));
222
- server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
223
- const { name, arguments: args = {} } = request.params;
224
- try {
225
- switch (name) {
226
- // ── Session ──────────────────────────────────────────────────────
227
- case 'remember': {
228
- const entry = await memory.remember(args.sessionId, args.content, args.role || 'user', args.metadata);
229
- return { content: [{ type: 'text', text: JSON.stringify(entry, null, 2) }] };
230
- }
231
- case 'recall': {
232
- const tiers = args.tiers
233
- ? args.tiers.split(',').map(t => t.trim())
234
- : undefined;
235
- const entries = await memory.recall(args.sessionId, args.query, args.limit || 10, {
236
- tiers,
237
- userId: args.userId,
238
- });
239
- return { content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }] };
240
- }
241
- case 'history': {
242
- const entries = await memory.history(args.sessionId, args.limit || 20);
243
- return { content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }] };
244
- }
245
- case 'forget': {
246
- const opts = {};
247
- if (args.id)
248
- opts.id = args.id;
249
- if (args.before)
250
- opts.before = new Date(args.before);
251
- const deleted = await memory.forget(args.sessionId, opts);
252
- return { content: [{ type: 'text', text: `Deleted ${deleted} memor${deleted === 1 ? 'y' : 'ies'}.` }] };
253
- }
254
- case 'stats': {
255
- const stats = await memory.stats(args.sessionId);
256
- return { content: [{ type: 'text', text: JSON.stringify(stats, null, 2) }] };
257
- }
258
- case 'consolidate': {
259
- const result = await memory.consolidate(args.sessionId, {
260
- batch: args.batch,
261
- keep: args.keep,
262
- dryRun: args.dryRun,
263
- });
264
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
265
- }
266
- case 'graph': {
267
- const result = await memory.graph(args.sessionId, args.entity);
268
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
269
- }
270
- // ── User-scoped ───────────────────────────────────────────────────
271
- case 'remember_user': {
272
- const entry = await memory.rememberUser(args.userId, args.content, args.role || 'user', args.metadata);
273
- return { content: [{ type: 'text', text: JSON.stringify(entry, null, 2) }] };
274
- }
275
- case 'recall_user': {
276
- const entries = await memory.recallUser(args.userId, args.query, args.limit || 10);
277
- return { content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }] };
278
- }
279
- case 'forget_user': {
280
- const opts = {};
281
- if (args.id)
282
- opts.id = args.id;
283
- if (args.before)
284
- opts.before = new Date(args.before);
285
- const deleted = await memory.forgetUser(args.userId, opts);
286
- return { content: [{ type: 'text', text: `Deleted ${deleted} user memor${deleted === 1 ? 'y' : 'ies'}.` }] };
287
- }
288
- case 'consolidate_user': {
289
- const result = await memory.consolidateUser(args.userId, {
290
- batch: args.batch,
291
- keep: args.keep,
292
- dryRun: args.dryRun,
293
- });
294
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
295
- }
296
- case 'user_stats': {
297
- const stats = await memory.userStats(args.userId);
298
- return { content: [{ type: 'text', text: JSON.stringify(stats, null, 2) }] };
299
- }
300
- default:
301
- return {
302
- content: [{ type: 'text', text: `Unknown tool: ${name}` }],
303
- isError: true,
304
- };
305
- }
306
- }
307
- catch (err) {
308
- return {
309
- content: [{ type: 'text', text: `Error: ${err.message}` }],
310
- isError: true,
311
- };
312
- }
313
- });
314
- async function main() {
315
- const transport = new stdio_js_1.StdioServerTransport();
316
- await server.connect(transport);
317
- process.stderr.write('Engram MCP server v0.2.0 running\n');
318
- }
319
- main().catch((err) => {
320
- process.stderr.write(`Fatal: ${err.message}\n`);
321
- process.exit(1);
322
- });