@prom.codes/memory-mcp 0.10.3 → 0.11.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.
- package/README.md +3 -2
- package/dist/bin.js +201 -28
- 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);
|
|
@@ -92,6 +93,29 @@ function cachePath(dir, name) {
|
|
|
92
93
|
const safe = name.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
93
94
|
return join(dir, `.update-check-${safe}.json`);
|
|
94
95
|
}
|
|
96
|
+
function availabilityMarkerPath(dir, name) {
|
|
97
|
+
const safe = name.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
98
|
+
return join(dir, `.update-available-${safe}.json`);
|
|
99
|
+
}
|
|
100
|
+
async function syncAvailabilityMarker(dir, name, current, latest, updateAvailable) {
|
|
101
|
+
const file = availabilityMarkerPath(dir, name);
|
|
102
|
+
try {
|
|
103
|
+
if (updateAvailable && latest !== null) {
|
|
104
|
+
const marker = {
|
|
105
|
+
name,
|
|
106
|
+
current,
|
|
107
|
+
latest,
|
|
108
|
+
command: UPGRADE_COMMAND,
|
|
109
|
+
notedAt: Date.now()
|
|
110
|
+
};
|
|
111
|
+
await mkdir(dir, { recursive: true }).catch(() => void 0);
|
|
112
|
+
await writeFile(file, JSON.stringify(marker), "utf8");
|
|
113
|
+
} else if (latest !== null) {
|
|
114
|
+
await rm(file, { force: true });
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
95
119
|
async function readCache(path2) {
|
|
96
120
|
try {
|
|
97
121
|
const raw = await readFile(path2, "utf8");
|
|
@@ -120,7 +144,7 @@ async function fetchLatest(name, fetchImpl, timeoutMs) {
|
|
|
120
144
|
const url = `https://registry.npmjs.org/${name.replace("/", "%2F")}/latest`;
|
|
121
145
|
const res = await fetchImpl(url, {
|
|
122
146
|
signal: controller.signal,
|
|
123
|
-
headers: { accept: "application/
|
|
147
|
+
headers: { accept: "application/json" }
|
|
124
148
|
});
|
|
125
149
|
if (!res.ok)
|
|
126
150
|
return null;
|
|
@@ -155,6 +179,7 @@ async function checkForUpdate(options) {
|
|
|
155
179
|
const updateAvailable2 = cached.latest !== null && isNewerVersion(cached.latest, version);
|
|
156
180
|
if (updateAvailable2)
|
|
157
181
|
notify(log, name, version, cached.latest);
|
|
182
|
+
await syncAvailabilityMarker(cacheDir, name, version, cached.latest, updateAvailable2);
|
|
158
183
|
return {
|
|
159
184
|
...base,
|
|
160
185
|
latest: cached.latest,
|
|
@@ -173,13 +198,62 @@ async function checkForUpdate(options) {
|
|
|
173
198
|
const updateAvailable = isNewerVersion(latest, version);
|
|
174
199
|
if (updateAvailable)
|
|
175
200
|
notify(log, name, version, latest);
|
|
201
|
+
await syncAvailabilityMarker(cacheDir, name, version, latest, updateAvailable);
|
|
176
202
|
return { ...base, latest, checked: true, updateAvailable };
|
|
177
203
|
}
|
|
204
|
+
async function getLatestVersion(name, options = {}) {
|
|
205
|
+
const { env = process.env, fetch: fetchImpl = globalThis.fetch, cacheDir = join(homedir(), ".prometheus"), cacheTtlMs = DEFAULT_TTL_MS, timeoutMs = DEFAULT_TIMEOUT_MS } = options;
|
|
206
|
+
const file = cachePath(cacheDir, name);
|
|
207
|
+
const cached = await readCache(file);
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
if (cached !== null && cached.latest !== null && now - cached.checkedAt < cacheTtlMs) {
|
|
210
|
+
return cached.latest;
|
|
211
|
+
}
|
|
212
|
+
if (OPT_OUT_RE.test(env.PROMETHEUS_NO_UPDATE_CHECK ?? "") || typeof fetchImpl !== "function") {
|
|
213
|
+
return cached?.latest ?? null;
|
|
214
|
+
}
|
|
215
|
+
const latest = await fetchLatest(name, fetchImpl, timeoutMs);
|
|
216
|
+
if (latest !== null) {
|
|
217
|
+
await mkdir(cacheDir, { recursive: true }).catch(() => void 0);
|
|
218
|
+
await writeCache(file, { checkedAt: now, latest });
|
|
219
|
+
return latest;
|
|
220
|
+
}
|
|
221
|
+
return cached?.latest ?? null;
|
|
222
|
+
}
|
|
178
223
|
function notify(log, name, current, latest) {
|
|
179
224
|
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
225
|
`);
|
|
181
226
|
}
|
|
182
227
|
|
|
228
|
+
// ../shared/dist/update-info.js
|
|
229
|
+
async function buildUpdateStatus(pkgName, currentVersion, options = {}) {
|
|
230
|
+
const base = { current: currentVersion, command: UPGRADE_COMMAND };
|
|
231
|
+
if (options.isDevBuild === true) {
|
|
232
|
+
return {
|
|
233
|
+
...base,
|
|
234
|
+
latest: null,
|
|
235
|
+
updateAvailable: null,
|
|
236
|
+
note: "dev build (workspace) \u2014 version comparison skipped"
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
let latest = null;
|
|
240
|
+
try {
|
|
241
|
+
latest = await getLatestVersion(pkgName, {
|
|
242
|
+
...options.env !== void 0 ? { env: options.env } : {},
|
|
243
|
+
...options.fetch !== void 0 ? { fetch: options.fetch } : {},
|
|
244
|
+
...options.cacheDir !== void 0 ? { cacheDir: options.cacheDir } : {},
|
|
245
|
+
timeoutMs: options.timeoutMs ?? 1500
|
|
246
|
+
});
|
|
247
|
+
} catch {
|
|
248
|
+
latest = null;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
...base,
|
|
252
|
+
latest,
|
|
253
|
+
updateAvailable: latest !== null ? isNewerVersion(latest, currentVersion) : null
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
183
257
|
// ../shared/dist/workspace-root.js
|
|
184
258
|
import { homedir as homedir2 } from "node:os";
|
|
185
259
|
import { dirname, resolve } from "node:path";
|
|
@@ -194,10 +268,80 @@ function isHomeOrFilesystemRoot(root) {
|
|
|
194
268
|
return false;
|
|
195
269
|
}
|
|
196
270
|
|
|
271
|
+
// ../shared/dist/heartbeat.js
|
|
272
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
273
|
+
import { homedir as homedir3 } from "node:os";
|
|
274
|
+
import { join as join2 } from "node:path";
|
|
275
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
276
|
+
var STALE_AFTER_MS = 5 * 6e4;
|
|
277
|
+
function defaultStatusDir(env = process.env) {
|
|
278
|
+
const override = (env.PROMETHEUS_STATUS_DIR ?? "").trim();
|
|
279
|
+
if (override !== "")
|
|
280
|
+
return override;
|
|
281
|
+
return join2(homedir3(), ".prometheus", "status");
|
|
282
|
+
}
|
|
283
|
+
function startHeartbeat(options) {
|
|
284
|
+
const dir = options.dir ?? defaultStatusDir(options.env ?? process.env);
|
|
285
|
+
const file = join2(dir, `${options.server}-${process.pid}.json`);
|
|
286
|
+
const intervalMs = options.intervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
287
|
+
let record = {
|
|
288
|
+
server: options.server,
|
|
289
|
+
pid: process.pid,
|
|
290
|
+
version: options.version,
|
|
291
|
+
startedAt: Date.now(),
|
|
292
|
+
updatedAt: Date.now(),
|
|
293
|
+
workspaceRoot: options.workspaceRoot ?? null
|
|
294
|
+
};
|
|
295
|
+
let stopped = false;
|
|
296
|
+
const persist = () => {
|
|
297
|
+
if (stopped)
|
|
298
|
+
return;
|
|
299
|
+
try {
|
|
300
|
+
mkdirSync(dir, { recursive: true });
|
|
301
|
+
writeFileSync(file, JSON.stringify(record), "utf8");
|
|
302
|
+
} catch {
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
const remove = () => {
|
|
306
|
+
try {
|
|
307
|
+
rmSync(file, { force: true });
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
persist();
|
|
312
|
+
const timer = setInterval(() => {
|
|
313
|
+
record = { ...record, updatedAt: Date.now() };
|
|
314
|
+
persist();
|
|
315
|
+
}, intervalMs);
|
|
316
|
+
timer.unref?.();
|
|
317
|
+
const onExit = () => {
|
|
318
|
+
stopped = true;
|
|
319
|
+
remove();
|
|
320
|
+
};
|
|
321
|
+
process.once("exit", onExit);
|
|
322
|
+
return {
|
|
323
|
+
file,
|
|
324
|
+
update(patch) {
|
|
325
|
+
if (stopped)
|
|
326
|
+
return;
|
|
327
|
+
record = { ...record, ...patch, updatedAt: Date.now() };
|
|
328
|
+
persist();
|
|
329
|
+
},
|
|
330
|
+
stop() {
|
|
331
|
+
if (stopped)
|
|
332
|
+
return;
|
|
333
|
+
stopped = true;
|
|
334
|
+
clearInterval(timer);
|
|
335
|
+
process.removeListener("exit", onExit);
|
|
336
|
+
remove();
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
197
341
|
// dist/composition.js
|
|
198
342
|
import { createHash } from "node:crypto";
|
|
199
|
-
import { homedir as
|
|
200
|
-
import { basename, join as
|
|
343
|
+
import { homedir as homedir4 } from "node:os";
|
|
344
|
+
import { basename, join as join3, resolve as resolve2 } from "node:path";
|
|
201
345
|
|
|
202
346
|
// ../embeddings-openai-compat/dist/index.js
|
|
203
347
|
var DEFAULT_BATCH = 96;
|
|
@@ -1445,7 +1589,7 @@ var OpenAICompatRewriter = class {
|
|
|
1445
1589
|
|
|
1446
1590
|
// dist/sqlite.js
|
|
1447
1591
|
import { randomUUID } from "node:crypto";
|
|
1448
|
-
import { mkdirSync } from "node:fs";
|
|
1592
|
+
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
1449
1593
|
import { dirname as dirname2 } from "node:path";
|
|
1450
1594
|
import Database from "better-sqlite3";
|
|
1451
1595
|
|
|
@@ -1773,7 +1917,7 @@ var SqliteMemoryBackend = class {
|
|
|
1773
1917
|
closed = false;
|
|
1774
1918
|
constructor(dbPath, opts = {}) {
|
|
1775
1919
|
if (dbPath !== ":memory:") {
|
|
1776
|
-
|
|
1920
|
+
mkdirSync2(dirname2(dbPath), { recursive: true });
|
|
1777
1921
|
}
|
|
1778
1922
|
this.db = new Database(dbPath);
|
|
1779
1923
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -2226,7 +2370,7 @@ function projectIdFor(workspaceRoot) {
|
|
|
2226
2370
|
return createHash("sha256").update(abs).digest("hex").slice(0, 16);
|
|
2227
2371
|
}
|
|
2228
2372
|
function defaultMemoryDbPath() {
|
|
2229
|
-
return
|
|
2373
|
+
return join3(homedir4(), ".prometheus", "memory.db");
|
|
2230
2374
|
}
|
|
2231
2375
|
function intEnv(env, name, def) {
|
|
2232
2376
|
const raw = env[name];
|
|
@@ -2667,9 +2811,9 @@ function assertNoSecrets(text) {
|
|
|
2667
2811
|
}
|
|
2668
2812
|
|
|
2669
2813
|
// dist/setup.js
|
|
2670
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2814
|
+
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
2671
2815
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2672
|
-
import { dirname as dirname3, join as
|
|
2816
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
2673
2817
|
var MEMORY_RUNTIMES = [
|
|
2674
2818
|
"claude-code",
|
|
2675
2819
|
"cursor",
|
|
@@ -2713,13 +2857,13 @@ alwaysApply: true
|
|
|
2713
2857
|
var TARGETS = {
|
|
2714
2858
|
"claude-code": { relPath: "CLAUDE.md", mode: "block", detect: "CLAUDE.md" },
|
|
2715
2859
|
cursor: {
|
|
2716
|
-
relPath:
|
|
2860
|
+
relPath: join5(".cursor", "rules", "prometheus-memory.mdc"),
|
|
2717
2861
|
mode: "file",
|
|
2718
2862
|
fileContent: CURSOR_FRONTMATTER + withMarkers(RULE_BLOCK) + "\n",
|
|
2719
2863
|
detect: ".cursor"
|
|
2720
2864
|
},
|
|
2721
2865
|
augment: {
|
|
2722
|
-
relPath:
|
|
2866
|
+
relPath: join5(".augment", "rules", "prometheus-memory.md"),
|
|
2723
2867
|
mode: "file",
|
|
2724
2868
|
fileContent: withMarkers(RULE_BLOCK) + "\n",
|
|
2725
2869
|
detect: ".augment"
|
|
@@ -2727,19 +2871,19 @@ var TARGETS = {
|
|
|
2727
2871
|
agents: { relPath: "AGENTS.md", mode: "block", detect: "AGENTS.md" }
|
|
2728
2872
|
};
|
|
2729
2873
|
function detectRuntimes(workspaceRoot) {
|
|
2730
|
-
const found = MEMORY_RUNTIMES.filter((rt) => existsSync(
|
|
2874
|
+
const found = MEMORY_RUNTIMES.filter((rt) => existsSync(join5(workspaceRoot, TARGETS[rt].detect)));
|
|
2731
2875
|
return found.length > 0 ? found : ["agents"];
|
|
2732
2876
|
}
|
|
2733
2877
|
function existingRuntimes(workspaceRoot) {
|
|
2734
|
-
return MEMORY_RUNTIMES.filter((rt) => existsSync(
|
|
2878
|
+
return MEMORY_RUNTIMES.filter((rt) => existsSync(join5(workspaceRoot, TARGETS[rt].detect)));
|
|
2735
2879
|
}
|
|
2736
2880
|
function installedRuntimes(workspaceRoot) {
|
|
2737
2881
|
return MEMORY_RUNTIMES.filter((rt) => {
|
|
2738
|
-
const p =
|
|
2882
|
+
const p = join5(workspaceRoot, TARGETS[rt].relPath);
|
|
2739
2883
|
if (!existsSync(p))
|
|
2740
2884
|
return false;
|
|
2741
2885
|
try {
|
|
2742
|
-
return
|
|
2886
|
+
return readFileSync2(p, "utf-8").includes(BLOCK_START);
|
|
2743
2887
|
} catch {
|
|
2744
2888
|
return false;
|
|
2745
2889
|
}
|
|
@@ -2764,7 +2908,7 @@ function upsertBlock(existing, block) {
|
|
|
2764
2908
|
}
|
|
2765
2909
|
async function installRuntime(workspaceRoot, runtime) {
|
|
2766
2910
|
const target = TARGETS[runtime];
|
|
2767
|
-
const absPath =
|
|
2911
|
+
const absPath = join5(workspaceRoot, target.relPath);
|
|
2768
2912
|
const exists = existsSync(absPath);
|
|
2769
2913
|
const before = exists ? await readFile3(absPath, "utf-8") : "";
|
|
2770
2914
|
const after = target.mode === "file" ? target.fileContent : upsertBlock(before, RULE_BLOCK);
|
|
@@ -2930,9 +3074,19 @@ var setupInput = {
|
|
|
2930
3074
|
runtimes: z.array(runtimeEnum).min(1).optional()
|
|
2931
3075
|
};
|
|
2932
3076
|
var emptyInput = {};
|
|
2933
|
-
function registerTools(server, source) {
|
|
3077
|
+
function registerTools(server, source, hooks = {}) {
|
|
2934
3078
|
const ready = typeof source === "function" ? source : () => Promise.resolve(source);
|
|
2935
|
-
|
|
3079
|
+
const onToolCall = hooks.onToolCall;
|
|
3080
|
+
const reg = ((name, meta, handler) => server.registerTool(name, meta, (...callArgs) => {
|
|
3081
|
+
if (onToolCall !== void 0) {
|
|
3082
|
+
try {
|
|
3083
|
+
onToolCall(name);
|
|
3084
|
+
} catch {
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
return handler(...callArgs);
|
|
3088
|
+
}));
|
|
3089
|
+
reg("read", {
|
|
2936
3090
|
title: "Recall agent memory",
|
|
2937
3091
|
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
3092
|
inputSchema: readInput
|
|
@@ -2955,7 +3109,7 @@ function registerTools(server, source) {
|
|
|
2955
3109
|
records: records.map(recordToJson)
|
|
2956
3110
|
});
|
|
2957
3111
|
});
|
|
2958
|
-
|
|
3112
|
+
reg("write", {
|
|
2959
3113
|
title: "Store agent memory",
|
|
2960
3114
|
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
3115
|
inputSchema: writeInput
|
|
@@ -2985,7 +3139,7 @@ ${args.value}`);
|
|
|
2985
3139
|
}
|
|
2986
3140
|
return textResult({ record: recordToJson(record), projectFile });
|
|
2987
3141
|
});
|
|
2988
|
-
|
|
3142
|
+
reg("capture", {
|
|
2989
3143
|
title: "Consolidate session learnings",
|
|
2990
3144
|
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
3145
|
inputSchema: captureInput
|
|
@@ -3049,7 +3203,7 @@ ${f.value}`);
|
|
|
3049
3203
|
});
|
|
3050
3204
|
return textResult({ written: written.map(recordToJson), extracted: extractedCount });
|
|
3051
3205
|
});
|
|
3052
|
-
|
|
3206
|
+
reg("search", {
|
|
3053
3207
|
title: "Search agent memory",
|
|
3054
3208
|
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
3209
|
inputSchema: searchInput
|
|
@@ -3075,7 +3229,7 @@ ${f.value}`);
|
|
|
3075
3229
|
}))
|
|
3076
3230
|
});
|
|
3077
3231
|
});
|
|
3078
|
-
|
|
3232
|
+
reg("list", {
|
|
3079
3233
|
title: "List stored memory (admin)",
|
|
3080
3234
|
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
3235
|
inputSchema: listInput
|
|
@@ -3096,7 +3250,7 @@ ${f.value}`);
|
|
|
3096
3250
|
records: records.map(recordToJson)
|
|
3097
3251
|
});
|
|
3098
3252
|
});
|
|
3099
|
-
|
|
3253
|
+
reg("delete", {
|
|
3100
3254
|
title: "Delete stored memory",
|
|
3101
3255
|
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
3256
|
inputSchema: deleteInput
|
|
@@ -3118,7 +3272,7 @@ ${f.value}`);
|
|
|
3118
3272
|
}
|
|
3119
3273
|
return textResult({ removed, fileRemoved });
|
|
3120
3274
|
});
|
|
3121
|
-
|
|
3275
|
+
reg("setup", {
|
|
3122
3276
|
title: "Install memory rules into runtime configs",
|
|
3123
3277
|
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
3278
|
inputSchema: setupInput
|
|
@@ -3140,7 +3294,7 @@ ${f.value}`);
|
|
|
3140
3294
|
}
|
|
3141
3295
|
return textResult({ workspaceRoot, results });
|
|
3142
3296
|
});
|
|
3143
|
-
|
|
3297
|
+
reg("status", {
|
|
3144
3298
|
title: "Memory status / health check",
|
|
3145
3299
|
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
3300
|
inputSchema: emptyInput
|
|
@@ -3161,7 +3315,8 @@ ${f.value}`);
|
|
|
3161
3315
|
embeddingsError = err instanceof Error ? err.message : String(err);
|
|
3162
3316
|
}
|
|
3163
3317
|
}
|
|
3164
|
-
const
|
|
3318
|
+
const update = await buildUpdateStatus("@prom.codes/memory-mcp", "0.11.0", { isDevBuild: false });
|
|
3319
|
+
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
3320
|
return textResult({
|
|
3166
3321
|
installed: true,
|
|
3167
3322
|
project: { id: projectId, name: projectName, workspaceRoot },
|
|
@@ -3187,6 +3342,7 @@ ${f.value}`);
|
|
|
3187
3342
|
dedup: deps.dedupEnabled,
|
|
3188
3343
|
extract: deps.extractorId
|
|
3189
3344
|
},
|
|
3345
|
+
update,
|
|
3190
3346
|
summary
|
|
3191
3347
|
});
|
|
3192
3348
|
});
|
|
@@ -3195,7 +3351,7 @@ ${f.value}`);
|
|
|
3195
3351
|
// dist/server.js
|
|
3196
3352
|
var SERVER_IDENTITY = {
|
|
3197
3353
|
name: "prometheus-memory-mcp",
|
|
3198
|
-
version: "0.
|
|
3354
|
+
version: "0.11.0",
|
|
3199
3355
|
title: "prom.codes Memory"
|
|
3200
3356
|
};
|
|
3201
3357
|
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 +3367,11 @@ async function main() {
|
|
|
3211
3367
|
const claudeRoot = (env.CLAUDE_PROJECT_DIR ?? "").trim();
|
|
3212
3368
|
const eagerVia = explicitRoot !== "" ? "PROMETHEUS_WORKSPACE_ROOT" : claudeRoot !== "" ? "CLAUDE_PROJECT_DIR" : null;
|
|
3213
3369
|
void maybeNotifyUpdate(import.meta.url, env);
|
|
3370
|
+
const heartbeat = startHeartbeat({
|
|
3371
|
+
server: "memory",
|
|
3372
|
+
version: SERVER_IDENTITY.version,
|
|
3373
|
+
env
|
|
3374
|
+
});
|
|
3214
3375
|
const transport = new StdioServerTransport();
|
|
3215
3376
|
const server = new McpServer2(SERVER_IDENTITY, {
|
|
3216
3377
|
capabilities: { tools: {} },
|
|
@@ -3221,7 +3382,9 @@ async function main() {
|
|
|
3221
3382
|
const composedReady = new Promise((res) => {
|
|
3222
3383
|
composedResolve = res;
|
|
3223
3384
|
});
|
|
3224
|
-
registerTools(server, () => composedReady
|
|
3385
|
+
registerTools(server, () => composedReady, {
|
|
3386
|
+
onToolCall: (tool) => heartbeat.update({ lastTool: tool, lastToolCallAt: Date.now() })
|
|
3387
|
+
});
|
|
3225
3388
|
let shuttingDown = false;
|
|
3226
3389
|
const shutdown = async (reason) => {
|
|
3227
3390
|
if (shuttingDown)
|
|
@@ -3229,6 +3392,7 @@ async function main() {
|
|
|
3229
3392
|
shuttingDown = true;
|
|
3230
3393
|
process.stderr.write(`prometheus-memory-mcp: ${reason}, shutting down
|
|
3231
3394
|
`);
|
|
3395
|
+
heartbeat.stop();
|
|
3232
3396
|
try {
|
|
3233
3397
|
await server.close();
|
|
3234
3398
|
} finally {
|
|
@@ -3249,6 +3413,15 @@ async function main() {
|
|
|
3249
3413
|
});
|
|
3250
3414
|
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
3415
|
`);
|
|
3416
|
+
heartbeat.update({
|
|
3417
|
+
workspaceRoot: composed.workspaceRoot,
|
|
3418
|
+
extra: {
|
|
3419
|
+
dbPath: composed.dbPath,
|
|
3420
|
+
projectId: composed.projectId,
|
|
3421
|
+
projectName: composed.projectName,
|
|
3422
|
+
embed: composed.embedderId
|
|
3423
|
+
}
|
|
3424
|
+
});
|
|
3252
3425
|
if (composed.rootIsHomeOrFsRoot) {
|
|
3253
3426
|
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
3427
|
`);
|