mcp-memory-keeper 0.10.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 (98) hide show
  1. package/CHANGELOG.md +433 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1051 -0
  4. package/bin/mcp-memory-keeper +52 -0
  5. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  6. package/dist/__tests__/helpers/test-server.js +92 -0
  7. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  8. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  9. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  10. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  11. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  12. package/dist/__tests__/integration/channels.test.js +376 -0
  13. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  14. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  15. package/dist/__tests__/integration/context-operations.test.js +243 -0
  16. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  17. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  18. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  19. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  20. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  21. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  22. package/dist/__tests__/integration/contextSearch.test.js +938 -0
  23. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  24. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  25. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  26. package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
  27. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  28. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  29. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  30. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  31. package/dist/__tests__/integration/error-cases.test.js +407 -0
  32. package/dist/__tests__/integration/export-import.test.js +367 -0
  33. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  34. package/dist/__tests__/integration/file-operations.test.js +264 -0
  35. package/dist/__tests__/integration/git-integration.test.js +237 -0
  36. package/dist/__tests__/integration/index-tools.test.js +496 -0
  37. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  38. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  39. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  40. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  41. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  42. package/dist/__tests__/integration/migrations.test.js +528 -0
  43. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  44. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  45. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  46. package/dist/__tests__/integration/project-directory.test.js +283 -0
  47. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  48. package/dist/__tests__/integration/retention.test.js +513 -0
  49. package/dist/__tests__/integration/search.test.js +333 -0
  50. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  51. package/dist/__tests__/integration/server-initialization.test.js +307 -0
  52. package/dist/__tests__/integration/session-management.test.js +219 -0
  53. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  54. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  55. package/dist/__tests__/integration/summarization.test.js +308 -0
  56. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  57. package/dist/__tests__/security/input-validation.test.js +115 -0
  58. package/dist/__tests__/utils/agents.test.js +473 -0
  59. package/dist/__tests__/utils/database.test.js +177 -0
  60. package/dist/__tests__/utils/git.test.js +122 -0
  61. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  62. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  63. package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
  64. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  65. package/dist/__tests__/utils/validation.test.js +200 -0
  66. package/dist/__tests__/utils/vector-store.test.js +231 -0
  67. package/dist/handlers/contextWatchHandlers.js +206 -0
  68. package/dist/index.js +4310 -0
  69. package/dist/index.phase1.backup.js +410 -0
  70. package/dist/index.phase2.backup.js +704 -0
  71. package/dist/migrations/003_add_channels.js +174 -0
  72. package/dist/migrations/004_add_context_watch.js +151 -0
  73. package/dist/migrations/005_add_context_watch.js +98 -0
  74. package/dist/migrations/simplify-sharing.js +117 -0
  75. package/dist/repositories/BaseRepository.js +30 -0
  76. package/dist/repositories/CheckpointRepository.js +140 -0
  77. package/dist/repositories/ContextRepository.js +1873 -0
  78. package/dist/repositories/FileRepository.js +104 -0
  79. package/dist/repositories/RepositoryManager.js +62 -0
  80. package/dist/repositories/SessionRepository.js +66 -0
  81. package/dist/repositories/WatcherRepository.js +252 -0
  82. package/dist/repositories/index.js +15 -0
  83. package/dist/server.js +384 -0
  84. package/dist/test-helpers/database-helper.js +128 -0
  85. package/dist/types/entities.js +3 -0
  86. package/dist/utils/agents.js +791 -0
  87. package/dist/utils/channels.js +150 -0
  88. package/dist/utils/database.js +731 -0
  89. package/dist/utils/feature-flags.js +476 -0
  90. package/dist/utils/git.js +145 -0
  91. package/dist/utils/knowledge-graph.js +264 -0
  92. package/dist/utils/migrationHealthCheck.js +373 -0
  93. package/dist/utils/migrations.js +452 -0
  94. package/dist/utils/retention.js +460 -0
  95. package/dist/utils/timestamps.js +112 -0
  96. package/dist/utils/validation.js +296 -0
  97. package/dist/utils/vector-store.js +247 -0
  98. package/package.json +84 -0
