mcp-memory-bucket 0.8.2 → 0.8.3
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/dist/src/server.js
CHANGED
|
@@ -38,13 +38,22 @@ const PORT = process.env.PORT ? Number(process.env.PORT) : 8767;
|
|
|
38
38
|
const config = loadConfig();
|
|
39
39
|
const db = openCache(config.cacheDbPath);
|
|
40
40
|
const identity = new IdentityTracker(config.folderfooMode);
|
|
41
|
-
|
|
41
|
+
// Built ONCE and shared by reference with SkillRepository below — registerRemoteFolder/addFolder
|
|
42
|
+
// push into this same array in place, so skillSpec.sources (used by initialScan/watchSources/
|
|
43
|
+
// startRemotePolling/pollOne's upsertFile) sees a folder added live, without a restart. Evaluating
|
|
44
|
+
// this array literal separately for skillSpec and for `new SkillRepository(...)` (as before) creates
|
|
45
|
+
// two distinct arrays that fall out of sync the moment a folder is added live: upsertFile's
|
|
46
|
+
// folderForFile lookup against the stale skillSpec.sources then finds no match and stamps folder=""
|
|
47
|
+
// on every doc from that folder — permanently, since upsertFile's mtime-based skip check means the
|
|
48
|
+
// row is never reprocessed to pick up the correct value once the array is finally in sync.
|
|
49
|
+
const skillFolders = [{ name: 'builtin', path: builtinSkillsDir }, ...config.skillFolders];
|
|
50
|
+
const skillSpec = skillSyncSpec(skillFolders);
|
|
42
51
|
const memorySpec = memorySyncSpec(config.memoryFolders);
|
|
43
52
|
initialScan(db, skillSpec);
|
|
44
53
|
initialScan(db, memorySpec);
|
|
45
54
|
const skillWatcher = watchSources(db, skillSpec);
|
|
46
55
|
const memoryWatcher = watchSources(db, memorySpec);
|
|
47
|
-
const skillRepo = new SkillRepository(db,
|
|
56
|
+
const skillRepo = new SkillRepository(db, skillFolders, config.remoteSkillFolders, config.baseDir, identity);
|
|
48
57
|
const memoryRepo = new MemoryRepository(db, config.memoryFolders, config.remoteMemoryFolders, config.baseDir, identity);
|
|
49
58
|
skillRepo.setWatcher(skillWatcher);
|
|
50
59
|
memoryRepo.setWatcher(memoryWatcher);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-memory-bucket",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server exposing skill_* (reusable coding patterns) and memory_* (point-in-time working context) tools over a markdown+frontmatter source, cached into SQLite at runtime.",
|
|
6
6
|
"repository": {
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { saveRoot, removeRoot as removeRootFromConfig, sanitizeRootName } from '../config.js';
|
|
5
|
-
import { initialScan } from '../store/sync.js';
|
|
6
|
-
const KIND = z.enum(['skill', 'memory']);
|
|
7
|
-
export function registerBucketRootTools(mcp, config, skillRepo, memoryRepo, db, skillSpec, memorySpec) {
|
|
8
|
-
mcp.tool('bucket_list_roots', 'Lists the configured skill and memory roots (the named source directories skills/memory docs live under, e.g. "super-skills", "demo-skills", "builtin") — use this to see what roots exist before passing a `root` argument to a create/list/search tool, or before adding/removing one.', {}, async () => {
|
|
9
|
-
const skill = skillRepo.listRoots().map((r) => ({ ...r, kind: 'skill' }));
|
|
10
|
-
const memory = memoryRepo.listRoots().map((r) => ({ ...r, kind: 'memory' }));
|
|
11
|
-
return { content: [{ type: 'text', text: JSON.stringify({ skill, memory }, null, 2) }] };
|
|
12
|
-
});
|
|
13
|
-
mcp.tool('bucket_create_root', 'Registers a new skill or memory root: an existing absolute directory path becomes a new named source that skill_create/memory_create can target via `root`. Scans it once and starts watching it live — never creates the directory itself, it must already exist.', {
|
|
14
|
-
kind: KIND,
|
|
15
|
-
path: z.string().describe('absolute path to an existing directory'),
|
|
16
|
-
name: z.string().optional().describe('name for the root; defaults to a sanitized version of the directory\'s basename'),
|
|
17
|
-
}, async ({ kind, path: dirPath, name }) => {
|
|
18
|
-
try {
|
|
19
|
-
if (!path.isAbsolute(dirPath)) {
|
|
20
|
-
return { content: [{ type: 'text', text: 'path must be an absolute directory path' }], isError: true };
|
|
21
|
-
}
|
|
22
|
-
if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
|
|
23
|
-
return { content: [{ type: 'text', text: `not a directory: ${dirPath}` }], isError: true };
|
|
24
|
-
}
|
|
25
|
-
const rootName = sanitizeRootName(name || path.basename(dirPath));
|
|
26
|
-
if (!rootName) {
|
|
27
|
-
return { content: [{ type: 'text', text: 'could not derive a valid root name — provide one explicitly' }], isError: true };
|
|
28
|
-
}
|
|
29
|
-
const repo = kind === 'skill' ? skillRepo : memoryRepo;
|
|
30
|
-
repo.addRoot({ name: rootName, path: dirPath });
|
|
31
|
-
saveRoot(config, kind, { name: rootName, path: dirPath });
|
|
32
|
-
return { content: [{ type: 'text', text: JSON.stringify({ name: rootName, path: dirPath, kind }, null, 2) }] };
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
return { content: [{ type: 'text', text: err.message }], isError: true };
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
mcp.tool('bucket_delete_root', 'Unregisters a skill or memory root by name: stops watching it and drops its cached entries from the index. Never touches files on disk — the directory and its contents are left in place.', { kind: KIND, name: z.string() }, async ({ kind, name }) => {
|
|
39
|
-
try {
|
|
40
|
-
const repo = kind === 'skill' ? skillRepo : memoryRepo;
|
|
41
|
-
repo.removeRoot(name);
|
|
42
|
-
removeRootFromConfig(config, kind, name);
|
|
43
|
-
return { content: [{ type: 'text', text: `Removed ${kind} root "${name}"` }] };
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
return { content: [{ type: 'text', text: err.message }], isError: true };
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
mcp.tool('bucket_rebuild_cache', 'EMERGENCY USE ONLY. Wipes the entire SQLite cache (all skills, memory docs, the full-text search index, and the date index) and rebuilds it from scratch by rescanning every configured root from disk. Source markdown files on disk are never touched — this only affects the derived cache, which is always safe to discard and regenerate. Use this only when other tools return results that contradict what you can see in the actual files (e.g. stale search hits, a doc that clearly exists on disk but skill_get/memory_get can\'t find, or search_by_date returning wrong dates) and a normal create/update/relocate call hasn\'t resolved it — this is a last resort, not a routine maintenance step. Takes a moment to complete on a large root; nothing else should be called until it returns.', {}, async () => {
|
|
50
|
-
try {
|
|
51
|
-
db.exec(`DELETE FROM skills; DELETE FROM memory_docs; DELETE FROM search_index; DELETE FROM doc_dates;`);
|
|
52
|
-
initialScan(db, skillSpec);
|
|
53
|
-
initialScan(db, memorySpec);
|
|
54
|
-
const skillCount = db.prepare(`SELECT COUNT(*) AS n FROM skills`).get().n;
|
|
55
|
-
const memoryCount = db.prepare(`SELECT COUNT(*) AS n FROM memory_docs`).get().n;
|
|
56
|
-
return {
|
|
57
|
-
content: [
|
|
58
|
-
{ type: 'text', text: `Cache rebuilt from disk: ${skillCount} skill(s), ${memoryCount} memory doc(s) reindexed.` },
|
|
59
|
-
],
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
return { content: [{ type: 'text', text: err.message }], isError: true };
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|