@prom.codes/memory-mcp 0.10.3 → 0.11.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/README.md +3 -2
- package/dist/bin.js +274 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,8 +51,9 @@ your home dir. Open a project folder so memories scope correctly.
|
|
|
51
51
|
Tools (docked as `memory`): `memory_read`, `memory_write`, `memory_capture`,
|
|
52
52
|
`memory_search`, `memory_list`, `memory_delete`, `memory_setup`,
|
|
53
53
|
`memory_status` (health check: which folder, how many records, does the key
|
|
54
|
-
work?). Secrets are rejected on every write.
|
|
55
|
-
machine (only short query/record text
|
|
54
|
+
work, is a newer version published?). Secrets are rejected on every write.
|
|
55
|
+
Your memories never leave your machine (only short query/record text
|
|
56
|
+
transits when embeddings are enabled).
|
|
56
57
|
|
|
57
58
|
## Native modules
|
|
58
59
|
|
package/dist/bin.js
CHANGED
|
@@ -34,10 +34,11 @@ var LANGUAGE_IDS = [
|
|
|
34
34
|
];
|
|
35
35
|
|
|
36
36
|
// ../shared/dist/update-check.js
|
|
37
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
37
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
38
38
|
import { homedir } from "node:os";
|
|
39
39
|
import { join } from "node:path";
|
|
40
40
|
import { fileURLToPath } from "node:url";
|
|
41
|
+
var UPGRADE_COMMAND = "npm install -g @prom.codes/context-mcp @prom.codes/memory-mcp @prom.codes/saver --ignore-scripts=false --foreground-scripts";
|
|
41
42
|
async function packageIdentity(binImportMetaUrl) {
|
|
42
43
|
try {
|
|
43
44
|
const binPath = fileURLToPath(binImportMetaUrl);
|
|
@@ -88,10 +89,36 @@ function isNewerVersion(latest, current) {
|
|
|
88
89
|
return true;
|
|
89
90
|
return false;
|
|
90
91
|
}
|
|
92
|
+
function cachedLatestIsStale(cachedLatest, minVersion) {
|
|
93
|
+
return minVersion !== void 0 && cachedLatest !== null && isNewerVersion(minVersion, cachedLatest);
|
|
94
|
+
}
|
|
91
95
|
function cachePath(dir, name) {
|
|
92
96
|
const safe = name.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
93
97
|
return join(dir, `.update-check-${safe}.json`);
|
|
94
98
|
}
|
|
99
|
+
function availabilityMarkerPath(dir, name) {
|
|
100
|
+
const safe = name.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
101
|
+
return join(dir, `.update-available-${safe}.json`);
|
|
102
|
+
}
|
|
103
|
+
async function syncAvailabilityMarker(dir, name, current, latest, updateAvailable) {
|
|
104
|
+
const file = availabilityMarkerPath(dir, name);
|
|
105
|
+
try {
|
|
106
|
+
if (updateAvailable && latest !== null) {
|
|
107
|
+
const marker = {
|
|
108
|
+
name,
|
|
109
|
+
current,
|
|
110
|
+
latest,
|
|
111
|
+
command: UPGRADE_COMMAND,
|
|
112
|
+
notedAt: Date.now()
|
|
113
|
+
};
|
|
114
|
+
await mkdir(dir, { recursive: true }).catch(() => void 0);
|
|
115
|
+
await writeFile(file, JSON.stringify(marker), "utf8");
|
|
116
|
+
} else if (latest !== null) {
|
|
117
|
+
await rm(file, { force: true });
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
}
|
|
95
122
|
async function readCache(path2) {
|
|
96
123
|
try {
|
|
97
124
|
const raw = await readFile(path2, "utf8");
|
|
@@ -120,7 +147,7 @@ async function fetchLatest(name, fetchImpl, timeoutMs) {
|
|
|
120
147
|
const url = `https://registry.npmjs.org/${name.replace("/", "%2F")}/latest`;
|
|
121
148
|
const res = await fetchImpl(url, {
|
|
122
149
|
signal: controller.signal,
|
|
123
|
-
headers: { accept: "application/
|
|
150
|
+
headers: { accept: "application/json" }
|
|
124
151
|
});
|
|
125
152
|
if (!res.ok)
|
|
126
153
|
return null;
|
|
@@ -151,10 +178,11 @@ async function checkForUpdate(options) {
|
|
|
151
178
|
const now = Date.now();
|
|
152
179
|
if (!force) {
|
|
153
180
|
const cached = await readCache(file);
|
|
154
|
-
if (cached !== null && now - cached.checkedAt < cacheTtlMs) {
|
|
181
|
+
if (cached !== null && now - cached.checkedAt < cacheTtlMs && !cachedLatestIsStale(cached.latest, version)) {
|
|
155
182
|
const updateAvailable2 = cached.latest !== null && isNewerVersion(cached.latest, version);
|
|
156
183
|
if (updateAvailable2)
|
|
157
184
|
notify(log, name, version, cached.latest);
|
|
185
|
+
await syncAvailabilityMarker(cacheDir, name, version, cached.latest, updateAvailable2);
|
|
158
186
|
return {
|
|
159
187
|
...base,
|
|
160
188
|
latest: cached.latest,
|
|
@@ -173,13 +201,64 @@ async function checkForUpdate(options) {
|
|
|
173
201
|
const updateAvailable = isNewerVersion(latest, version);
|
|
174
202
|
if (updateAvailable)
|
|
175
203
|
notify(log, name, version, latest);
|
|
204
|
+
await syncAvailabilityMarker(cacheDir, name, version, latest, updateAvailable);
|
|
176
205
|
return { ...base, latest, checked: true, updateAvailable };
|
|
177
206
|
}
|
|
207
|
+
async function getLatestVersion(name, options = {}) {
|
|
208
|
+
const { env = process.env, fetch: fetchImpl = globalThis.fetch, cacheDir = join(homedir(), ".prometheus"), cacheTtlMs = DEFAULT_TTL_MS, timeoutMs = DEFAULT_TIMEOUT_MS, minVersion } = options;
|
|
209
|
+
const file = cachePath(cacheDir, name);
|
|
210
|
+
const cached = await readCache(file);
|
|
211
|
+
const now = Date.now();
|
|
212
|
+
const stale = cachedLatestIsStale(cached?.latest ?? null, minVersion);
|
|
213
|
+
if (cached !== null && cached.latest !== null && now - cached.checkedAt < cacheTtlMs && !stale) {
|
|
214
|
+
return cached.latest;
|
|
215
|
+
}
|
|
216
|
+
if (OPT_OUT_RE.test(env.PROMETHEUS_NO_UPDATE_CHECK ?? "") || typeof fetchImpl !== "function") {
|
|
217
|
+
return stale ? null : cached?.latest ?? null;
|
|
218
|
+
}
|
|
219
|
+
const latest = await fetchLatest(name, fetchImpl, timeoutMs);
|
|
220
|
+
if (latest !== null) {
|
|
221
|
+
await mkdir(cacheDir, { recursive: true }).catch(() => void 0);
|
|
222
|
+
await writeCache(file, { checkedAt: now, latest });
|
|
223
|
+
return latest;
|
|
224
|
+
}
|
|
225
|
+
return stale ? null : cached?.latest ?? null;
|
|
226
|
+
}
|
|
178
227
|
function notify(log, name, current, latest) {
|
|
179
228
|
log(`${name}: a newer version (${latest}) is available \u2014 you are on ${current}. npx users get it automatically on the next restart; for a global install run \`npm update -g ${name}\`. (Set PROMETHEUS_NO_UPDATE_CHECK=1 to silence.)
|
|
180
229
|
`);
|
|
181
230
|
}
|
|
182
231
|
|
|
232
|
+
// ../shared/dist/update-info.js
|
|
233
|
+
async function buildUpdateStatus(pkgName, currentVersion, options = {}) {
|
|
234
|
+
const base = { current: currentVersion, command: UPGRADE_COMMAND };
|
|
235
|
+
if (options.isDevBuild === true) {
|
|
236
|
+
return {
|
|
237
|
+
...base,
|
|
238
|
+
latest: null,
|
|
239
|
+
updateAvailable: null,
|
|
240
|
+
note: "dev build (workspace) \u2014 version comparison skipped"
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
let latest = null;
|
|
244
|
+
try {
|
|
245
|
+
latest = await getLatestVersion(pkgName, {
|
|
246
|
+
...options.env !== void 0 ? { env: options.env } : {},
|
|
247
|
+
...options.fetch !== void 0 ? { fetch: options.fetch } : {},
|
|
248
|
+
...options.cacheDir !== void 0 ? { cacheDir: options.cacheDir } : {},
|
|
249
|
+
timeoutMs: options.timeoutMs ?? 1500,
|
|
250
|
+
minVersion: currentVersion
|
|
251
|
+
});
|
|
252
|
+
} catch {
|
|
253
|
+
latest = null;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
...base,
|
|
257
|
+
latest,
|
|
258
|
+
updateAvailable: latest !== null ? isNewerVersion(latest, currentVersion) : null
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
183
262
|
// ../shared/dist/workspace-root.js
|
|
184
263
|
import { homedir as homedir2 } from "node:os";
|
|
185
264
|
import { dirname, resolve } from "node:path";
|
|
@@ -194,10 +273,131 @@ function isHomeOrFilesystemRoot(root) {
|
|
|
194
273
|
return false;
|
|
195
274
|
}
|
|
196
275
|
|
|
276
|
+
// ../shared/dist/heartbeat.js
|
|
277
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
278
|
+
import { homedir as homedir3 } from "node:os";
|
|
279
|
+
import { join as join2 } from "node:path";
|
|
280
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
281
|
+
var STALE_AFTER_MS = 5 * 6e4;
|
|
282
|
+
function defaultStatusDir(env = process.env) {
|
|
283
|
+
const override = (env.PROMETHEUS_STATUS_DIR ?? "").trim();
|
|
284
|
+
if (override !== "")
|
|
285
|
+
return override;
|
|
286
|
+
return join2(homedir3(), ".prometheus", "status");
|
|
287
|
+
}
|
|
288
|
+
function startHeartbeat(options) {
|
|
289
|
+
const dir = options.dir ?? defaultStatusDir(options.env ?? process.env);
|
|
290
|
+
const file = join2(dir, `${options.server}-${process.pid}.json`);
|
|
291
|
+
const intervalMs = options.intervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
292
|
+
let record = {
|
|
293
|
+
server: options.server,
|
|
294
|
+
pid: process.pid,
|
|
295
|
+
version: options.version,
|
|
296
|
+
startedAt: Date.now(),
|
|
297
|
+
updatedAt: Date.now(),
|
|
298
|
+
workspaceRoot: options.workspaceRoot ?? null
|
|
299
|
+
};
|
|
300
|
+
let stopped = false;
|
|
301
|
+
const persist = () => {
|
|
302
|
+
if (stopped)
|
|
303
|
+
return;
|
|
304
|
+
try {
|
|
305
|
+
mkdirSync(dir, { recursive: true });
|
|
306
|
+
writeFileSync(file, JSON.stringify(record), "utf8");
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
const remove = () => {
|
|
311
|
+
try {
|
|
312
|
+
rmSync(file, { force: true });
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
persist();
|
|
317
|
+
const timer = setInterval(() => {
|
|
318
|
+
record = { ...record, updatedAt: Date.now() };
|
|
319
|
+
persist();
|
|
320
|
+
}, intervalMs);
|
|
321
|
+
timer.unref?.();
|
|
322
|
+
const onExit = () => {
|
|
323
|
+
stopped = true;
|
|
324
|
+
remove();
|
|
325
|
+
};
|
|
326
|
+
process.once("exit", onExit);
|
|
327
|
+
return {
|
|
328
|
+
file,
|
|
329
|
+
update(patch) {
|
|
330
|
+
if (stopped)
|
|
331
|
+
return;
|
|
332
|
+
record = { ...record, ...patch, updatedAt: Date.now() };
|
|
333
|
+
persist();
|
|
334
|
+
},
|
|
335
|
+
stop() {
|
|
336
|
+
if (stopped)
|
|
337
|
+
return;
|
|
338
|
+
stopped = true;
|
|
339
|
+
clearInterval(timer);
|
|
340
|
+
process.removeListener("exit", onExit);
|
|
341
|
+
remove();
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ../shared/dist/idle-watchdog.js
|
|
347
|
+
var DEFAULT_IDLE_EXIT_MS = 30 * 6e4;
|
|
348
|
+
var IDLE_CHECK_INTERVAL_MS = 6e4;
|
|
349
|
+
var IDLE_EXIT_ENV = "PROMETHEUS_IDLE_EXIT_MS";
|
|
350
|
+
function parseIdleExitMs(env) {
|
|
351
|
+
const raw = (env[IDLE_EXIT_ENV] ?? "").trim();
|
|
352
|
+
if (raw === "")
|
|
353
|
+
return void 0;
|
|
354
|
+
const n = Number(raw);
|
|
355
|
+
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
356
|
+
}
|
|
357
|
+
function createIdleWatchdog(options) {
|
|
358
|
+
const env = options.env ?? process.env;
|
|
359
|
+
const idleMs = options.idleMs ?? parseIdleExitMs(env) ?? DEFAULT_IDLE_EXIT_MS;
|
|
360
|
+
const now = options.now ?? Date.now;
|
|
361
|
+
const checkIntervalMs = options.checkIntervalMs ?? Math.max(1e3, Math.min(IDLE_CHECK_INTERVAL_MS, idleMs));
|
|
362
|
+
if (!(idleMs > 0)) {
|
|
363
|
+
return { touch() {
|
|
364
|
+
}, stop() {
|
|
365
|
+
}, idleMs: 0 };
|
|
366
|
+
}
|
|
367
|
+
let lastActivity = now();
|
|
368
|
+
let stopped = false;
|
|
369
|
+
let fired = false;
|
|
370
|
+
const timer = setInterval(() => {
|
|
371
|
+
if (stopped || fired)
|
|
372
|
+
return;
|
|
373
|
+
const idleFor = now() - lastActivity;
|
|
374
|
+
if (idleFor >= idleMs) {
|
|
375
|
+
fired = true;
|
|
376
|
+
clearInterval(timer);
|
|
377
|
+
options.onIdle(`idle for ${Math.round(idleFor / 1e3)}s with no client activity (set ${IDLE_EXIT_ENV}=0 to disable)`);
|
|
378
|
+
}
|
|
379
|
+
}, checkIntervalMs);
|
|
380
|
+
timer.unref?.();
|
|
381
|
+
return {
|
|
382
|
+
idleMs,
|
|
383
|
+
touch() {
|
|
384
|
+
if (stopped)
|
|
385
|
+
return;
|
|
386
|
+
lastActivity = now();
|
|
387
|
+
},
|
|
388
|
+
stop() {
|
|
389
|
+
if (stopped)
|
|
390
|
+
return;
|
|
391
|
+
stopped = true;
|
|
392
|
+
clearInterval(timer);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
197
397
|
// dist/composition.js
|
|
198
398
|
import { createHash } from "node:crypto";
|
|
199
|
-
import { homedir as
|
|
200
|
-
import { basename, join as
|
|
399
|
+
import { homedir as homedir4 } from "node:os";
|
|
400
|
+
import { basename, join as join3, resolve as resolve2 } from "node:path";
|
|
201
401
|
|
|
202
402
|
// ../embeddings-openai-compat/dist/index.js
|
|
203
403
|
var DEFAULT_BATCH = 96;
|
|
@@ -1445,7 +1645,7 @@ var OpenAICompatRewriter = class {
|
|
|
1445
1645
|
|
|
1446
1646
|
// dist/sqlite.js
|
|
1447
1647
|
import { randomUUID } from "node:crypto";
|
|
1448
|
-
import { mkdirSync } from "node:fs";
|
|
1648
|
+
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
1449
1649
|
import { dirname as dirname2 } from "node:path";
|
|
1450
1650
|
import Database from "better-sqlite3";
|
|
1451
1651
|
|
|
@@ -1773,7 +1973,7 @@ var SqliteMemoryBackend = class {
|
|
|
1773
1973
|
closed = false;
|
|
1774
1974
|
constructor(dbPath, opts = {}) {
|
|
1775
1975
|
if (dbPath !== ":memory:") {
|
|
1776
|
-
|
|
1976
|
+
mkdirSync2(dirname2(dbPath), { recursive: true });
|
|
1777
1977
|
}
|
|
1778
1978
|
this.db = new Database(dbPath);
|
|
1779
1979
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -2226,7 +2426,7 @@ function projectIdFor(workspaceRoot) {
|
|
|
2226
2426
|
return createHash("sha256").update(abs).digest("hex").slice(0, 16);
|
|
2227
2427
|
}
|
|
2228
2428
|
function defaultMemoryDbPath() {
|
|
2229
|
-
return
|
|
2429
|
+
return join3(homedir4(), ".prometheus", "memory.db");
|
|
2230
2430
|
}
|
|
2231
2431
|
function intEnv(env, name, def) {
|
|
2232
2432
|
const raw = env[name];
|
|
@@ -2667,9 +2867,9 @@ function assertNoSecrets(text) {
|
|
|
2667
2867
|
}
|
|
2668
2868
|
|
|
2669
2869
|
// dist/setup.js
|
|
2670
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2870
|
+
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
2671
2871
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2672
|
-
import { dirname as dirname3, join as
|
|
2872
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
2673
2873
|
var MEMORY_RUNTIMES = [
|
|
2674
2874
|
"claude-code",
|
|
2675
2875
|
"cursor",
|
|
@@ -2713,13 +2913,13 @@ alwaysApply: true
|
|
|
2713
2913
|
var TARGETS = {
|
|
2714
2914
|
"claude-code": { relPath: "CLAUDE.md", mode: "block", detect: "CLAUDE.md" },
|
|
2715
2915
|
cursor: {
|
|
2716
|
-
relPath:
|
|
2916
|
+
relPath: join5(".cursor", "rules", "prometheus-memory.mdc"),
|
|
2717
2917
|
mode: "file",
|
|
2718
2918
|
fileContent: CURSOR_FRONTMATTER + withMarkers(RULE_BLOCK) + "\n",
|
|
2719
2919
|
detect: ".cursor"
|
|
2720
2920
|
},
|
|
2721
2921
|
augment: {
|
|
2722
|
-
relPath:
|
|
2922
|
+
relPath: join5(".augment", "rules", "prometheus-memory.md"),
|
|
2723
2923
|
mode: "file",
|
|
2724
2924
|
fileContent: withMarkers(RULE_BLOCK) + "\n",
|
|
2725
2925
|
detect: ".augment"
|
|
@@ -2727,19 +2927,19 @@ var TARGETS = {
|
|
|
2727
2927
|
agents: { relPath: "AGENTS.md", mode: "block", detect: "AGENTS.md" }
|
|
2728
2928
|
};
|
|
2729
2929
|
function detectRuntimes(workspaceRoot) {
|
|
2730
|
-
const found = MEMORY_RUNTIMES.filter((rt) => existsSync(
|
|
2930
|
+
const found = MEMORY_RUNTIMES.filter((rt) => existsSync(join5(workspaceRoot, TARGETS[rt].detect)));
|
|
2731
2931
|
return found.length > 0 ? found : ["agents"];
|
|
2732
2932
|
}
|
|
2733
2933
|
function existingRuntimes(workspaceRoot) {
|
|
2734
|
-
return MEMORY_RUNTIMES.filter((rt) => existsSync(
|
|
2934
|
+
return MEMORY_RUNTIMES.filter((rt) => existsSync(join5(workspaceRoot, TARGETS[rt].detect)));
|
|
2735
2935
|
}
|
|
2736
2936
|
function installedRuntimes(workspaceRoot) {
|
|
2737
2937
|
return MEMORY_RUNTIMES.filter((rt) => {
|
|
2738
|
-
const p =
|
|
2938
|
+
const p = join5(workspaceRoot, TARGETS[rt].relPath);
|
|
2739
2939
|
if (!existsSync(p))
|
|
2740
2940
|
return false;
|
|
2741
2941
|
try {
|
|
2742
|
-
return
|
|
2942
|
+
return readFileSync2(p, "utf-8").includes(BLOCK_START);
|
|
2743
2943
|
} catch {
|
|
2744
2944
|
return false;
|
|
2745
2945
|
}
|
|
@@ -2764,7 +2964,7 @@ function upsertBlock(existing, block) {
|
|
|
2764
2964
|
}
|
|
2765
2965
|
async function installRuntime(workspaceRoot, runtime) {
|
|
2766
2966
|
const target = TARGETS[runtime];
|
|
2767
|
-
const absPath =
|
|
2967
|
+
const absPath = join5(workspaceRoot, target.relPath);
|
|
2768
2968
|
const exists = existsSync(absPath);
|
|
2769
2969
|
const before = exists ? await readFile3(absPath, "utf-8") : "";
|
|
2770
2970
|
const after = target.mode === "file" ? target.fileContent : upsertBlock(before, RULE_BLOCK);
|
|
@@ -2930,9 +3130,19 @@ var setupInput = {
|
|
|
2930
3130
|
runtimes: z.array(runtimeEnum).min(1).optional()
|
|
2931
3131
|
};
|
|
2932
3132
|
var emptyInput = {};
|
|
2933
|
-
function registerTools(server, source) {
|
|
3133
|
+
function registerTools(server, source, hooks = {}) {
|
|
2934
3134
|
const ready = typeof source === "function" ? source : () => Promise.resolve(source);
|
|
2935
|
-
|
|
3135
|
+
const onToolCall = hooks.onToolCall;
|
|
3136
|
+
const reg = ((name, meta, handler) => server.registerTool(name, meta, (...callArgs) => {
|
|
3137
|
+
if (onToolCall !== void 0) {
|
|
3138
|
+
try {
|
|
3139
|
+
onToolCall(name);
|
|
3140
|
+
} catch {
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
return handler(...callArgs);
|
|
3144
|
+
}));
|
|
3145
|
+
reg("read", {
|
|
2936
3146
|
title: "Recall agent memory",
|
|
2937
3147
|
description: "Read agent memory for this project along the scope chain (project \u2192 workspace \u2192 tenant \u2192 system; narrowest scope wins). Syncs `.prometheus/memories/*.md` first, then returns the resolved records plus a prompt-ready `woven` markdown block (token-capped). Call this at the START of a session or task to recall what earlier sessions learned.",
|
|
2938
3148
|
inputSchema: readInput
|
|
@@ -2955,7 +3165,7 @@ function registerTools(server, source) {
|
|
|
2955
3165
|
records: records.map(recordToJson)
|
|
2956
3166
|
});
|
|
2957
3167
|
});
|
|
2958
|
-
|
|
3168
|
+
reg("write", {
|
|
2959
3169
|
title: "Store agent memory",
|
|
2960
3170
|
description: "Upsert one memory record (identity: scope+type+key). Use type `semantic` for durable facts, `procedural` for how-to knowledge, `episodic` for session events, `working` for short-lived notes. Default scope `project` also mirrors the value to `.prometheus/memories/<key>.md` (git-versioned, human-editable). Values matching the secret deny-list are rejected. Call this whenever the user states a durable preference, decision, or correction worth remembering.",
|
|
2961
3171
|
inputSchema: writeInput
|
|
@@ -2985,7 +3195,7 @@ ${args.value}`);
|
|
|
2985
3195
|
}
|
|
2986
3196
|
return textResult({ record: recordToJson(record), projectFile });
|
|
2987
3197
|
});
|
|
2988
|
-
|
|
3198
|
+
reg("capture", {
|
|
2989
3199
|
title: "Consolidate session learnings",
|
|
2990
3200
|
description: "Session-end consolidation: `plan`/`outcome` become one episodic record (key = sessionId), `facts` become semantic upserts, `procedures` become procedural upserts. Secret-bearing payloads are rejected. Call this at the END of a session to persist what was learned.",
|
|
2991
3201
|
inputSchema: captureInput
|
|
@@ -3049,7 +3259,7 @@ ${f.value}`);
|
|
|
3049
3259
|
});
|
|
3050
3260
|
return textResult({ written: written.map(recordToJson), extracted: extractedCount });
|
|
3051
3261
|
});
|
|
3052
|
-
|
|
3262
|
+
reg("search", {
|
|
3053
3263
|
title: "Search agent memory",
|
|
3054
3264
|
description: "Full-text search (FTS5) over memory keys and values within this project's scope chain, ranked by relevance. Returns matching records plus a highlighted snippet per hit. Use this when memory_read's recall is not specific enough. Does not bump useCount.",
|
|
3055
3265
|
inputSchema: searchInput
|
|
@@ -3075,7 +3285,7 @@ ${f.value}`);
|
|
|
3075
3285
|
}))
|
|
3076
3286
|
});
|
|
3077
3287
|
});
|
|
3078
|
-
|
|
3288
|
+
reg("list", {
|
|
3079
3289
|
title: "List stored memory (admin)",
|
|
3080
3290
|
description: "Flat listing of this project's memory records without scope resolution \u2014 inspection/debug surface. Optional filters: scope, type, keyContains (case-insensitive substring).",
|
|
3081
3291
|
inputSchema: listInput
|
|
@@ -3096,7 +3306,7 @@ ${f.value}`);
|
|
|
3096
3306
|
records: records.map(recordToJson)
|
|
3097
3307
|
});
|
|
3098
3308
|
});
|
|
3099
|
-
|
|
3309
|
+
reg("delete", {
|
|
3100
3310
|
title: "Delete stored memory",
|
|
3101
3311
|
description: "Delete one memory record by identity (scope+type+key). For project-scoped semantic records the mirrored `.prometheus/memories/<key>.md` file is removed as well. Returns whether a record/file was actually removed.",
|
|
3102
3312
|
inputSchema: deleteInput
|
|
@@ -3118,7 +3328,7 @@ ${f.value}`);
|
|
|
3118
3328
|
}
|
|
3119
3329
|
return textResult({ removed, fileRemoved });
|
|
3120
3330
|
});
|
|
3121
|
-
|
|
3331
|
+
reg("setup", {
|
|
3122
3332
|
title: "Install memory rules into runtime configs",
|
|
3123
3333
|
description: "Idempotently install the Prometheus memory-protocol rule block into agent runtime configs in this workspace: CLAUDE.md (claude-code), .cursor/rules/prometheus-memory.mdc (cursor), .augment/rules/prometheus-memory.md (augment), AGENTS.md (agents). Without `runtimes` it auto-detects which runtimes are present (fallback: agents). Only the marked block is written \u2014 existing content is never touched. Re-running updates the block in place.",
|
|
3124
3334
|
inputSchema: setupInput
|
|
@@ -3140,7 +3350,7 @@ ${f.value}`);
|
|
|
3140
3350
|
}
|
|
3141
3351
|
return textResult({ workspaceRoot, results });
|
|
3142
3352
|
});
|
|
3143
|
-
|
|
3353
|
+
reg("status", {
|
|
3144
3354
|
title: "Memory status / health check",
|
|
3145
3355
|
description: "Health check for this project's agent memory. Reports the resolved workspace root, project id, DB path, how many records are stored (total + by scope), the embedding provider with a zero-cost key-reachability probe, and which quality levers are active (rerank / rewrite / temporal). CALL THIS to confirm where memory is stored, how much is there, and whether the API key works.",
|
|
3146
3356
|
inputSchema: emptyInput
|
|
@@ -3161,7 +3371,8 @@ ${f.value}`);
|
|
|
3161
3371
|
embeddingsError = err instanceof Error ? err.message : String(err);
|
|
3162
3372
|
}
|
|
3163
3373
|
}
|
|
3164
|
-
const
|
|
3374
|
+
const update = await buildUpdateStatus("@prom.codes/memory-mcp", "0.11.1", { isDevBuild: false });
|
|
3375
|
+
const summary = deps.rootIsHomeOrFsRoot ? `Memory at ${dbPath}: ${stats.total} records, but the workspace resolved to ${workspaceRoot} (home/root) \u2014 open a project folder so memories scope and mirror correctly.` : `Memory at ${dbPath}: ${stats.total} records for project "${projectName}".${update.updateAvailable === true ? ` Update available: ${update.current} \u2192 ${update.latest} (ask me to run update via the context server's update_servers tool).` : ""}`;
|
|
3165
3376
|
return textResult({
|
|
3166
3377
|
installed: true,
|
|
3167
3378
|
project: { id: projectId, name: projectName, workspaceRoot },
|
|
@@ -3187,6 +3398,7 @@ ${f.value}`);
|
|
|
3187
3398
|
dedup: deps.dedupEnabled,
|
|
3188
3399
|
extract: deps.extractorId
|
|
3189
3400
|
},
|
|
3401
|
+
update,
|
|
3190
3402
|
summary
|
|
3191
3403
|
});
|
|
3192
3404
|
});
|
|
@@ -3195,7 +3407,7 @@ ${f.value}`);
|
|
|
3195
3407
|
// dist/server.js
|
|
3196
3408
|
var SERVER_IDENTITY = {
|
|
3197
3409
|
name: "prometheus-memory-mcp",
|
|
3198
|
-
version: "0.
|
|
3410
|
+
version: "0.11.1",
|
|
3199
3411
|
title: "prom.codes Memory"
|
|
3200
3412
|
};
|
|
3201
3413
|
var SERVER_INSTRUCTIONS = "Persistent agent memory for this workspace \u2014 USE IT PROACTIVELY; the user will not tell you to. Protocol:\n1. ONE-TIME: if this workspace has no Prometheus memory rule yet, call memory_setup now (idempotent) so the protocol is installed into the runtime rule files and survives future sessions. (The server also auto-installs it on startup when a project rule file already exists \u2014 memory_setup covers the rest.)\n2. SESSION START: before any non-trivial task, call memory_read to recall facts, decisions and procedures from earlier sessions.\n3. DURING WORK: when the user states a durable preference, decision, correction or project fact, store it with memory_write (semantic for facts, procedural for how-tos) \u2014 without being asked.\n4. LOOK-UP: use memory_search for keyword recall when memory_read is not specific enough.\n5. SESSION END: consolidate what was learned with memory_capture.\nCall memory_status anytime to check what is stored and whether the rule is installed. Never store secrets, API keys or credentials \u2014 such writes are rejected.";
|
|
@@ -3211,6 +3423,11 @@ async function main() {
|
|
|
3211
3423
|
const claudeRoot = (env.CLAUDE_PROJECT_DIR ?? "").trim();
|
|
3212
3424
|
const eagerVia = explicitRoot !== "" ? "PROMETHEUS_WORKSPACE_ROOT" : claudeRoot !== "" ? "CLAUDE_PROJECT_DIR" : null;
|
|
3213
3425
|
void maybeNotifyUpdate(import.meta.url, env);
|
|
3426
|
+
const heartbeat = startHeartbeat({
|
|
3427
|
+
server: "memory",
|
|
3428
|
+
version: SERVER_IDENTITY.version,
|
|
3429
|
+
env
|
|
3430
|
+
});
|
|
3214
3431
|
const transport = new StdioServerTransport();
|
|
3215
3432
|
const server = new McpServer2(SERVER_IDENTITY, {
|
|
3216
3433
|
capabilities: { tools: {} },
|
|
@@ -3221,7 +3438,10 @@ async function main() {
|
|
|
3221
3438
|
const composedReady = new Promise((res) => {
|
|
3222
3439
|
composedResolve = res;
|
|
3223
3440
|
});
|
|
3224
|
-
registerTools(server, () => composedReady
|
|
3441
|
+
registerTools(server, () => composedReady, {
|
|
3442
|
+
onToolCall: (tool) => heartbeat.update({ lastTool: tool, lastToolCallAt: Date.now() })
|
|
3443
|
+
});
|
|
3444
|
+
let watchdog = null;
|
|
3225
3445
|
let shuttingDown = false;
|
|
3226
3446
|
const shutdown = async (reason) => {
|
|
3227
3447
|
if (shuttingDown)
|
|
@@ -3229,6 +3449,8 @@ async function main() {
|
|
|
3229
3449
|
shuttingDown = true;
|
|
3230
3450
|
process.stderr.write(`prometheus-memory-mcp: ${reason}, shutting down
|
|
3231
3451
|
`);
|
|
3452
|
+
watchdog?.stop();
|
|
3453
|
+
heartbeat.stop();
|
|
3232
3454
|
try {
|
|
3233
3455
|
await server.close();
|
|
3234
3456
|
} finally {
|
|
@@ -3242,6 +3464,18 @@ async function main() {
|
|
|
3242
3464
|
process.stdin.once("end", () => void shutdown("stdin closed (client exited)"));
|
|
3243
3465
|
process.stdin.once("close", () => void shutdown("stdin closed (client exited)"));
|
|
3244
3466
|
server.server.onclose = () => void shutdown("transport closed (client exited)");
|
|
3467
|
+
watchdog = createIdleWatchdog({ onIdle: (reason) => void shutdown(reason), env });
|
|
3468
|
+
if (watchdog.idleMs > 0) {
|
|
3469
|
+
process.stderr.write(`prometheus-memory-mcp: idle self-exit armed (${Math.round(watchdog.idleMs / 6e4)} min of no client activity)
|
|
3470
|
+
`);
|
|
3471
|
+
}
|
|
3472
|
+
const armIdleWatch = () => {
|
|
3473
|
+
const prev = transport.onmessage?.bind(transport);
|
|
3474
|
+
transport.onmessage = ((...args) => {
|
|
3475
|
+
watchdog?.touch();
|
|
3476
|
+
prev?.(...args);
|
|
3477
|
+
});
|
|
3478
|
+
};
|
|
3245
3479
|
const boot = (override, via) => {
|
|
3246
3480
|
composed = composeFromEnv({
|
|
3247
3481
|
env,
|
|
@@ -3249,6 +3483,15 @@ async function main() {
|
|
|
3249
3483
|
});
|
|
3250
3484
|
process.stderr.write(`prometheus-memory-mcp: workspace=${composed.workspaceRoot} (via ${via}) project=${composed.projectName} (${composed.projectId}) db=${composed.dbPath} embed=${composed.embedderId}${composed.embeddingsEnabled ? "" : " (keyword-only)"} rerank=${composed.rerankerId} extract=${composed.extractorId} rewrite=${composed.rewriterId} temporal=${composed.temporalEnabled ? "on" : "off"} dedup=${composed.dedupEnabled ? "on" : "off"}
|
|
3251
3485
|
`);
|
|
3486
|
+
heartbeat.update({
|
|
3487
|
+
workspaceRoot: composed.workspaceRoot,
|
|
3488
|
+
extra: {
|
|
3489
|
+
dbPath: composed.dbPath,
|
|
3490
|
+
projectId: composed.projectId,
|
|
3491
|
+
projectName: composed.projectName,
|
|
3492
|
+
embed: composed.embedderId
|
|
3493
|
+
}
|
|
3494
|
+
});
|
|
3252
3495
|
if (composed.rootIsHomeOrFsRoot) {
|
|
3253
3496
|
process.stderr.write(`prometheus-memory-mcp: workspace resolved to ${composed.workspaceRoot} (your home directory or a filesystem root) \u2014 project memories will NOT be mirrored to markdown there. Open a project folder (Claude Code passes it via CLAUDE_PROJECT_DIR) or set PROMETHEUS_WORKSPACE_ROOT. Call memory_status for details.
|
|
3254
3497
|
`);
|
|
@@ -3267,6 +3510,7 @@ async function main() {
|
|
|
3267
3510
|
if (eagerVia !== null) {
|
|
3268
3511
|
boot(void 0, eagerVia);
|
|
3269
3512
|
await server.connect(transport);
|
|
3513
|
+
armIdleWatch();
|
|
3270
3514
|
return;
|
|
3271
3515
|
}
|
|
3272
3516
|
let booted = false;
|
|
@@ -3290,6 +3534,7 @@ async function main() {
|
|
|
3290
3534
|
void resolveAndBoot();
|
|
3291
3535
|
};
|
|
3292
3536
|
await server.connect(transport);
|
|
3537
|
+
armIdleWatch();
|
|
3293
3538
|
const t = setTimeout(() => void resolveAndBoot(), 5e3);
|
|
3294
3539
|
t.unref?.();
|
|
3295
3540
|
}
|