mcp-memory-keeper 0.12.0 → 0.12.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.12.1] - 2026-03-24
11
+
12
+ ### Fixed
13
+
14
+ - **Server crashes with SQLITE_CANTOPEN when parent process CWD changes** (#31)
15
+ - Server now resolves database path to an absolute location (`$DATA_DIR` or `~/mcp-data/memory-keeper/`) instead of relying on CWD
16
+ - Added try/catch around data directory creation with actionable error message
17
+ - Startup warning with exact `cp` command when legacy `context.db` detected in CWD
18
+ - README "from source" install command now points to `bin/mcp-memory-keeper` instead of `node dist/index.js`
19
+ - Added Upgrading section documenting database path change and migration steps
20
+
21
+ ### Technical
22
+
23
+ - Fixed integration tests to use `DATA_DIR` instead of dead `MCP_DB_PATH` environment variable
24
+ - Fixed `git.init()` in tests to use `--initial-branch=master` for deterministic behavior
25
+
10
26
  ## [0.12.0] - 2026-02-06
11
27
 
12
28
  ### Added
@@ -491,7 +507,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
491
507
  - **Security**: Security updates
492
508
  - **Technical**: Internal improvements
493
509
 
494
- [Unreleased]: https://github.com/mkreyman/mcp-memory-keeper/compare/v0.12.0...HEAD
510
+ [Unreleased]: https://github.com/mkreyman/mcp-memory-keeper/compare/v0.12.1...HEAD
511
+ [0.12.1]: https://github.com/mkreyman/mcp-memory-keeper/compare/v0.12.0...v0.12.1
495
512
  [0.12.0]: https://github.com/mkreyman/mcp-memory-keeper/compare/v0.11.0...v0.12.0
496
513
  [0.11.0]: https://github.com/mkreyman/mcp-memory-keeper/compare/v0.10.2...v0.11.0
497
514
  [0.10.2]: https://github.com/mkreyman/mcp-memory-keeper/compare/v0.10.1...v0.10.2
package/README.md CHANGED
@@ -174,7 +174,7 @@ npm install
174
174
  npm run build
175
175
 
176
176
  # 4. Add to Claude
177
- claude mcp add memory-keeper node /absolute/path/to/mcp-memory-keeper/dist/index.js
177
+ claude mcp add memory-keeper /absolute/path/to/mcp-memory-keeper/bin/mcp-memory-keeper
178
178
  ```
179
179
 
180
180
  </details>
@@ -1212,6 +1212,38 @@ Test categories:
1212
1212
  - [ ] Custom context templates
1213
1213
  - [ ] Automatic retention policies
1214
1214
 
1215
+ ## Upgrading
1216
+
1217
+ ### Database path change (v0.12.x+)
1218
+
1219
+ Prior to this release, the server resolved `context.db` relative to the process's current working directory. The database now lives at an absolute path:
1220
+
1221
+ - **Default:** `~/mcp-data/memory-keeper/context.db`
1222
+ - **Custom:** set `DATA_DIR=/your/path` — the server will use `$DATA_DIR/context.db`
1223
+
1224
+ If you have existing data in a `context.db` in your old working directory, move it to the new location before restarting the server:
1225
+
1226
+ ```bash
1227
+ mkdir -p ~/mcp-data/memory-keeper
1228
+ cp /path/to/old/context.db ~/mcp-data/memory-keeper/context.db
1229
+ ```
1230
+
1231
+ If `DATA_DIR` is set, use that path as the destination instead of `~/mcp-data/memory-keeper/`.
1232
+
1233
+ The server will print a warning to stderr if it detects a `context.db` in the current directory that differs from the configured data directory, including the exact `cp` command to run.
1234
+
1235
+ ### From-source install command change
1236
+
1237
+ If you registered memory-keeper using `node dist/index.js` directly, update your MCP config to use the bin wrapper instead:
1238
+
1239
+ ```bash
1240
+ # remove the old entry
1241
+ claude mcp remove memory-keeper
1242
+
1243
+ # add the updated entry
1244
+ claude mcp add memory-keeper /absolute/path/to/mcp-memory-keeper/bin/mcp-memory-keeper
1245
+ ```
1246
+
1215
1247
  ## Contributing
1216
1248
 
1217
1249
  Contributions are welcome! Please feel free to submit a Pull Request.
@@ -55,7 +55,7 @@ describe('Git Integration Tests', () => {
55
55
  // Create and initialize a real git repo for testing
56
56
  fs.mkdirSync(tempRepoPath, { recursive: true });
57
57
  git = (0, simple_git_1.simpleGit)(tempRepoPath);
58
- await git.init();
58
+ await git.init(['--initial-branch=master']);
59
59
  await git.addConfig('user.name', 'Test User');
60
60
  await git.addConfig('user.email', 'test@example.com');
61
61
  // Use repo-local hooks directory to prevent global hooks from interfering
@@ -86,12 +86,11 @@ const os = __importStar(require("os"));
86
86
  }
87
87
  });
88
88
  (0, globals_1.it)('should start server and respond to initialize request', done => {
89
- const dbPath = path.join(tempDir, 'test.db');
90
89
  // Start the server
91
90
  serverProcess = (0, child_process_1.spawn)('node', [path.join(__dirname, '../../../dist/index.js')], {
92
91
  env: {
93
92
  ...process.env,
94
- MCP_DB_PATH: dbPath,
93
+ DATA_DIR: tempDir,
95
94
  },
96
95
  stdio: ['pipe', 'pipe', 'pipe'],
97
96
  });
@@ -161,6 +160,7 @@ const os = __importStar(require("os"));
161
160
  // Start the server
162
161
  serverProcess = (0, child_process_1.spawn)('node', [path.join(__dirname, '../../../dist/index.js')], {
163
162
  stdio: ['pipe', 'pipe', 'pipe'],
163
+ env: { ...process.env, DATA_DIR: tempDir },
164
164
  });
165
165
  // Track for global cleanup
166
166
  if (!global.testProcesses) {
@@ -190,11 +190,10 @@ const os = __importStar(require("os"));
190
190
  }
191
191
  });
192
192
  (0, globals_1.it)('should handle invalid requests gracefully', done => {
193
- const dbPath = path.join(tempDir, 'test.db');
194
193
  serverProcess = (0, child_process_1.spawn)('node', [path.join(__dirname, '../../../dist/index.js')], {
195
194
  env: {
196
195
  ...process.env,
197
- MCP_DB_PATH: dbPath,
196
+ DATA_DIR: tempDir,
198
197
  },
199
198
  stdio: ['pipe', 'pipe', 'pipe'],
200
199
  });
@@ -256,11 +255,10 @@ const os = __importStar(require("os"));
256
255
  }, 5000);
257
256
  });
258
257
  (0, globals_1.it)('should handle server shutdown gracefully', done => {
259
- const dbPath = path.join(tempDir, 'test.db');
260
258
  serverProcess = (0, child_process_1.spawn)('node', [path.join(__dirname, '../../../dist/index.js')], {
261
259
  env: {
262
260
  ...process.env,
263
- MCP_DB_PATH: dbPath,
261
+ DATA_DIR: tempDir,
264
262
  },
265
263
  stdio: ['pipe', 'pipe', 'pipe'],
266
264
  });
package/dist/index.js CHANGED
@@ -58,7 +58,26 @@ const token_limits_js_1 = require("./utils/token-limits.js");
58
58
  const contextWatchHandlers_js_1 = require("./handlers/contextWatchHandlers.js");
59
59
  const tool_profiles_js_1 = require("./utils/tool-profiles.js");
60
60
  // Initialize database with migrations
61
- const dbManager = new database_js_1.DatabaseManager({ filename: 'context.db' });
61
+ const dataDir = process.env.DATA_DIR
62
+ ? path.resolve(process.env.DATA_DIR)
63
+ : path.join(os.homedir(), 'mcp-data', 'memory-keeper');
64
+ try {
65
+ fs.mkdirSync(dataDir, { recursive: true });
66
+ }
67
+ catch (err) {
68
+ console.error(`[memory-keeper] FATAL: Cannot create data directory "${dataDir}": ${err.message}\n` +
69
+ `Set DATA_DIR to a writable location or create the directory manually.`);
70
+ process.exit(1);
71
+ }
72
+ // Warn users whose legacy DB is sitting in CWD
73
+ const legacyDb = path.join(process.cwd(), 'context.db');
74
+ if (process.cwd() !== dataDir && fs.existsSync(legacyDb)) {
75
+ console.error(`[memory-keeper] WARNING: context.db found in current directory but the ` +
76
+ `database now lives in ${dataDir}. ` +
77
+ `To preserve your data, run:\n` +
78
+ ` cp "${legacyDb}" "${path.join(dataDir, 'context.db')}"`);
79
+ }
80
+ const dbManager = new database_js_1.DatabaseManager({ filename: path.join(dataDir, 'context.db') });
62
81
  exports.dbManager = dbManager;
63
82
  const db = dbManager.getDatabase();
64
83
  // Initialize repository manager
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-memory-keeper",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "MCP server for persistent context management in AI coding assistants",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,128 +0,0 @@
1
- "use strict";
2
- /**
3
- * Test for token limit issue with channel queries
4
- *
5
- * Reproduces the bug where context_get with limit: 50 still exceeds token limits
6
- * when querying a channel with large items.
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- const globals_1 = require("@jest/globals");
10
- const database_js_1 = require("../../utils/database.js");
11
- const RepositoryManager_js_1 = require("../../repositories/RepositoryManager.js");
12
- const token_limits_js_1 = require("../../utils/token-limits.js");
13
- (0, globals_1.describe)('Token Limit with Channel Query Bug', () => {
14
- let dbManager;
15
- let repositories;
16
- let sessionId;
17
- (0, globals_1.beforeEach)(() => {
18
- dbManager = new database_js_1.DatabaseManager({ filename: ':memory:' });
19
- repositories = new RepositoryManager_js_1.RepositoryManager(dbManager);
20
- const session = repositories.sessions.create({
21
- name: 'Test Session',
22
- defaultChannel: 'test-channel',
23
- });
24
- sessionId = session.id;
25
- });
26
- (0, globals_1.afterEach)(() => {
27
- dbManager.close();
28
- });
29
- (0, globals_1.it)('should enforce token limits even when limit parameter is provided', () => {
30
- // Create 50 large context items (each ~1000 chars)
31
- const largeValue = 'x'.repeat(1000);
32
- for (let i = 0; i < 50; i++) {
33
- repositories.contexts.save(sessionId, {
34
- key: `large-item-${i}`,
35
- value: largeValue,
36
- channel: 'outbound-call-center',
37
- priority: 'normal',
38
- });
39
- }
40
- // Query with limit: 50
41
- const result = repositories.contexts.queryEnhanced({
42
- sessionId,
43
- channel: 'outbound-call-center',
44
- limit: 50,
45
- sort: 'updated_desc',
46
- includeMetadata: false,
47
- });
48
- (0, globals_1.expect)(result.items.length).toBe(50);
49
- // Check if response would exceed token limit
50
- const tokenConfig = (0, token_limits_js_1.getTokenConfig)();
51
- const { exceedsLimit, safeItemCount } = (0, token_limits_js_1.checkTokenLimit)(result.items, false, tokenConfig);
52
- // Build the actual response structure
53
- const response = {
54
- items: result.items,
55
- pagination: {
56
- total: result.totalCount,
57
- returned: result.items.length,
58
- offset: 0,
59
- hasMore: false,
60
- nextOffset: null,
61
- truncated: false,
62
- truncatedCount: 0,
63
- },
64
- };
65
- const responseJson = JSON.stringify(response, null, 2);
66
- const actualTokens = Math.ceil(responseJson.length / tokenConfig.charsPerToken);
67
- // The bug: even with limit: 50, the response can exceed token limits
68
- if (actualTokens > tokenConfig.mcpMaxTokens) {
69
- // BUG REPRODUCED: The response exceeds token limits
70
- // Verify that checkTokenLimit correctly detected the issue
71
- (0, globals_1.expect)(exceedsLimit).toBe(true);
72
- (0, globals_1.expect)(safeItemCount).toBeLessThan(50);
73
- (0, globals_1.expect)(actualTokens).toBeGreaterThan(tokenConfig.mcpMaxTokens);
74
- }
75
- });
76
- (0, globals_1.it)('should respect token limits over user-provided limit parameter', () => {
77
- // Create 100 large context items (each ~800 chars)
78
- const largeValue = 'y'.repeat(800);
79
- for (let i = 0; i < 100; i++) {
80
- repositories.contexts.save(sessionId, {
81
- key: `item-${i}`,
82
- value: largeValue,
83
- channel: 'test-channel',
84
- priority: 'normal',
85
- });
86
- }
87
- // Query with limit: 50 (user expectation)
88
- const result = repositories.contexts.queryEnhanced({
89
- sessionId,
90
- channel: 'test-channel',
91
- limit: 50,
92
- sort: 'created_desc',
93
- includeMetadata: false,
94
- });
95
- // Simulate the context_get handler logic
96
- const tokenConfig = (0, token_limits_js_1.getTokenConfig)();
97
- const { exceedsLimit, safeItemCount } = (0, token_limits_js_1.checkTokenLimit)(result.items, false, tokenConfig);
98
- let actualItems = result.items;
99
- let wasTruncated = false;
100
- if (exceedsLimit && safeItemCount < result.items.length) {
101
- actualItems = result.items.slice(0, safeItemCount);
102
- wasTruncated = true;
103
- }
104
- // Build response
105
- const response = {
106
- items: actualItems,
107
- pagination: {
108
- total: result.totalCount,
109
- returned: actualItems.length,
110
- offset: 0,
111
- hasMore: wasTruncated || actualItems.length < result.totalCount,
112
- nextOffset: wasTruncated ? actualItems.length : null,
113
- truncated: wasTruncated,
114
- truncatedCount: wasTruncated ? result.items.length - actualItems.length : 0,
115
- },
116
- };
117
- const responseJson = JSON.stringify(response, null, 2);
118
- const actualTokens = Math.ceil(responseJson.length / tokenConfig.charsPerToken);
119
- // Verify token limit is not exceeded
120
- (0, globals_1.expect)(actualTokens).toBeLessThanOrEqual(tokenConfig.mcpMaxTokens);
121
- // Verify truncation occurred if needed
122
- if (exceedsLimit) {
123
- (0, globals_1.expect)(wasTruncated).toBe(true);
124
- (0, globals_1.expect)(actualItems.length).toBeLessThan(50);
125
- (0, globals_1.expect)(actualItems.length).toBe(safeItemCount);
126
- }
127
- });
128
- });