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 +18 -1
- package/README.md +33 -1
- package/dist/__tests__/integration/git-integration.test.js +1 -1
- package/dist/__tests__/integration/server-initialization.test.js +4 -6
- package/dist/index.js +20 -1
- package/package.json +1 -1
- package/dist/__tests__/integration/issue-token-limit-channel-query.test.js +0 -128
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,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
|
-
});
|