@@ -0,0 +1,704 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
40
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
41
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
42
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
43
+ const uuid_1 = require("uuid");
44
+ const crypto = __importStar(require("crypto"));
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const simple_git_1 = require("simple-git");
48
+ // Initialize database
49
+ const db = new better_sqlite3_1.default('context.db');
50
+ // Initialize git
51
+ const git = (0, simple_git_1.simpleGit)();
52
+ // Create tables with enhanced schema
53
+ db.exec(`
54
+ -- Sessions table
55
+ CREATE TABLE IF NOT EXISTS sessions (
56
+ id TEXT PRIMARY KEY,
57
+ name TEXT,
58
+ description TEXT,
59
+ branch TEXT,
60
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
61
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
62
+ );
63
+
64
+ -- Enhanced context_items table with session support
65
+ CREATE TABLE IF NOT EXISTS context_items (
66
+ id TEXT PRIMARY KEY,
67
+ session_id TEXT NOT NULL,
68
+ key TEXT NOT NULL,
69
+ value TEXT NOT NULL,
70
+ category TEXT,
71
+ priority TEXT DEFAULT 'normal',
72
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
73
+ FOREIGN KEY (session_id) REFERENCES sessions(id),
74
+ UNIQUE(session_id, key)
75
+ );
76
+
77
+ -- File cache table
78
+ CREATE TABLE IF NOT EXISTS file_cache (
79
+ id TEXT PRIMARY KEY,
80
+ session_id TEXT NOT NULL,
81
+ file_path TEXT NOT NULL,
82
+ content TEXT,
83
+ hash TEXT,
84
+ last_read TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
85
+ FOREIGN KEY (session_id) REFERENCES sessions(id),
86
+ UNIQUE(session_id, file_path)
87
+ );
88
+
89
+ -- Checkpoints table (Phase 2)
90
+ CREATE TABLE IF NOT EXISTS checkpoints (
91
+ id TEXT PRIMARY KEY,
92
+ session_id TEXT NOT NULL,
93
+ name TEXT NOT NULL,
94
+ description TEXT,
95
+ git_status TEXT,
96
+ git_branch TEXT,
97
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
98
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
99
+ );
100
+
101
+ -- Checkpoint items table (Phase 2)
102
+ CREATE TABLE IF NOT EXISTS checkpoint_items (
103
+ id TEXT PRIMARY KEY,
104
+ checkpoint_id TEXT NOT NULL,
105
+ context_item_id TEXT NOT NULL,
106
+ FOREIGN KEY (checkpoint_id) REFERENCES checkpoints(id),
107
+ FOREIGN KEY (context_item_id) REFERENCES context_items(id)
108
+ );
109
+
110
+ -- Checkpoint files table (Phase 2)
111
+ CREATE TABLE IF NOT EXISTS checkpoint_files (
112
+ id TEXT PRIMARY KEY,
113
+ checkpoint_id TEXT NOT NULL,
114
+ file_cache_id TEXT NOT NULL,
115
+ FOREIGN KEY (checkpoint_id) REFERENCES checkpoints(id),
116
+ FOREIGN KEY (file_cache_id) REFERENCES file_cache(id)
117
+ );
118
+ `);
119
+ // Track current session
120
+ let currentSessionId = null;
121
+ // Helper function to get or create default session
122
+ function ensureSession() {
123
+ if (!currentSessionId) {
124
+ const session = db.prepare('SELECT id FROM sessions ORDER BY created_at DESC LIMIT 1').get();
125
+ if (session) {
126
+ currentSessionId = session.id;
127
+ }
128
+ else {
129
+ // Create default session
130
+ currentSessionId = (0, uuid_1.v4)();
131
+ db.prepare('INSERT INTO sessions (id, name, description) VALUES (?, ?, ?)').run(currentSessionId, 'Default Session', 'Auto-created default session');
132
+ }
133
+ }
134
+ return currentSessionId;
135
+ }
136
+ // Helper to calculate file hash
137
+ function calculateFileHash(content) {
138
+ return crypto.createHash('sha256').update(content).digest('hex');
139
+ }
140
+ // Helper to get git status
141
+ async function getGitStatus() {
142
+ try {
143
+ const status = await git.status();
144
+ const branch = await git.branch();
145
+ return {
146
+ status: JSON.stringify({
147
+ modified: status.modified,
148
+ created: status.created,
149
+ deleted: status.deleted,
150
+ staged: status.staged,
151
+ ahead: status.ahead,
152
+ behind: status.behind,
153
+ }),
154
+ branch: branch.current,
155
+ };
156
+ }
157
+ catch (e) {
158
+ return { status: 'No git repository', branch: 'none' };
159
+ }
160
+ }
161
+ // Helper to create summary
162
+ function createSummary(items, options) {
163
+ const { categories, maxLength = 1000 } = options;
164
+ let filteredItems = items;
165
+ if (categories && categories.length > 0) {
166
+ filteredItems = items.filter(item => categories.includes(item.category));
167
+ }
168
+ // Group by category
169
+ const grouped = filteredItems.reduce((acc, item) => {
170
+ const cat = item.category || 'uncategorized';
171
+ if (!acc[cat])
172
+ acc[cat] = [];
173
+ acc[cat].push(item);
174
+ return acc;
175
+ }, {});
176
+ // Build summary
177
+ let summary = '# Context Summary\n\n';
178
+ // High priority items first
179
+ const highPriorityItems = filteredItems.filter(item => item.priority === 'high');
180
+ if (highPriorityItems.length > 0) {
181
+ summary += '## High Priority Items\n';
182
+ highPriorityItems.forEach(item => {
183
+ summary += `- **${item.key}**: ${item.value.substring(0, 200)}${item.value.length > 200 ? '...' : ''}\n`;
184
+ });
185
+ summary += '\n';
186
+ }
187
+ // Then by category
188
+ Object.entries(grouped).forEach(([category, categoryItems]) => {
189
+ if (category !== 'uncategorized') {
190
+ summary += `## ${category.charAt(0).toUpperCase() + category.slice(1)}\n`;
191
+ categoryItems.forEach((item) => {
192
+ if (item.priority !== 'high') { // Already shown above
193
+ summary += `- ${item.key}: ${item.value.substring(0, 100)}${item.value.length > 100 ? '...' : ''}\n`;
194
+ }
195
+ });
196
+ summary += '\n';
197
+ }
198
+ });
199
+ // Truncate if needed
200
+ if (summary.length > maxLength) {
201
+ summary = summary.substring(0, maxLength - 3) + '...';
202
+ }
203
+ return summary;
204
+ }
205
+ // Create MCP server
206
+ const server = new index_js_1.Server({
207
+ name: 'memory-keeper',
208
+ version: '0.3.0',
209
+ }, {
210
+ capabilities: {
211
+ tools: {},
212
+ },
213
+ });
214
+ // Main request handler
215
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
216
+ const toolName = request.params.name;
217
+ const args = request.params.arguments;
218
+ switch (toolName) {
219
+ // Session Management
220
+ case 'context_session_start': {
221
+ const { name, description, continueFrom } = args;
222
+ const sessionId = (0, uuid_1.v4)();
223
+ // Get current git branch if available
224
+ let branch = null;
225
+ try {
226
+ const gitHeadPath = path.join(process.cwd(), '.git', 'HEAD');
227
+ if (fs.existsSync(gitHeadPath)) {
228
+ const headContent = fs.readFileSync(gitHeadPath, 'utf8').trim();
229
+ if (headContent.startsWith('ref: refs/heads/')) {
230
+ branch = headContent.replace('ref: refs/heads/', '');
231
+ }
232
+ }
233
+ }
234
+ catch (e) {
235
+ // Ignore git errors
236
+ }
237
+ db.prepare('INSERT INTO sessions (id, name, description, branch) VALUES (?, ?, ?, ?)').run(sessionId, name || `Session ${new Date().toISOString()}`, description || '', branch);
238
+ // Copy context from previous session if specified
239
+ if (continueFrom) {
240
+ const copyStmt = db.prepare(`
241
+ INSERT INTO context_items (id, session_id, key, value, category, priority)
242
+ SELECT ?, ?, key, value, category, priority
243
+ FROM context_items
244
+ WHERE session_id = ?
245
+ `);
246
+ const items = db.prepare('SELECT * FROM context_items WHERE session_id = ?').all(continueFrom);
247
+ for (const item of items) {
248
+ copyStmt.run((0, uuid_1.v4)(), sessionId, continueFrom);
249
+ }
250
+ }
251
+ currentSessionId = sessionId;
252
+ return {
253
+ content: [{
254
+ type: 'text',
255
+ text: `Started new session: ${sessionId}\nName: ${name || 'Unnamed'}\nBranch: ${branch || 'unknown'}`,
256
+ }],
257
+ };
258
+ }
259
+ case 'context_session_list': {
260
+ const { limit = 10 } = args;
261
+ const sessions = db.prepare(`
262
+ SELECT id, name, description, branch, created_at,
263
+ (SELECT COUNT(*) FROM context_items WHERE session_id = sessions.id) as item_count
264
+ FROM sessions
265
+ ORDER BY created_at DESC
266
+ LIMIT ?
267
+ `).all(limit);
268
+ const sessionList = sessions.map((s) => `• ${s.name} (${s.id.substring(0, 8)})\n Created: ${s.created_at}\n Items: ${s.item_count}\n Branch: ${s.branch || 'unknown'}`).join('\n\n');
269
+ return {
270
+ content: [{
271
+ type: 'text',
272
+ text: `Recent sessions:\n\n${sessionList}`,
273
+ }],
274
+ };
275
+ }
276
+ // Enhanced Context Storage
277
+ case 'context_save': {
278
+ const { key, value, category, priority = 'normal' } = args;
279
+ const sessionId = ensureSession();
280
+ const stmt = db.prepare(`
281
+ INSERT OR REPLACE INTO context_items (id, session_id, key, value, category, priority)
282
+ VALUES (?, ?, ?, ?, ?, ?)
283
+ `);
284
+ stmt.run((0, uuid_1.v4)(), sessionId, key, value, category, priority);
285
+ return {
286
+ content: [{
287
+ type: 'text',
288
+ text: `Saved: ${key}\nCategory: ${category || 'none'}\nPriority: ${priority}\nSession: ${sessionId.substring(0, 8)}`,
289
+ }],
290
+ };
291
+ }
292
+ case 'context_get': {
293
+ const { key, category, sessionId: specificSessionId } = args;
294
+ const targetSessionId = specificSessionId || currentSessionId || ensureSession();
295
+ let query = 'SELECT * FROM context_items WHERE session_id = ?';
296
+ const params = [targetSessionId];
297
+ if (key) {
298
+ query += ' AND key = ?';
299
+ params.push(key);
300
+ }
301
+ if (category) {
302
+ query += ' AND category = ?';
303
+ params.push(category);
304
+ }
305
+ query += ' ORDER BY priority DESC, created_at DESC';
306
+ const rows = db.prepare(query).all(...params);
307
+ if (rows.length === 0) {
308
+ return {
309
+ content: [{
310
+ type: 'text',
311
+ text: 'No matching context found',
312
+ }],
313
+ };
314
+ }
315
+ if (key && rows.length === 1) {
316
+ // Single item requested
317
+ const item = rows[0];
318
+ return {
319
+ content: [{
320
+ type: 'text',
321
+ text: item.value,
322
+ }],
323
+ };
324
+ }
325
+ // Multiple items
326
+ const items = rows.map((r) => `• [${r.priority}] ${r.key}: ${r.value.substring(0, 100)}${r.value.length > 100 ? '...' : ''}`).join('\n');
327
+ return {
328
+ content: [{
329
+ type: 'text',
330
+ text: `Found ${rows.length} context items:\n\n${items}`,
331
+ }],
332
+ };
333
+ }
334
+ // File Caching
335
+ case 'context_cache_file': {
336
+ const { filePath, content } = args;
337
+ const sessionId = ensureSession();
338
+ const hash = calculateFileHash(content);
339
+ const stmt = db.prepare(`
340
+ INSERT OR REPLACE INTO file_cache (id, session_id, file_path, content, hash)
341
+ VALUES (?, ?, ?, ?, ?)
342
+ `);
343
+ stmt.run((0, uuid_1.v4)(), sessionId, filePath, content, hash);
344
+ return {
345
+ content: [{
346
+ type: 'text',
347
+ text: `Cached file: ${filePath}\nHash: ${hash.substring(0, 16)}...\nSize: ${content.length} bytes`,
348
+ }],
349
+ };
350
+ }
351
+ case 'context_file_changed': {
352
+ const { filePath, currentContent } = args;
353
+ const sessionId = ensureSession();
354
+ const cached = db.prepare('SELECT hash, content FROM file_cache WHERE session_id = ? AND file_path = ?').get(sessionId, filePath);
355
+ if (!cached) {
356
+ return {
357
+ content: [{
358
+ type: 'text',
359
+ text: `No cached version found for: ${filePath}`,
360
+ }],
361
+ };
362
+ }
363
+ const currentHash = currentContent ? calculateFileHash(currentContent) : null;
364
+ const hasChanged = currentHash !== cached.hash;
365
+ return {
366
+ content: [{
367
+ type: 'text',
368
+ text: `File: ${filePath}\nChanged: ${hasChanged}\nCached hash: ${cached.hash.substring(0, 16)}...\nCurrent hash: ${currentHash ? currentHash.substring(0, 16) + '...' : 'N/A'}`,
369
+ }],
370
+ };
371
+ }
372
+ case 'context_status': {
373
+ const sessionId = currentSessionId || ensureSession();
374
+ const stats = db.prepare(`
375
+ SELECT
376
+ (SELECT COUNT(*) FROM context_items WHERE session_id = ?) as item_count,
377
+ (SELECT COUNT(*) FROM file_cache WHERE session_id = ?) as file_count,
378
+ (SELECT created_at FROM sessions WHERE id = ?) as session_created,
379
+ (SELECT name FROM sessions WHERE id = ?) as session_name
380
+ `).get(sessionId, sessionId, sessionId, sessionId);
381
+ const recentItems = db.prepare(`
382
+ SELECT key, category, priority FROM context_items
383
+ WHERE session_id = ?
384
+ ORDER BY created_at DESC
385
+ LIMIT 5
386
+ `).all(sessionId);
387
+ const recentList = recentItems.map((item) => ` • [${item.priority}] ${item.key} (${item.category || 'uncategorized'})`).join('\n');
388
+ return {
389
+ content: [{
390
+ type: 'text',
391
+ text: `Current Session: ${stats.session_name}
392
+ Session ID: ${sessionId.substring(0, 8)}
393
+ Created: ${stats.session_created}
394
+ Context Items: ${stats.item_count}
395
+ Cached Files: ${stats.file_count}
396
+
397
+ Recent Items:
398
+ ${recentList || ' None'}`,
399
+ }],
400
+ };
401
+ }
402
+ // Phase 2: Checkpoint System
403
+ case 'context_checkpoint': {
404
+ const { name, description, includeFiles = true, includeGitStatus = true } = args;
405
+ const sessionId = ensureSession();
406
+ const checkpointId = (0, uuid_1.v4)();
407
+ // Get git status if requested
408
+ let gitStatus = null;
409
+ let gitBranch = null;
410
+ if (includeGitStatus) {
411
+ const gitInfo = await getGitStatus();
412
+ gitStatus = gitInfo.status;
413
+ gitBranch = gitInfo.branch;
414
+ }
415
+ // Create checkpoint
416
+ db.prepare(`
417
+ INSERT INTO checkpoints (id, session_id, name, description, git_status, git_branch)
418
+ VALUES (?, ?, ?, ?, ?, ?)
419
+ `).run(checkpointId, sessionId, name, description || '', gitStatus, gitBranch);
420
+ // Save context items
421
+ const contextItems = db.prepare('SELECT id FROM context_items WHERE session_id = ?').all(sessionId);
422
+ const itemStmt = db.prepare('INSERT INTO checkpoint_items (id, checkpoint_id, context_item_id) VALUES (?, ?, ?)');
423
+ for (const item of contextItems) {
424
+ itemStmt.run((0, uuid_1.v4)(), checkpointId, item.id);
425
+ }
426
+ // Save file cache if requested
427
+ let fileCount = 0;
428
+ if (includeFiles) {
429
+ const files = db.prepare('SELECT id FROM file_cache WHERE session_id = ?').all(sessionId);
430
+ const fileStmt = db.prepare('INSERT INTO checkpoint_files (id, checkpoint_id, file_cache_id) VALUES (?, ?, ?)');
431
+ for (const file of files) {
432
+ fileStmt.run((0, uuid_1.v4)(), checkpointId, file.id);
433
+ fileCount++;
434
+ }
435
+ }
436
+ return {
437
+ content: [{
438
+ type: 'text',
439
+ text: `Created checkpoint: ${name}
440
+ ID: ${checkpointId.substring(0, 8)}
441
+ Context items: ${contextItems.length}
442
+ Cached files: ${fileCount}
443
+ Git branch: ${gitBranch || 'none'}
444
+ Git status: ${gitStatus ? 'captured' : 'not captured'}`,
445
+ }],
446
+ };
447
+ }
448
+ case 'context_restore_checkpoint': {
449
+ const { name, checkpointId, restoreFiles = true } = args;
450
+ // Find checkpoint
451
+ let checkpoint;
452
+ if (checkpointId) {
453
+ checkpoint = db.prepare('SELECT * FROM checkpoints WHERE id = ?').get(checkpointId);
454
+ }
455
+ else if (name) {
456
+ checkpoint = db.prepare('SELECT * FROM checkpoints ORDER BY created_at DESC').all()
457
+ .find((cp) => cp.name === name);
458
+ }
459
+ else {
460
+ // Get latest checkpoint
461
+ checkpoint = db.prepare('SELECT * FROM checkpoints ORDER BY created_at DESC LIMIT 1').get();
462
+ }
463
+ if (!checkpoint) {
464
+ return {
465
+ content: [{
466
+ type: 'text',
467
+ text: 'No checkpoint found',
468
+ }],
469
+ };
470
+ }
471
+ const cp = checkpoint;
472
+ // Start new session from checkpoint
473
+ const newSessionId = (0, uuid_1.v4)();
474
+ db.prepare(`
475
+ INSERT INTO sessions (id, name, description, branch)
476
+ VALUES (?, ?, ?, ?)
477
+ `).run(newSessionId, `Restored from: ${cp.name}`, `Checkpoint ${cp.id.substring(0, 8)} created at ${cp.created_at}`, cp.git_branch);
478
+ // Restore context items
479
+ const contextItems = db.prepare(`
480
+ SELECT ci.* FROM context_items ci
481
+ JOIN checkpoint_items cpi ON ci.id = cpi.context_item_id
482
+ WHERE cpi.checkpoint_id = ?
483
+ `).all(cp.id);
484
+ const itemStmt = db.prepare(`
485
+ INSERT INTO context_items (id, session_id, key, value, category, priority, created_at)
486
+ VALUES (?, ?, ?, ?, ?, ?, ?)
487
+ `);
488
+ for (const item of contextItems) {
489
+ itemStmt.run((0, uuid_1.v4)(), newSessionId, item.key, item.value, item.category, item.priority, item.created_at);
490
+ }
491
+ // Restore file cache if requested
492
+ let fileCount = 0;
493
+ if (restoreFiles) {
494
+ const files = db.prepare(`
495
+ SELECT fc.* FROM file_cache fc
496
+ JOIN checkpoint_files cpf ON fc.id = cpf.file_cache_id
497
+ WHERE cpf.checkpoint_id = ?
498
+ `).all(cp.id);
499
+ const fileStmt = db.prepare(`
500
+ INSERT INTO file_cache (id, session_id, file_path, content, hash, last_read)
501
+ VALUES (?, ?, ?, ?, ?, ?)
502
+ `);
503
+ for (const file of files) {
504
+ fileStmt.run((0, uuid_1.v4)(), newSessionId, file.file_path, file.content, file.hash, file.last_read);
505
+ fileCount++;
506
+ }
507
+ }
508
+ currentSessionId = newSessionId;
509
+ return {
510
+ content: [{
511
+ type: 'text',
512
+ text: `Restored from checkpoint: ${cp.name}
513
+ New session: ${newSessionId.substring(0, 8)}
514
+ Context items restored: ${contextItems.length}
515
+ Files restored: ${fileCount}
516
+ Original git branch: ${cp.git_branch || 'none'}
517
+ Original checkpoint created: ${cp.created_at}`,
518
+ }],
519
+ };
520
+ }
521
+ // Phase 2: Summarization
522
+ case 'context_summarize': {
523
+ const { sessionId: specificSessionId, categories, maxLength } = args;
524
+ const targetSessionId = specificSessionId || currentSessionId || ensureSession();
525
+ const items = db.prepare(`
526
+ SELECT * FROM context_items
527
+ WHERE session_id = ?
528
+ ORDER BY priority DESC, created_at DESC
529
+ `).all(targetSessionId);
530
+ const summary = createSummary(items, { categories, maxLength });
531
+ return {
532
+ content: [{
533
+ type: 'text',
534
+ text: summary,
535
+ }],
536
+ };
537
+ }
538
+ default:
539
+ throw new Error(`Unknown tool: ${toolName}`);
540
+ }
541
+ });
542
+ // List available tools
543
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
544
+ return {
545
+ tools: [
546
+ // Session Management
547
+ {
548
+ name: 'context_session_start',
549
+ description: 'Start a new context session',
550
+ inputSchema: {
551
+ type: 'object',
552
+ properties: {
553
+ name: { type: 'string', description: 'Session name' },
554
+ description: { type: 'string', description: 'Session description' },
555
+ continueFrom: { type: 'string', description: 'Session ID to continue from' },
556
+ },
557
+ },
558
+ },
559
+ {
560
+ name: 'context_session_list',
561
+ description: 'List recent sessions',
562
+ inputSchema: {
563
+ type: 'object',
564
+ properties: {
565
+ limit: { type: 'number', description: 'Maximum number of sessions to return', default: 10 },
566
+ },
567
+ },
568
+ },
569
+ // Enhanced Context Storage
570
+ {
571
+ name: 'context_save',
572
+ description: 'Save a context item with optional category and priority',
573
+ inputSchema: {
574
+ type: 'object',
575
+ properties: {
576
+ key: { type: 'string', description: 'Unique key for the context item' },
577
+ value: { type: 'string', description: 'Context value to save' },
578
+ category: {
579
+ type: 'string',
580
+ description: 'Category (e.g., task, decision, progress)',
581
+ enum: ['task', 'decision', 'progress', 'note', 'error', 'warning']
582
+ },
583
+ priority: {
584
+ type: 'string',
585
+ description: 'Priority level',
586
+ enum: ['high', 'normal', 'low'],
587
+ default: 'normal'
588
+ },
589
+ },
590
+ required: ['key', 'value'],
591
+ },
592
+ },
593
+ {
594
+ name: 'context_get',
595
+ description: 'Retrieve saved context by key, category, or session',
596
+ inputSchema: {
597
+ type: 'object',
598
+ properties: {
599
+ key: { type: 'string', description: 'Specific key to retrieve' },
600
+ category: { type: 'string', description: 'Filter by category' },
601
+ sessionId: { type: 'string', description: 'Specific session ID (defaults to current)' },
602
+ },
603
+ },
604
+ },
605
+ // File Caching
606
+ {
607
+ name: 'context_cache_file',
608
+ description: 'Cache file content with hash for change detection',
609
+ inputSchema: {
610
+ type: 'object',
611
+ properties: {
612
+ filePath: { type: 'string', description: 'Path to the file' },
613
+ content: { type: 'string', description: 'File content to cache' },
614
+ },
615
+ required: ['filePath', 'content'],
616
+ },
617
+ },
618
+ {
619
+ name: 'context_file_changed',
620
+ description: 'Check if a file has changed since it was cached',
621
+ inputSchema: {
622
+ type: 'object',
623
+ properties: {
624
+ filePath: { type: 'string', description: 'Path to the file' },
625
+ currentContent: { type: 'string', description: 'Current file content to compare' },
626
+ },
627
+ required: ['filePath'],
628
+ },
629
+ },
630
+ // Status
631
+ {
632
+ name: 'context_status',
633
+ description: 'Get current context status and statistics',
634
+ inputSchema: {
635
+ type: 'object',
636
+ properties: {},
637
+ },
638
+ },
639
+ // Phase 2: Checkpoint System
640
+ {
641
+ name: 'context_checkpoint',
642
+ description: 'Create a named checkpoint of current context',
643
+ inputSchema: {
644
+ type: 'object',
645
+ properties: {
646
+ name: { type: 'string', description: 'Checkpoint name' },
647
+ description: { type: 'string', description: 'Checkpoint description' },
648
+ includeFiles: {
649
+ type: 'boolean',
650
+ description: 'Include cached files in checkpoint',
651
+ default: true
652
+ },
653
+ includeGitStatus: {
654
+ type: 'boolean',
655
+ description: 'Capture current git status',
656
+ default: true
657
+ },
658
+ },
659
+ required: ['name'],
660
+ },
661
+ },
662
+ {
663
+ name: 'context_restore_checkpoint',
664
+ description: 'Restore context from a checkpoint',
665
+ inputSchema: {
666
+ type: 'object',
667
+ properties: {
668
+ name: { type: 'string', description: 'Checkpoint name to restore' },
669
+ checkpointId: { type: 'string', description: 'Specific checkpoint ID' },
670
+ restoreFiles: {
671
+ type: 'boolean',
672
+ description: 'Restore cached files',
673
+ default: true
674
+ },
675
+ },
676
+ },
677
+ },
678
+ // Phase 2: Summarization
679
+ {
680
+ name: 'context_summarize',
681
+ description: 'Get AI-friendly summary of session context',
682
+ inputSchema: {
683
+ type: 'object',
684
+ properties: {
685
+ sessionId: { type: 'string', description: 'Session to summarize (defaults to current)' },
686
+ categories: {
687
+ type: 'array',
688
+ items: { type: 'string' },
689
+ description: 'Filter by specific categories'
690
+ },
691
+ maxLength: {
692
+ type: 'number',
693
+ description: 'Maximum summary length',
694
+ default: 1000
695
+ },
696
+ },
697
+ },
698
+ },
699
+ ],
700
+ };
701
+ });
702
+ // Start server
703
+ const transport = new stdio_js_1.StdioServerTransport();
704
+ server.connect(transport);